# Table of Contents - [N64brew Wiki](#n64brew-wiki) - [COP1 - N64brew Wiki](#cop1-n64brew-wiki) - [Help:Editing - N64brew Wiki](#help-editing-n64brew-wiki) - [SysAD Interface - N64brew Wiki](#sysad-interface-n64brew-wiki) - [RDRAM - N64brew Wiki](#rdram-n64brew-wiki) - [Reality Coprocessor - N64brew Wiki](#reality-coprocessor-n64brew-wiki) - [FAQ - N64brew Wiki](#faq-n64brew-wiki) - [VR4300 - N64brew Wiki](#vr4300-n64brew-wiki) - [Reality Signal Processor - N64brew Wiki](#reality-signal-processor-n64brew-wiki) - [Homebrew Projects - N64brew Wiki](#homebrew-projects-n64brew-wiki) - [Reality Display Processor - N64brew Wiki](#reality-display-processor-n64brew-wiki) - [Audio DAC - N64brew Wiki](#audio-dac-n64brew-wiki) - [PIF-NUS - N64brew Wiki](#pif-nus-n64brew-wiki) - [Video DAC - N64brew Wiki](#video-dac-n64brew-wiki) - [Memory map - N64brew Wiki](#memory-map-n64brew-wiki) - [Audio Interface - N64brew Wiki](#audio-interface-n64brew-wiki) - [Train Controller - N64brew Wiki](#train-controller-n64brew-wiki) - [Mouse - N64brew Wiki](#mouse-n64brew-wiki) - [Fishing Rod - N64brew Wiki](#fishing-rod-n64brew-wiki) - [Voice Recognition Unit - N64brew Wiki](#voice-recognition-unit-n64brew-wiki) - [Transfer Pak - N64brew Wiki](#transfer-pak-n64brew-wiki) - [Doctor V64 - N64brew Wiki](#doctor-v64-n64brew-wiki) - [Libdragon - N64brew Wiki](#libdragon-n64brew-wiki) - [IQue SDK - N64brew Wiki](#ique-sdk-n64brew-wiki) - [N64 IRIX - N64brew Wiki](#n64-irix-n64brew-wiki) - [SGI Audio Tools - N64brew Wiki](#sgi-audio-tools-n64brew-wiki) - [Partner-N64 - N64brew Wiki](#partner-n64-n64brew-wiki) - [Building GCC - N64brew Wiki](#building-gcc-n64brew-wiki) - [Game Pak - N64brew Wiki](#game-pak-n64brew-wiki) - [Rumble Pak - N64brew Wiki](#rumble-pak-n64brew-wiki) - [Video Interface - N64brew Wiki](#video-interface-n64brew-wiki) - [Jumper Pak - N64brew Wiki](#jumper-pak-n64brew-wiki) - [Pseultra - N64brew Wiki](#pseultra-n64brew-wiki) - [MIPS Assembly - N64brew Wiki](#mips-assembly-n64brew-wiki) - [Serial Interface - N64brew Wiki](#serial-interface-n64brew-wiki) - [RDRAM Interface - N64brew Wiki](#rdram-interface-n64brew-wiki) - [Expansion Pak - N64brew Wiki](#expansion-pak-n64brew-wiki) - [MIPS III instructions - N64brew Wiki](#mips-iii-instructions-n64brew-wiki) - [MIPS Interface - N64brew Wiki](#mips-interface-n64brew-wiki) - [Parallel Interface - N64brew Wiki](#parallel-interface-n64brew-wiki) - [Konami Dance Pad - N64brew Wiki](#konami-dance-pad-n64brew-wiki) - [Keyboard - N64brew Wiki](#keyboard-n64brew-wiki) - [Flashcarts - N64brew Wiki](#flashcarts-n64brew-wiki) - [Controller - N64brew Wiki](#controller-n64brew-wiki) - [Joybus Protocol - N64brew Wiki](#joybus-protocol-n64brew-wiki) - [Controller Pak - N64brew Wiki](#controller-pak-n64brew-wiki) - [64DD - N64brew Wiki](#64dd-n64brew-wiki) - [Category:Game Jams - N64brew Wiki](#category-game-jams-n64brew-wiki) - [Getting Started - N64brew Wiki](#getting-started-n64brew-wiki) - [Libultra - N64brew Wiki](#libultra-n64brew-wiki) - [Todo - N64brew Wiki](#todo-n64brew-wiki) - [Reality Coprocessor - N64brew Wiki](#reality-coprocessor-n64brew-wiki) - [Reality Display Processor/Hazards - N64brew Wiki](#reality-display-processor-hazards-n64brew-wiki) - [N64brew Game Jam 2022 - N64brew Wiki](#n64brew-game-jam-2022-n64brew-wiki) - [Reality Signal Processor/CPU Core - N64brew Wiki](#reality-signal-processor-cpu-core-n64brew-wiki) - [PIF-NUS - N64brew Wiki](#pif-nus-n64brew-wiki) - [Reality Signal Processor/CPU Pipeline - N64brew Wiki](#reality-signal-processor-cpu-pipeline-n64brew-wiki) - [Reality Display Processor/Interface - N64brew Wiki](#reality-display-processor-interface-n64brew-wiki) - [Lunar Assault 64 - N64brew Wiki](#lunar-assault-64-n64brew-wiki) - [N64brew Game Jam 2020 - N64brew Wiki](#n64brew-game-jam-2020-n64brew-wiki) - [Reality Signal Processor/Interface - N64brew Wiki](#reality-signal-processor-interface-n64brew-wiki) - [N64brew Game Jam 2021 - N64brew Wiki](#n64brew-game-jam-2021-n64brew-wiki) - [Reality Display Processor/Pipeline - N64brew Wiki](#reality-display-processor-pipeline-n64brew-wiki) - [SysAD Interface - N64brew Wiki](#sysad-interface-n64brew-wiki) - [Initial Program Load - N64brew Wiki](#initial-program-load-n64brew-wiki) - [Parallel Interface - N64brew Wiki](#parallel-interface-n64brew-wiki) - [File:VR4300-Users-Manual.pdf - N64brew Wiki](#file-vr4300-users-manual-pdf-n64brew-wiki) - [File:Cncrntug.pdf - N64brew Wiki](#file-cncrntug-pdf-n64brew-wiki) - [Flash - N64brew Wiki](#flash-n64brew-wiki) - [Sharp SM5 Microcontroller - N64brew Wiki](#sharp-sm5-microcontroller-n64brew-wiki) - [CIC-NUS - N64brew Wiki](#cic-nus-n64brew-wiki) - [Reality Display Processor/Commands - N64brew Wiki](#reality-display-processor-commands-n64brew-wiki) - [Serial Interface - N64brew Wiki](#serial-interface-n64brew-wiki) - [Reality Signal Processor - N64brew Wiki](#reality-signal-processor-n64brew-wiki) - [Parallel Interface - N64brew Wiki](#parallel-interface-n64brew-wiki) - [Clock Timing - N64brew Wiki](#clock-timing-n64brew-wiki) - [ROM Header - N64brew Wiki](#rom-header-n64brew-wiki) - [Initial Program Load - N64brew Wiki](#initial-program-load-n64brew-wiki) - [N64brew Game Jam 2024 - N64brew Wiki](#n64brew-game-jam-2024-n64brew-wiki) - [Controller Pak/Filesystem - N64brew Wiki](#controller-pak-filesystem-n64brew-wiki) - [64DD/Commands - N64brew Wiki](#64dd-commands-n64brew-wiki) - [N64brew Game Jam 2025 - N64brew Wiki](#n64brew-game-jam-2025-n64brew-wiki) - [64DD/Interface - N64brew Wiki](#64dd-interface-n64brew-wiki) - [User:Bigbass - N64brew Wiki](#user-bigbass-n64brew-wiki) - [SGI Audio Tools - N64brew Wiki](#sgi-audio-tools-n64brew-wiki) - [Category:Software Development Kits - N64brew Wiki](#category-software-development-kits-n64brew-wiki) - [Libultra/Memory Allocation - N64brew Wiki](#libultra-memory-allocation-n64brew-wiki) - [N64brew Game Jam 2023 - N64brew Wiki](#n64brew-game-jam-2023-n64brew-wiki) - [Nintendo 64 - N64brew Wiki](#nintendo-64-n64brew-wiki) - [Secure Kernel Calls - N64brew Wiki](#secure-kernel-calls-n64brew-wiki) - [Konami Dance Pad - N64brew Wiki](#konami-dance-pad-n64brew-wiki) - [File:LH52256CVN.pdf - N64brew Wiki](#file-lh52256cvn-pdf-n64brew-wiki) - [Libultra/Data Compression - N64brew Wiki](#libultra-data-compression-n64brew-wiki) - [Libultra/Splitting Assets from Code - N64brew Wiki](#libultra-splitting-assets-from-code-n64brew-wiki) - [Keyboard - N64brew Wiki](#keyboard-n64brew-wiki) - [Libultra/Development Troubleshooting - N64brew Wiki](#libultra-development-troubleshooting-n64brew-wiki) - [Libultra/Code segmentation guide - N64brew Wiki](#libultra-code-segmentation-guide-n64brew-wiki) - [ares - N64brew Wiki](#ares-n64brew-wiki) - [Everdrive 64 - N64brew Wiki](#everdrive-64-n64brew-wiki) - [Category:Emulators - N64brew Wiki](#category-emulators-n64brew-wiki) - [N64brew Wiki:About - N64brew Wiki](#n64brew-wiki-about-n64brew-wiki) - [N64brew Wiki:General disclaimer - N64brew Wiki](#n64brew-wiki-general-disclaimer-n64brew-wiki) - [User:Polprzewodnikowy - N64brew Wiki](#user-polprzewodnikowy-n64brew-wiki) - [Checking Integrated Circuit - N64brew Wiki](#checking-integrated-circuit-n64brew-wiki) - [ROM Metadata - N64brew Wiki](#rom-metadata-n64brew-wiki) - [N64brew Wiki](#n64brew-wiki) - [Display List - N64brew Wiki](#display-list-n64brew-wiki) - [BizHawk - N64brew Wiki](#bizhawk-n64brew-wiki) - [emux - N64brew Wiki](#emux-n64brew-wiki) - [CEN64 - N64brew Wiki](#cen64-n64brew-wiki) - [MAME - N64brew Wiki](#mame-n64brew-wiki) - [Gopher64 - N64brew Wiki](#gopher64-n64brew-wiki) - [EverDrive-64 v3 - N64brew Wiki](#everdrive-64-v3-n64brew-wiki) - [Project64 - N64brew Wiki](#project64-n64brew-wiki) - [EverDrive-64 X7 - N64brew Wiki](#everdrive-64-x7-n64brew-wiki) - [Category:Accessories - N64brew Wiki](#category-accessories-n64brew-wiki) - [Checking Integrated Circuit - N64brew Wiki](#checking-integrated-circuit-n64brew-wiki) - [Modem - N64brew Wiki](#modem-n64brew-wiki) - [Category:Flash Carts - N64brew Wiki](#category-flash-carts-n64brew-wiki) - [Capture Cassette - N64brew Wiki](#capture-cassette-n64brew-wiki) - [Cleaning Kit - N64brew Wiki](#cleaning-kit-n64brew-wiki) - [Parts and Accessories - N64brew Wiki](#parts-and-accessories-n64brew-wiki) - [64drive - N64brew Wiki](#64drive-n64brew-wiki) - [64DD - N64brew Wiki](#64dd-n64brew-wiki) - [Nintendo 64 - N64brew Wiki](#nintendo-64-n64brew-wiki) - [NUS-011 - N64brew Wiki](#nus-011-n64brew-wiki) - [Jumper Pak - N64brew Wiki](#jumper-pak-n64brew-wiki) - [Controller - N64brew Wiki](#controller-n64brew-wiki) - [Mouse - N64brew Wiki](#mouse-n64brew-wiki) - [VRU - N64brew Wiki](#vru-n64brew-wiki) - [Expansion Pak - N64brew Wiki](#expansion-pak-n64brew-wiki) - [Capture Cassette - N64brew Wiki](#capture-cassette-n64brew-wiki) - [Modem - N64brew Wiki](#modem-n64brew-wiki) - [Jumper Pak - N64brew Wiki](#jumper-pak-n64brew-wiki) - [Jumper Pak - N64brew Wiki](#jumper-pak-n64brew-wiki) - [Keyboard - N64brew Wiki](#keyboard-n64brew-wiki) - [Rumble Pak - N64brew Wiki](#rumble-pak-n64brew-wiki) - [VRU - N64brew Wiki](#vru-n64brew-wiki) - [Voice Recognition Unit - N64brew Wiki](#voice-recognition-unit-n64brew-wiki) - [VRU - N64brew Wiki](#vru-n64brew-wiki) - [VRU - N64brew Wiki](#vru-n64brew-wiki) - [Transfer Pak - N64brew Wiki](#transfer-pak-n64brew-wiki) - [Game Pak - N64brew Wiki](#game-pak-n64brew-wiki) - [Cleaning Kit - N64brew Wiki](#cleaning-kit-n64brew-wiki) - [Cleaning Kit - N64brew Wiki](#cleaning-kit-n64brew-wiki) - [Cleaning Kit - N64brew Wiki](#cleaning-kit-n64brew-wiki) - [VRU - N64brew Wiki](#vru-n64brew-wiki) - [Controller Pak - N64brew Wiki](#controller-pak-n64brew-wiki) - [Controller - N64brew Wiki](#controller-n64brew-wiki) - [Category:Paks - N64brew Wiki](#category-paks-n64brew-wiki) --- # N64brew Wiki [](https://n64brew.dev/wiki/Main_Page#) Main Page ========= | Welcome to the N64brew Wiki! | | --- | | This wiki is a collaboration among the homebrew community, proving accurate documentation of the Nintendo 64, its peripherals, and related software.

**Everyone is [welcome to contribute](https://n64brew.dev/wiki/Help:Editing "Help:Editing")
!**

Find us on [Discord](https://discord.gg/WqFgNWf)
, and be sure to check out [homebrew, hardware, and more](https://n64brew.dev/wiki/Homebrew_Projects "Homebrew Projects")
from community members.
Also take a look at the [Frequently Asked Questions](https://n64brew.dev/wiki/FAQ "FAQ")
. | **Hardware****Software** | Physical Components | I/O Interfaces | | --- | --- | | * [VR4300 CPU](https://n64brew.dev/wiki/VR4300 "VR4300")
* [FPU - CP1](https://n64brew.dev/wiki/COP1 "COP1")

* [SysAD Interface](https://n64brew.dev/wiki/SysAD_Interface "SysAD Interface")

* [Reality Coprocessor - RCP](https://n64brew.dev/wiki/Reality_Coprocessor "Reality Coprocessor")
* [Reality Signal Processor - RSP](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor")

* [Reality Display Processor - RDP](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor")

* [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM")

Rambus DRAM shared by the console

* [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS")

A 4-bit microcomputer used to communicate with the controllers and EEPROM

* [Audio DAC](https://n64brew.dev/wiki/Audio_DAC "Audio DAC")

* [Video DAC](https://n64brew.dev/wiki/Video_DAC "Video DAC") | * [Memory map](https://n64brew.dev/wiki/Memory_map "Memory map")

* [MI - MIPS Interface](https://n64brew.dev/wiki/MIPS_Interface "MIPS Interface")

* [VI - Video Interface](https://n64brew.dev/wiki/Video_Interface "Video Interface")

* [AI - Audio Interface](https://n64brew.dev/wiki/Audio_Interface "Audio Interface")

* [PI - Parallel Interface](https://n64brew.dev/wiki/Parallel_Interface "Parallel Interface")

* [RI - RDRAM Interface](https://n64brew.dev/wiki/RDRAM_Interface "RDRAM Interface")

* [SI - Serial Interface](https://n64brew.dev/wiki/Serial_Interface "Serial Interface")
* [Joybus Protocol](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol")

Communication protocol between the PIF, game cartridge, and connected controllers | | Controllers | Paks | Addons / Miscellaneous | | --- | --- | --- | | * [Tri-Wing Controller](https://n64brew.dev/wiki/Controller "Controller")

* [Train Controller](https://n64brew.dev/wiki/Train_Controller "Train Controller")

Used exclusively for Densha de Go

* [Mouse](https://n64brew.dev/wiki/Mouse "Mouse")

* [Randnet Keyboard](https://n64brew.dev/wiki/Randnet_Keyboard "Randnet Keyboard")

* [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit")

* [Dance Pad](https://n64brew.dev/wiki/Dance_Pad "Dance Pad")

* [Fishing Rod](https://n64brew.dev/wiki/Fishing_Rod "Fishing Rod") | * [Game Pak](https://n64brew.dev/wiki/Game_Pak "Game Pak")
(Cartridge)
* [Controller Pak](https://n64brew.dev/wiki/Controller_Pak "Controller Pak")
(Memory Pak)
* [Rumble Pak](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak")

* [Transfer Pak](https://n64brew.dev/wiki/Transfer_Pak "Transfer Pak")

* [Expansion Pak](https://n64brew.dev/wiki/Expansion_Pak "Expansion Pak")

* [Jumper Pak](https://n64brew.dev/wiki/Jumper_Pak "Jumper Pak") | * [64DD](https://n64brew.dev/wiki/64DD "64DD")
(64 Disk Drive)
* [Doctor V64](https://n64brew.dev/wiki/Doctor_V64 "Doctor V64")

* [Flashcarts](https://n64brew.dev/wiki/Flashcarts "Flashcarts")

* [Partner-N64](https://n64brew.dev/wiki/Partner-N64 "Partner-N64") | | Programming Tools / SDK's | Game Development | | --- | --- | | * [libdragon](https://n64brew.dev/wiki/Libdragon "Libdragon")

Homebrew SDK, Public Domain, 2D & 3D, Audio and Controller support, OpenGL 1.1

* [libultra](https://n64brew.dev/wiki/Libultra "Libultra")

Nintendo's Official SDK, Partial source available

* [iQue SDK](https://n64brew.dev/wiki/IQue_SDK "IQue SDK")

Development library for the iQue Player

* [pseultra](https://n64brew.dev/wiki/Pseultra "Pseultra")

Abandoned, Homebrew SDK, BSD 3 Clause License, 2D & 3D, Controller support but no audio

* [SGI Audio Tools](https://n64brew.dev/wiki/SGI_Audio_Tools "SGI Audio Tools")

* [SGI Workstations using IRIX](https://n64brew.dev/wiki/N64_IRIX "N64 IRIX") | * [Getting Started](https://n64brew.dev/wiki/Getting_Started "Getting Started")

An introduction for beginners who wish to delve into homebrew development

* [Game Jams](https://n64brew.dev/wiki/Category:Game_Jams "Category:Game Jams")

List of homebrew game development events

* [Building GCC](https://n64brew.dev/wiki/Building_GCC "Building GCC")

Guide to building a GCC cross-compiler for N64 development

* [MIPS Assembly](https://n64brew.dev/wiki/MIPS_Assembly "MIPS Assembly")

Notes about MIPS assembly programming

* [Instruction Cheatsheet](https://n64brew.dev/wiki/MIPS_III_instructions "MIPS III instructions")

A breakdown of CPU and FPU instructions and their opcodes | Retrieved from "[https://n64brew.dev/wiki/Main\_Page?oldid=5909](https://n64brew.dev/wiki/Main_Page?oldid=5909) " --- # COP1 - N64brew Wiki [](https://n64brew.dev/wiki/COP1#) COP1 ==== Contents -------- * [1 Overview](https://n64brew.dev/wiki/COP1#Overview) * [2 Getting data to/from the COP1](https://n64brew.dev/wiki/COP1#Getting_data_to/from_the_COP1) * [3 Supported formats and conversions](https://n64brew.dev/wiki/COP1#Supported_formats_and_conversions) * [4 Rounding modes and inexact results](https://n64brew.dev/wiki/COP1#Rounding_modes_and_inexact_results) * [5 FCSR](https://n64brew.dev/wiki/COP1#FCSR) * [6 Exceptions Overview](https://n64brew.dev/wiki/COP1#Exceptions_Overview) * [7 Floating Point Numbers](https://n64brew.dev/wiki/COP1#Floating_Point_Numbers) * [8 Special Cases](https://n64brew.dev/wiki/COP1#Special_Cases) * [9 Comparisons](https://n64brew.dev/wiki/COP1#Comparisons) * [10 Full Mode vs Half Mode](https://n64brew.dev/wiki/COP1#Full_Mode_vs_Half_Mode) Overview -------- The COP1 is the FPU of the main CPU. It operates on floats (either 32 bit singles or 64 bit doubles). Just like the main CPU, the COP1 has 32 registers which are each 64 bit wide. Unlike the main CPU registers, all registers are equal (there is no zero register). Getting data to/from the COP1 ----------------------------- Numbers can be passed from main registers to FPU registers via MTC1 (32 bit) and DMTC1 (64 bit). For the way back, use MFC1/DMFC1. Alternatively, numbers can be loaded from RAM via LWC1/LDC1 and stored via SWC1/SDC1. Supported formats and conversions --------------------------------- The instructions above simply transfer bits. In order to actually calculate, they need to be interpreted correctly. The COP1 understands four formats: | | | | | --- | --- | --- |Supported formats | Name | Abbreviation | Explanation | | Single | S | 32 bit float | | Double | D | 64 bit float | | Word | W | 32 bit integer | | Long | Long | 64 bit integer | The COP1 can only perform calculations on singles and doubles; word and long are temporary formats merely used for conversion. Example: The following snippet puts 6 into V0, which is then moved to the COP1 into F0. It then converts that number to a double and puts it into F2. At the end, F2 will have the value 6.0: `ORI V0, R0, 6 MTC1 V0, F0 CVT.D.W F2, F0 // read: convert to Double from Word` The COP1 supports almost all conversions: | | | | | | | --- | --- | --- | --- | --- |Supported conversions | | From Single | From Double | From Word | From Long | | To Single | N/A | CVT.S.D | CVT.S.W | CVT.S.L | | To Double | CVT.D.S | N/A | CVT.D.W | CVT.D.L | | To Word | CVT.W.S | CVT.W.D | N/A | (doesn't exist) | | To Long | CVT.L.S | CVT.L.D | (doesn't exist) | N/A | Rounding modes and inexact results ---------------------------------- Most of conversions mentioned above, but also most regular instructions can be lossy. When that happens, the COP1 has to perform some sort of rounding to fit the result in the destination. It provides four modes: * ROUND: Round towards nearest number (e.g. 4.4 => 4 and 4.6 => 5), ties are broken by rounding to the nearest even number (e.g. 4.5 => 4 while 5.5 => 6). * TRUNC: Round towards zero (e.g. 4.9 => 4 and -4.9 => 4). * CEIL: Round towards larger number (e.g. 4.1 => 5 and -4.1 => -4). * FLOOR: Round towards smaller number (e.g. 4.9 => 4 and -4.9 => -5). The COP1 has a configurable rounding mode in FCSR (see below), which is applied for most instructions where it's applicable. For the specific case of float->int conversions, it provides specialized instructions that overwrite the global rounding mode: ROUND.x.y, TRUNC.x.y, CEIL.x.y, FLOOR.x.y (where x is either W or L and y is either S or D; all 16 combinations are supported). When rounding happens, inexact is signaled (see exceptions below). FCSR ---- In addition to the data registers, the COP1 also provides the Floating Point Status Register, is read via CFC1 and written through CTC1 (using index 31). It provides the following bits: | | | | --- | --- |FCSR | Bits | Description | | 0 to 1 | RoundingMode: Nearest (ROUND) = 0, Zero (TRUNC) = 1, PositiveInfinity (CEIL) = 2, NegativeInfinity (FLOOR) = 3 | | 2 | Flag: Inexact Operation | | 3 | Flag: Underflow | | 4 | Flag: Overflow | | 5 | Flag: Division By Zero | | 6 | Flag: Invalid Operation | | 7 | Enable: Inexact Operation | | 8 | Enable: Underflow | | 9 | Enable: Overflow | | 10 | Enable: Division By Zero | | 11 | Enable: Invalid Operation | | 12 | Cause: Inexact Operation | | 13 | Cause: Underflow | | 14 | Cause: Overflow | | 15 | Cause: Division By Zero | | 16 | Cause: Invalid Operation | | 17 | Cause: Unimplemented Operation | | 23 | Condition | | 24 | Flush Denorm To Zero | Exceptions Overview ------------------- The COP1 supports 6 exceptions: * Inexact: The destination can't hold the full result, so some data loss occurred and rounding was performed. * Underflow: The resulting number was so small it was rounded to 0. This is always in combination with inexact. (The COP1 has a quirk here: Unlike other CPUs (e.g. x64 or arm64), the rounding modes FLOOR/CEIL are taken literally even on underflow; if the result is smaller than the smallest possible float, it might not be rounded to 0 but to the minimum regular float). * Overflow: The resulting number was so large it couldn't be represented as a regular number and was instead "rounded up to infinity" (which is a special floating point value). This is always in combination with inexact. * Division By Zero: This just happens for DIV.S and DIV.D when the divisor is 0. * Invalid Operation: This happens in a bunch of special cases (see "Special Cases" below). * Unimplemented Operation: This happens in a bunch of special cases (see "Special Cases" below). Instructions that can fire exceptions (e.g. ADD.S, CVT.S.W) always clear all Cause bits that aren't being signaled in this specific instructions. For example, CVT.W.S from 5.5 to an int would affect the bits in the following way: * Clear all Cause bits * Perform operation * Set "Cause: Inexact" * If "Enable: Inexact" is true, fire exception. Otherwise, set "Flag: Inexact" and put result value into destination register. This means that Cause be looked at to see the result of the directly preceding instruction. Flags however are cumulative: They are true if any instruction since the last clear signaled that exception, assuming the exception was disabled. Unimplemented Operation is special as it can't be disabled - if it happens, it will always fire. Floating Point Numbers ---------------------- At this point, it makes sense to take a quick look at what floats actually are. The following is the bit representation of a single (doubles work exactly the same, but have more bits in the exponent and the mantissa): | | | | | --- | --- | --- |Single | 31 | 30 - 23 | 22 - 0 | | Sign (1 bit) | Exponent (8 bits) | Mantissa (23 bits) | If the sign bit is 0, the number is positive. If it is 1, the number is negative (because of this, a floating point number is easily negated - just XOR with 0x80000000). There are some special cases for the exponent and mantissa: | | | | | | --- | --- | --- | --- |Special Numbers | Sign bit | Exponent | Mantissa | Description | | 0 | 0 | 0 | Regular zero | | 1 | 0 | 0 | "Negative zero", which is considered equal to regular zero | | any | 0 | != 0 | Denormal/subnormal | | 0 | 0xFF | 0 | Positive Infinity | | 1 | 0xFF | 0 | Negative Infinity | | any | 0xFF | != 0 with highest bit 0 | sNAN (signaling Not-A-Number) | | any | 0xFF | != 0 with highest bit 1 | qNAN (quiet Not-A-Number) | | 0 | 0 bool { f & 0x7FFF_FFFF == 0 } fn is_subnormal(f: u32) -> bool { ((f & 0x7F80_0000) == 0) && ((f & 0x7F_FFFF) != 0) } fn is_nan(f: u32) -> bool { ((f & 0x7F80_0000) == 0x7F80_0000) && ((f & 0x7F_FFFF) != 0) } fn is_quiet_nan(f: u32) -> bool { (f & 0x7FC0_0000) == 0x7FC0_0000) } fn absolute_value(f: u32) -> bool { f & 0x7FFF_FFFF } fn negate(f: u32) -> bool { f ^ 0x8000_0000 }` Special Cases ------------- The COP1 will never on its own produce either a subnormal or a qNAN. The following rules applies to calculating instructions (ADD.X, SUB.X, DIV.X, MUL.X, SQRT.X, ABS, NEG.X, CVT.Y.X, ROUND.Y.X, TRUNC.Y.X, FLOOR.Y.X, CEIL.Y.X, where X is S/D): * If an input is sNAN, fire Unimplemented Operation * If an input is subnormal, fire Unimplemented Operation * If an input is qNAN, signal Invalid Operation and set result to sNAN (specifically 0x7FBFFFFF (for floats) or 0x7FF7FFFFFFFFFFFF (for doubles)) (exceptions: CVT.W.x and CVT.L.x also fire Unimplemented Operation as NAN can not be represented as an integer) * Perform the operation * If the operation underflowed, the following happens: * If "Flush Denorm To Zero" is 1 AND "Enable: Underflow" is 0 AND "Enable: Inexact" is 0, the result is flushed and Underflow and Inexact are signaled. In most cases this means that it is set to 0 or "negative 0". (Two exception: If the rounding mode is "Ceil" and the result is positive, it will be set to the smallest positive value instead; similarly, "Floor" will set a negative result to the negative value that is closest to 0). * Otherwise, fire Unimplemented Operations * If the operation is invalid (for example: Infinity-Infinity, 0.0 / 0.0 or SQRT(-2)), set result to sNAN (specifically 0x7FBFFFFF (for floats) or 0x7FF7FFFFFFFFFFFF (for doubles)) and signal Invalid Operation is signaled. * If the operation was a division by zero, signal division by zero * If the operation overflowed, signal Overflow and Inexact and set result to Infinite or -Infinity * If the operation was inexact, signal inexact MOV.S and MOV.D are special: They just copy the bits and never fire or signal exceptions. They also don't clear the Cause bits. Comparisons ----------- The COP1 in total has 16 single compare instructions with some pretty confusing names (and another 16 for doubles). The 16 instructions are all possible combinations of the following 4 bits: * Unordered (Bit 0): Comparison is considered true if one or both of the operands is NAN * Equal (Bit 1): Comparison is considered true if both operands are equal (note that 0 is equal to -0, but NAN is always different from another NAN) * Smaller (Bit 2): Comparison is considered true if the first operand is smaller than the second * SignalOnSNAN (Bit 3): If either operand is sNAN, this will signal Invalid Operation. If multiple bits are set, the conditions are ORed together: For example, UEQ is considered true if the two operands are equal or unordered. Note that inputs of qNAN always signal Invalid Operation. Using all bit combinations, this gives the following instructions: | | | | | | | | | --- | --- | --- | --- | --- | --- | --- |Compare encoding | SignalOnSNAN (Bit 3) | Smaller (Bit 2) | Equal (Bit 1) | Unordered (Bit 0) | Name | Result formula | Invalid Operation Condition | | 0 | 0 | 0 | 0 | F | Result = false | IsQNAN(arg1) OR isQNAN(arg2) | | 0 | 0 | 0 | 1 | UN | Result = unordered(arg1, arg2) | IsQNAN(arg1) OR isQNAN(arg2) | | 0 | 0 | 1 | 0 | EQ | Result = arg1 == arg2 | IsQNAN(arg1) OR isQNAN(arg2) | | 0 | 0 | 1 | 1 | UEQ | Result = unordered(arg1, arg2) OR (arg1 == arg2) | IsQNAN(arg1) OR isQNAN(arg2) | | 0 | 1 | 0 | 0 | OLT | Result = arg1 < arg2 | IsQNAN(arg1) OR isQNAN(arg2) | | 0 | 1 | 0 | 1 | ULT | Result = unordered(arg1, arg2) OR (arg1 < arg2) | IsQNAN(arg1) OR isQNAN(arg2) | | 0 | 1 | 1 | 0 | OLE | Result = arg1 <= arg2 | IsQNAN(arg1) OR isQNAN(arg2) | | 0 | 1 | 1 | 1 | ULE | Result = unordered(arg1, arg2) OR (arg1 <= arg2) | IsQNAN(arg1) OR isQNAN(arg2) | | 1 | 0 | 0 | 0 | SF | Result = false | IsNAN(arg1) OR isNAN(arg2) | | 1 | 0 | 0 | 1 | NGLE | Result = unordered(arg1, arg2) | IsNAN(arg1) OR isNAN(arg2) | | 1 | 0 | 1 | 0 | SEQ | Result = arg1 == arg2 | IsNAN(arg1) OR isNAN(arg2) | | 1 | 0 | 1 | 1 | NGL | Result = unordered(arg1, arg2) OR (arg1 == arg2) | IsNAN(arg1) OR isNAN(arg2) | | 1 | 1 | 0 | 0 | LT | Result = arg1 < arg2 | IsNAN(arg1) OR isNAN(arg2) | | 1 | 1 | 0 | 1 | NGE | Result = unordered(arg1, arg2) OR (arg1 < arg2) | IsNAN(arg1) OR isNAN(arg2) | | 1 | 1 | 1 | 0 | LE | Result = arg1 <= arg2 | IsNAN(arg1) OR isNAN(arg2) | | 1 | 1 | 1 | 1 | NGT | Result = unordered(arg1, arg2) OR (arg1 <= arg2) | IsNAN(arg1) OR isNAN(arg2) | Full Mode vs Half Mode ---------------------- The COP1 can run in one of two modes, which is controlled via COP0.Status Bit 26. In "Full Mode", the COP1 has 32 registers that are each 64 bits wide. In "Half Mode", only the 16 even numbered registers are legal to be used; using odd numbered registers is considered undefined behavior. Older software usually ran in "Half Mode". A reason for that could be that context switches (for multithreading) can be performed more cheaply, as only 16 FPU registers need to be stored. When using "Half Mode" it is important to not use any FPU registers with odd indices. For compiled code this has to be configured accordingly ("+nooddspreg" in clang). _The remainder of this section documents undefined behavior. Skip this unless you are an emulator developer who cares about accuracy a little bit too much._ If software decides to use odd indices in "Half Mode", different things happen, depending on the instruction: | | | | | | --- | --- | --- | --- |Illegal register indexing in "Half Mode" (normally undocumented behavior - do not use) | Actual Register Index | MFC1/MTC1/LWC1/LDC1 | fd (32 bit), ft (32 bit) or any 64 bit | fs (32 bit) | | 0 | 1 (high 32 bits) / 0 (low 32 bits) | 0 (low 32 bits) | 0 or 1 | | 1 | unused | 1 (low 32 bits) | unused | | 2 | 3 (high 32 bits) / 2 (low 32 bits) | 2 (low 32 bits) | 2 or 3 | | 3 | unused | 3 (low 32 bits) | unused | | 4 | 5 (high 32 bits) / 4 (low 32 bits) | 4 (low 32 bits) | 4 or 5 | | 5 | unused | 5 (low 32 bits) | unused | Retrieved from "[https://n64brew.dev/wiki/COP1?oldid=5785](https://n64brew.dev/wiki/COP1?oldid=5785) " --- # Help:Editing - N64brew Wiki [](https://n64brew.dev/wiki/Help:Editing#) Help:Editing ============ Since this is a wiki, anyone can edit any unprotected pages. However, for your own benefit and to deter spam bots, you are required to create an account (and verify your email address), before you can edit anything. It's recommended that you learn how to edit the source of a page, however if you'd prefer to use the visual editor, you may use that instead. Remember: You don't have to be a master editor right away! Just getting content onto a page is already quite helpful. Don't be afraid to ask for help, or to ask other editors to improve the formatting of a page. Also don't be discouraged if edits are made to your changes. If you believe something was changed in error, or want more details about the change, please discuss it in the community Discord. Refer to the [Todo](https://n64brew.dev/wiki/Todo "Todo") page for details about what is currently being worked on. Contents -------- * [1 Content Protocols](https://n64brew.dev/wiki/Help:Editing#Content_Protocols) * [1.1 Providing Accurate Information](https://n64brew.dev/wiki/Help:Editing#Providing_Accurate_Information) * [1.2 Warning - Double Check Your Sources](https://n64brew.dev/wiki/Help:Editing#Warning_-_Double_Check_Your_Sources) * [2 Source Editing](https://n64brew.dev/wiki/Help:Editing#Source_Editing) * [3 Editing the Main Page](https://n64brew.dev/wiki/Help:Editing#Editing_the_Main_Page) * [4 Categories](https://n64brew.dev/wiki/Help:Editing#Categories) * [5 Units of Measurement](https://n64brew.dev/wiki/Help:Editing#Units_of_Measurement) * [6 Edit Summaries](https://n64brew.dev/wiki/Help:Editing#Edit_Summaries) * [7 Discord](https://n64brew.dev/wiki/Help:Editing#Discord) Content Protocols ----------------- When adding content or especially when creating new pages, an encyclopedic (topic-based) style is critical. Any particular page should talk about a specific physical item or concept. _Please do not create vague or generic pages like "General Overview" or "The Console"._ Pages for items that are often referred to by an acronym, should instead use the full word/phrase. Then mention in the first paragraph any other forms of the name. [North American Aerospace Defense Command](https://en.wikipedia.org/wiki/North_American_Aerospace_Defense_Command "w:North American Aerospace Defense Command") from Wikipedia is a good example. Although this is almost always called "NORAD" in conversations, the page uses the full name. Redirects can be set up instead, in case someone searches for the page using the acronym. The content on each page should not include any argumentative or opinionated writing (unless it is within a quote). Instead, focus on the facts and information that is supported by evidence (which should then also be supplied). ### Providing Accurate Information It it extremely important that editors provide information and data that is as accurate as possible. If you aren't sure something is true, either don't write it, or mark it as unverified. An example of how this can be done for minor occurrences is [Joybus\_Protocol#0x00\_-\_Info](https://n64brew.dev/wiki/Joybus_Protocol#0x00_-_Info "Joybus Protocol") where the superscript number is used to denote certain entries of the table as unverified. If a major portion of a page is unverified, a warning should be placed at the top of the page (template will be created for this, but text is fine for now). If you are getting information from a particular source (besides yourself), like another webpage or publication, please write in a "References" section at the bottom of the page with links or other details for each source. If however, you know something to be true by your own experimentation or as collaboration with other people, it is preferred that you indicate that as a reference too. ### Warning - Double Check Your Sources A source of information may appear to be accurate, you may even verify parts of the page yourself. But that doesn't necessarily mean that everything on a page is 100% correct. If you can, compare multiple sources to each other. If they mismatch, assume that there could be other issues too. If possible, try to contact the author of the source(s) and verify that everything they wrote is correct and up-to-date. Source Editing -------------- As mentioned, editing the source of a page is recommended. It is ultimately far more powerful than the visual editor will ever be, and it keeps the source more organized. HTML tags are allowed, but wiki markup should be used when possible. If you run into a situation where a Template would be helpful, please check if something already exists, before making one yourself. HTML tags in Templates is perfectly okay (and quite often necessary). You can see a list of all templates by going [here](https://n64brew.dev/wiki/Special:AllPages "Special:AllPages") and selecting "Templates" from the Namespace list, then click "Go". To learn about source editing, refer to [MediaWiki's help page](https://www.mediawiki.org/wiki/Help:Contents "mw:Help:Contents") . Editing the Main Page --------------------- Editing this page is locked to only admins. It's a high traffic page, seen by all newcomers. If you want something changed, you'll need to request it from an admin in the #wiki-project channel on the Discord server. Categories ---------- These are useful for organizing related pages into a list automatically. To add a page to a category, go to the source page, and at the bottom add **\[\[Category:categoryName\]\]**, where "categoryName" is the name of the category you wish to add the page to. To link to a category, insert a colon just after the first double brackets **\[\[:Category:categoryName\]\]**. Units of Measurement -------------------- In particular, byte units for higher powers should use the older standard of KB, MB, GB, etc. Such that, 1 KB = 1,024 bytes, and so on. There are few to no situations where KB would be referring to 1,000 in this context. Additionally, all official N64 documentation, including the VR4300 manual, uses these units (sometimes even shortening it to a single letter: K, M, G). However, to remove any ambiguity, the first time a unit like this is used in any give page, it should be followed by the full number of bytes inside parentheses: 64 KB (65,536 bytes). Listing the number of bytes using hexadecimal is also acceptable (e.g. 0x10000 bytes). When using powers of bits, preferably use Kbits, Mbits, and Gbits. Frequencies are different however, as 1 KHz = 1,000 Hz. But keep in mind that frequencies are rarely that precise and can drift over time, or vary based on environmental factors like temperature. Edit Summaries -------------- Please try to include some kind of short summary of your change. If you're not sure what you should write, take a look at past editors using the History tab of any page. These comments are useful for other editors to quickly get an idea what your edits have changed without looking through entire diffs. Discord ------- While using the discussion/Talk pages can be useful, there is also a channel on the N64 homebrew Discord server specifically for discussing this wiki's development. Feel free to join to ask for help or guidance, or to report any problems with the wiki. [https://discord.gg/WqFgNWf](https://discord.gg/WqFgNWf) Retrieved from "[https://n64brew.dev/wiki/Help:Editing?oldid=4277](https://n64brew.dev/wiki/Help:Editing?oldid=4277) " --- # SysAD Interface - N64brew Wiki [](https://n64brew.dev/wiki/SysAD_Interface#) SysAD Interface =============== The MIPS interface is a bidirectional interface on the N64 that allows 32bits of address and data transfers with a 5bit control bus and 3 controlling signals (There are another 3 controls that are used for multi-cpu setups but are not used in the N64). The bus almost works like a packet system where the control bus select what is being read or written and how much data as well from the CPU or [RCP](https://n64brew.dev/wiki/RCP "RCP") . **Masterclock**: This is a clock feed from the RCP to the CPU. All signals are done on the Rising clock edge of the master clock. The internal CPU speed is multiplied using this clock to 93.75mhz. But all access between the RCP and CPU is at the masterclock rate (62.5mhz) **EoK** : This is a inverted ready signal from the RCP. When LOW, this signals that the RCP is ready to accept a command from the CPU. When the EoK is high the CPU will be placed in a wait state before the next set of data is sent from the CPU. This is mostly used when doing writes from the CPU so the RCP can be ready to send the next command. **Evalid**: This is the inverted signal from the RCP to say that there is valid data and commands on the SYSAD and SYSCMD buses. **Pvalid**: This is an inverted signal that says that the CPU is sending a valid SYSAD and SYSCMD signal on the buses. This can be held low (valid data) if it is waiting to send data to the RCP if the EoK is still high. **SYSAD \[31:0\]**: This is a Bidirectional Address and Data bus that is 32 bits. This can send single or burst data between the CPU and RCP when requested (Bursting is used for cache memory or double word read/writes accesses) **SYSCMD\[4:0\]**: This is a Bidirectional bus that tells the CPU or RCP is it a read/write, how much data to be sent or the end of data. Contents -------- * [1 SYSCMD Cheat sheet](https://n64brew.dev/wiki/SysAD_Interface#SYSCMD_Cheat_sheet) * [2 Instruction and Data: non-cached reads](https://n64brew.dev/wiki/SysAD_Interface#Instruction_and_Data:_non-cached_reads) * [3 Data: Word non-cached write](https://n64brew.dev/wiki/SysAD_Interface#Data:_Word_non-cached_write) * [4 Data: Double word non-cached write 64 bits](https://n64brew.dev/wiki/SysAD_Interface#Data:_Double_word_non-cached_write_64_bits) * [5 Data: Cached write 128 bits](https://n64brew.dev/wiki/SysAD_Interface#Data:_Cached_write_128_bits) * [6 Data: Cached read 128 bits](https://n64brew.dev/wiki/SysAD_Interface#Data:_Cached_read_128_bits) * [7 Instruction: Cached read 256 bits](https://n64brew.dev/wiki/SysAD_Interface#Instruction:_Cached_read_256_bits) * [8 CPU Throughput tips, tricks and ideas](https://n64brew.dev/wiki/SysAD_Interface#CPU_Throughput_tips,_tricks_and_ideas) SYSCMD Cheat sheet ================== | | | | | | | | --- | --- | --- | --- | --- | --- | | SYS Cmd Type | Bit 4 - Command or Data | Bit 3 | Bit 2 | Bit 1 - Size | Bit 0 - Size | | Command – Data On bus | 1 - Command | 0 - data flag | 0 | 0 | 0 | | Command – End Data | 1 - Command | 1 - Last data flag | 0 | 0 | 0 | | Command – Response data | 1 - Command | 0 - data flag | 1 – Response data | 0 | 0 | | Read – 32 bits | 0 – Data req | 0 - read | 0- Single read | 1 | 1 – 32 bits | | Read – 64 Bits | 0 – Data req | 0 - read | 1 – Block read | 0 | 0 – 64 bits | | Read – 128 Bits | 0 – Data req | 0 - read | 1 – Block read | 0 | 1 – 128 bits | | Read – 256 Bits | 0 – Data req | 0 - read | 1 – Block read | 1 | 0 – 256 bits | | Write – 8 bits | 0 – Data req | 1- write | 0- Single write | 0 | 0 – 8 bits | | Write – 16 bits | 0 – Data req | 1- write | 0- Single write | 0 | 1 – 16bits | | Write – 24 bits | 0 – Data req | 1- write | 0- Single write | 1 | 0 – 24 bits | | Write – 32 bits | 0 – Data req | 1- write | 0- Single write | 1 | 1 – 32 bits | | Write – 64 bits | 0 – Data req | 1- write | 1- block write | 0 | 0 – 64 bits | | Write – 128 bits | 0 – Data req | 1- write | 1- block write | 0 | 1 – 128 bits | | Write – 256 bits – this is only used in testing of icache | 0 – Data req | 1- write | 1- block write | 1 | 0 – 256 bits | Instruction and Data: non-cached reads ====================================== Both instruction reads and data word non cached reads run the same way.  [](https://n64brew.dev/wiki/File:CPU_Read_32bit.png) 32 bit read from the CPU 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to Read – 32 bits (All reads that are 8/16/24 and 32 are done as a 32-bit reads and the CPU does the shifting internally) * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next clock cycle the Pvalid goes high and the CPU keeps the SYSAD/CMD on the buses waiting for the Evalid to do low 4\.      Once the Evalid goes low, the CPU goes into High-Z mode and listens to the two buses. At that same time the RCP does the following: * The SYSAD bus from the RCP outputs the data that the CPU has requested * The SYSCMD outputs the End Data command 5\.      On the next master clock cycle the RCP puts the Evalid back high and puts both buses back in high-Z. Then the CPU does its next command Data: Word non-cached write =========================== All 8, 16, 24 and 32 bit writes are done as a single ’32 bit’ write structure but the last 2 bits of the SYSCMD bus say what type of write is to happen.  [](https://n64brew.dev/wiki/File:CPU_Write_32bit.png) 32 bit write from the CPU 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to Write – 8/16/24/32 bits * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next clock cycle the CPU will output the data to write and EoK from the RCP goes high to say it is accepting the data. Then the following happens: * The SYSAD puts the data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 4\.      On the next master clock cycle the CPU will place the Pvalid high to complete the write and RCP holds the EoK High to say that it is processing the write and will stay high until the write is completed internally. During this time the CPU can place then next command address on the busses and will stay in a hold state until the Eok Goes back low, Then the next master clock (So the EoK will be low for a full cycle) the RCP will then process that command. Now for 8/16/and 24 bits the data out will be address aligned on the data bus and repeated. This is so no processing is needed in the RCP on the alignment of the data. For 40/48/56 writes this is processed as two separate 32 write commands where the LSB is written first then the MSB is written next. Data: Double word non-cached write 64 bits ========================================== 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to Write – 64 bits * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next clock cycle the CPU will output the data to write and EoK from the RCP goes high to say it is accepting the data. Then the following happens: * The SYSAD puts the LSB data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 4\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the MSB data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 5\.      On the next master clock cycle the CPU will place the Pvalid high to complete the write and RCP holds the EoK High to say that it is processing the write and will stay high until the write is completed internally. During this time the CPU can place then next command address on the busses and will stay in a hold state until the Eok Goes back low, Then the next master clock (So the EoK will be low for a full cycle) the RCP will then process that command. Data: Cached write 128 bits =========================== The address for cache writes will always be 128 bit aligned for the address (So that last 4 bits will be 0000) This is because the D-cache ram in the CPU is 128 bits and when a dirty write happens the full 128 entry is written back to ram (cache dump opcodes run the same way too but only write back entrys that are marked as dirty)  [](https://n64brew.dev/wiki/File:Cpu_Write_dcache_128bit.png) 128bit D-cache write from the CPU 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to Write – 64 bits * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next clock cycle the CPU will output the data to write and EoK from the RCP goes high to say it is accepting the data. Then the following happens: * The SYSAD puts the first 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 4\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the second 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 5\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the third 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 6\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the fourth 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 7\.      On the next master clock cycle the CPU will place the Pvalid high to complete the write and RCP holds the EoK High to say that it is processing the write and will stay high until the write is completed internally. During this time the CPU can place then next command address on the busses and will stay in a hold state until the Eok Goes back low, Then the next master clock (So the EoK will be low for a full cycle) the RCP will then process that command. Data: Cached read 128 bits ========================== The address for cache reads will always be 64 bit aligned for the address (So that last 3 bits will be 000) Now why 64 bit address aligned? Well to keep speed up in the 4300i CPU the D-cache is 128 bits long but the CPU can read up to 64 bits for data at most, so we want to put the 64bit data as quickly as possible in the 128bit word aligned entry. Two things can happen if bit 4 of the data address requested is high or low. If low, the data to the CPU is sent normally from LSB to MSB words and the CPU will work on its merry way as it has received its needed 64 bits first. This is what we call a sequential order transfer. But if the 4th bit is high, the RCP will do something that will shock you. The CPU will send the 64 word aligned address (address 8000\_0018 data is sent to the RCP), First the 64 bits from that address requested are sent to the CPU. After that what data is sent next? The data from the address above it (8000\_0020) or below (8000\_0010)?  [](https://n64brew.dev/wiki/File:Dcache_read_128bit.png) CPU Read for Dcache 128 bits We have to look at how the D-cache is set up in the CPU. Each entry is 128 word aligned so with this in mind the previous 64bits of data are placed in the x8 (MSB) part of the d-cache ram so what happens to the low 64bits? This is where the RCP will then send the data from the previous address to the CPU. (This would be address 8000\_0010) This is what we call a subblock ordering (please look at page 339 on the VR4300 64-bit UM PDF on this). For this steps 4 and 5 are swapped with steps 6 and 7. 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to read – 128 bits * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next cycle the CPU will put the Pvalid high and wait for the RCP to respond. During this time the SYS buses will keep the address and command on the buses 4\.      Once the RCP has the data, the Evalid goes low, the CPU goes into High-Z mode and listens to the two buses. At that same time the RCP does the following: * The SYSAD has the first 32-bit data to write. * The SYSCMD outputs the End Data command * Evalid stays low to state that there is valid data on the two buses. 5\.      On the next clock cycle the RCP will output the data to write and the following happens: * The SYSAD has the second 32-bit data to write. * The SYSCMD outputs the End Data command * Evalid stays low to state that there is valid data on the two buses. 6\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the third 32-bit data to write. * The SYSCMD outputs the End Data command * Evalid stays low to state that there is valid data on the two buses. 7\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the fourth 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 8\.      On the next master clock cycle the RCP will place the Evalid high to complete the transfer. Instruction: Cached read 256 bits ================================= The address for I-cache reads will always be 256 bit aligned for the address (So that last 5 bits of the address will always be 00000) This is because the I-Cache memory is setup as a 256 aligned entry and there is no smarts in the 4300i CPU to know if all of the cache entry is full. Thus, the CPU must have a full entry in the icache before it can load instructions. The 256 reads run the same as the D-cache reads (and are sequential order). But the command sent to the RCP is the read – 256 it command. And 8x 32 bit data accesses are sent over the SYSAD bus  [](https://n64brew.dev/wiki/File:Read_icache_256bit.png) Read from the CPU that is 256 Bits for the I-cache CPU Throughput tips, tricks and ideas ===================================== These ideas are subjective and are only from a hardware over look perspective. * Cache, cache and cache all ram accesses. Ram is hard to get from the RCP and the CPU can be waiting for a long time before data is passed on from the RDRAM. * All RCP register access should not be done via cache memory locations in CPU. As the write backs will not work correctly and will cause the RCP to crash. Also keep them at 32 bit read and writes as they only work like this and there is no waste in bandwidth over the 32bit bus. * The SYSAD bus is advertised as a 250mbyte/second interface. But due to the bidirectional and wait states, I would believe max throughput would be more to the 200Mbyte/second or less mark. * When caching memory try and keep to a 16kbyte instruction blocks (thus keeping your cache hits higher) and data in 8Kbyte blocks. Cache opcodes can also help in the fulling and dumping of memory locations. These program locations can be “pre-cached/fulled” by using the CP0 co-processer and then activating it using the cache opcode. These opcodes are very helpful if used correctly and some keep the CPU running. * And something I would love to see done. Use the DMEM and IMEM in the RSP core like a fast ram access (Just remember these are 32 bit writes only) so no caching. But you can DMA to from ram to them and then used this as a cache ‘bootcode’ for the cache opcode process. Then run in cache memory. This ram is very fast and has about a 4-5 clock cycle wait time, where the RDRAM has about 10-20+ clock wait time for a data process to happen. Retrieved from "[https://n64brew.dev/wiki/SysAD\_Interface?oldid=5037](https://n64brew.dev/wiki/SysAD_Interface?oldid=5037) " --- # RDRAM - N64brew Wiki [](https://n64brew.dev/wiki/RDRAM#) RDRAM ===== Rambus DRAM (or **RDRAM**) is a type of synchronous dynamic random-access memory (SDRAM) designed by [Rambus](https://en.wikipedia.org/wiki/Rambus "w:Rambus") . The N64 motherboard came with either one or two chips, totaling 4 MiB (4,194,304 bytes) of general purpose storage which can be accessed by the CPU. The optional [Expansion Pak](https://n64brew.dev/wiki/Expansion_Pak "Expansion Pak") could increase this by an additional 4 MB and is required for some games to run. Each byte of RDRAM actually has an extra bit, which can only be used by the RDP and VI core. This 9th bit is used to store things like anti-aliasing coverage in the color buffer. On systems other than the N64, the 9th bit would likely be used for parity checks. Contents -------- * [1 RDRAM system overview](https://n64brew.dev/wiki/RDRAM#RDRAM_system_overview) * [2 Interface Pinouts](https://n64brew.dev/wiki/RDRAM#Interface_Pinouts) * [3 RDRAM registers](https://n64brew.dev/wiki/RDRAM#RDRAM_registers) * [3.1 0x00 - DeviceType](https://n64brew.dev/wiki/RDRAM#0x00_-_DeviceType) * [3.2 0x01 - DeviceId](https://n64brew.dev/wiki/RDRAM#0x01_-_DeviceId) * [3.3 0x02 - Delay](https://n64brew.dev/wiki/RDRAM#0x02_-_Delay) * [3.3.1 **Reset Complications**](https://n64brew.dev/wiki/RDRAM#Reset_Complications) * [3.4 0x03 - Mode](https://n64brew.dev/wiki/RDRAM#0x03_-_Mode) * [3.5 0x04 - RefInterval](https://n64brew.dev/wiki/RDRAM#0x04_-_RefInterval) * [3.6 0x05 - RefRow](https://n64brew.dev/wiki/RDRAM#0x05_-_RefRow) * [3.7 0x06 - RasInterval](https://n64brew.dev/wiki/RDRAM#0x06_-_RasInterval) * [3.8 0x07 - MinInterval](https://n64brew.dev/wiki/RDRAM#0x07_-_MinInterval) * [3.9 0x08 - AddressSelect](https://n64brew.dev/wiki/RDRAM#0x08_-_AddressSelect) * [3.10 0x09 - DeviceManufacturer](https://n64brew.dev/wiki/RDRAM#0x09_-_DeviceManufacturer) * [4 RDRAM addressing](https://n64brew.dev/wiki/RDRAM#RDRAM_addressing) * [5 Current Control calibration](https://n64brew.dev/wiki/RDRAM#Current_Control_calibration) * [6 Known RDRAM Console Chip Configurations](https://n64brew.dev/wiki/RDRAM#Known_RDRAM_Console_Chip_Configurations) * [7 Known RDRAM Expansion Pak Configurations](https://n64brew.dev/wiki/RDRAM#Known_RDRAM_Expansion_Pak_Configurations) * [8 Initialization Sequence](https://n64brew.dev/wiki/RDRAM#Initialization_Sequence) * [9 Expansion Pak Detection](https://n64brew.dev/wiki/RDRAM#Expansion_Pak_Detection) * [10 Drawbacks and Limitations](https://n64brew.dev/wiki/RDRAM#Drawbacks_and_Limitations) * [10.1 Opinion](https://n64brew.dev/wiki/RDRAM#Opinion) * [11 Datasheets](https://n64brew.dev/wiki/RDRAM#Datasheets) * [12 iQue Player](https://n64brew.dev/wiki/RDRAM#iQue_Player) RDRAM system overview ===================== A typical RDRAM system is composed of 3 main elements : * a controller, which act as the channel master. This role is fulfilled by the [RI](https://n64brew.dev/wiki/RDRAM_Interface "RDRAM Interface") with the help of the RAC (Rambus ASIC Cell). * the channel, which is a synchronous bus connecting the RDRAM devices together. * RDRAM modules, each containing memory banks, and some registers. RDRAM devices are daisy-chained via serial signals SIn/SOut.  [](https://n64brew.dev/wiki/File:RI_RDRAM_png.png) RI/RDRAM system overview The N64 system implements the "Base RDRAM" protocol, which is the earliest version of RDRAM protocol. Historical note, latter version of the protocol are "Concurrent RDRAM" and "Direct RDRAM". All known retail units use 2 MiB RDRAM modules, both within the console and within the expansion pak. There are no proof that 1 MiB RDRAM modules were ever produced and sold (preliminary datasheets can be found online, but there is no proof they ever went to production). Moreover, [a bug has been found](https://github.com/decompals/N64-IPL/blob/697693854124e1a62e8550d0f21598a7ee4e5314/src/ipl3.s#L339-L344) in the official Nintendo IPL3 code (in charge of RDRAM initialization) that would prevent 1 MiB modules from working. The code seems designed to handle those, but then it was probably never actually tested and in fact it would not work. This is another proof that 1 MiB modules were never used on Nintendo official parts, because if they were, the existing games would not boot on them because of this bug. Interface Pinouts ================= | | | | | | | --- | --- | --- | --- | --- | | Pin | Signal | RSL | I/O | Description | | 1 | VDD | | \- | +3.3V power supply. | | 2 | GND | | \- | Circuit ground. | | 3 | DQ8 | Y | I/O | Signal line (bit 8) for REQ, DIN, and DOUT packets. | | 4 | GND | | \- | Circuit ground. | | 5 | DQ7 | Y | I/O | Signal line (bit 7) for REQ, DIN, and DOUT packets. | | 6 | NC\* | | \- | Not connected. | | 7 | ADDRESS | Y | I | Signal line for COL packets with column addresses. | | 8 | VDD | | \- | +3.3V power supply. | | 9 | DQ6 | Y | I/O | Signal line (bit 6) for REQ, DIN, and DOUT packets. | | 10 | GND | | \- | Circuit ground. | | 11 | DQ5 | Y | I/O | Signal line (bit 5) for REQ, DIN, and DOUT packets. | | 12 | VDDA | | \- | Separate analog power supply for clock generation in the RDRAM. | | 13 | RXCLK | Y | I | Receive clock. All input packets are aligned to this clock. | | 14 | GNDA | | \- | Separate analog ground for clock generation in the RDRAM. | | 15 | TXCLK | Y | I | Transmit clock. DOUT packets are aligned with this clock. | | 16 | VDD | | \- | +3.3V power supply. | | 17 | DQ4 | Y | I/O | Signal line (bit 4) for REQ, DIN, and DOUT packets. | | 18 | GND | | \- | Circuit ground. | | 19 | COMMAND | Y | I | Signal line for REQ, RSTRB, RTERM, WSTRB, WTERM, RESET, and CKE packets. | | 20 | SIN | | I | Initialization daisy chain input. CMOS levels. See section on initialization for more details. | | 21 | VREF | | I | Logic threshold reference voltage for RSL signals. | | 22 | SOUT | | O | Initialization daisy chain output. CMOS levels. See section on initialization for more details. | | 23 | DQ3 | Y | I/O | Signal line (bit 3) for REQ, DIN, and DOUT packets. | | 24 | GND | | \- | Circuit ground. | | 25 | DQ2 | Y | I/O | Signal line (bit 2) for REQ, DIN, and DOUT packets. | | 26 | NC | | \- | Not connected. | | 27 | DQ1 | Y | I/O | Signal line (bit 1) for REQ, DIN, and DOUT packets. | | 28 | GND | | \- | Circuit ground. | | 29 | DQ0 | Y | I/O | Signal line (bit 0) for REQ, DIN, and DOUT packets. | | 30 | NC | | \- | Not connected. | | 31 | GND | | \- | Circuit ground. | | 32 | VDD | | \- | +3.3V power supply. | RSL stands for Rambus Signaling Levels, a low-voltage-swing, active-low signaling technology. Source: Rambus concurrent RDRAM datasheet [\[1\]](https://web.archive.org/web/19970101123304if_/http://www.rambus.com:80/docs/1664mcds.pdf) RDRAM registers =============== | | | | | | --- | --- | --- | --- |Register summary | Number | CPU Addr\[9:0\] | Name | Description | | 0 | 0x000 | DeviceType | Read-only register which describes RDRAM configuration | | 1 | 0x004 | DeviceId | Specifies base address of RDRAM | | 2 | 0x008 | Delay | Specifies CAS timing parameters | | 3 | 0x00C | Mode | Control operating mode and IOL output current | | 4 | 0x010 | RefInterval | Specifies refresh interval for devices that require refresh | | 5 | 0x014 | RefRow | Next row and bank to be refreshed | | 6 | 0x018 | RasInterval | Specifies RAS access interval | | 7 | 0x01C | MinInterval | Provides minimum delay information and some special control | | 8 | 0x020 | AddressSelect | Specifies Adr field subufield swapping to maximize hit rate | | 9 | 0x024 | DeviceManufacturer | Read-only register providing manufacturer and device information | | 128 | 0x200 | Row | Address of currently sensed row in each bank | RDRAM registers mirror every 0x40 bytes (16 words). Register numbers 10 to 15 all produce 0 when read. Above 0x200 (register number 128) the Device Type mirrors are replaced with the Row register. See [RI page](https://n64brew.dev/wiki/RDRAM_Interface "RDRAM Interface") for more details about how RDRAM registers are mapped into CPU address space. **Reset and initialization:** * After RDRAM device reset, **Delay** needs to be set correctly before anything will work, see **[Reset Complications](https://n64brew.dev/wiki/RDRAM#Reset_Complications) ** * After reset all devices will respond to Broadcast writes. Only the closet device in the Sin/Sout chain will respond to a non-broadcast write. * Before the device will respond to Register reads, each device needs to be assigned a **DeviceId** and enabled by setting **Mode's** DeviceEnable bit. * After each device has been enabled, the next device in the Sin/Sout chain will respond now respond to non-broadcast writes. * Register reads will not return the correct value until the after the device's current control calibration as finished. **Endianness:** Rambus devices are little endian. Doesn't matter for regular memory, but that does mean registers are all byte swapped. The register descriptions below have been remapped to show big endian bit offsets. **Address alignment issues:** The Rambus register commands (Wreg, WregB and Rreg) are defined to be "Quadbyte" (32bit) transfers, and so expect the data to be in the first 4 bytes.But RI only supports transfers with 1 to 16 "Octbyte" (64 bits). This works fine for accessing registers with "even" addresses (when `Addr[2] == 0`), as the value is already mapped to the first 4 bytes of the read/write data packet. But when accessing "odd" registers (when`Addr[2] == 1`), the value ends up bytes 4-7 of the Octbyte transfer, which gets ignored. _Note: Because RCP is big endian, it puts 32bit even addresses in the Upper half of the 64bit word, and 32bit odd addresses in the lower half, RI outputs the MSB first, so it ends up in byte 0 of the Rambus data packet._ [MI MODE](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0000_-_MI_MODE "MIPS Interface") 's **Upper mode** is used work around this problem, as it forces MI to always map the value into the upper half of DBus, which results in the value always mapping to bytes 0-3 of the "Octbyte" transfer, while still passing the original address through. Nintendo's IPL3 compulsively wraps all accesses to RDRAM register with writes to MI\_MODE's SetUpper/ClearUpper. But you only actually need Upper Mode for accessing the "odd" registers. **NOTE:** In the following register description we will omit the ninth bit which is unused when accessing RDRAM registers, and describe them as a 32bit word instead of 4x{8,9}bit. #### 0x00 - DeviceType * * * | DeviceType `0x00` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-? | R-? | R-? | R-? | U-0 | R-1 | U-0 | R-? | | ColumnBits | | | | — | Bn | — | En | | 23:16 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | BankBits | | | | RowBits | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | R-0 | R-0 | R-0 | R-? | R-0 | R-0 | R-0 | R-0 | | Version | | | | Type | | | | | | | | --- | --- | | bit 31-28 | **ColumnBits:** Number of column address bits, or said differently, declares that this RDRAM device has 2^ColumnBits bytes per row. | | bit 26 | **Bn:** Bonus, number of bits per byte.
0 = 8bit byte
1 = 9bit byte | | bit 24 | **En:** Enhanced speed grade.
0 = Normal
1 = Low Latency | | bit 23-20 | **BankBits:** Number of bank address bits, or said differently, declares that this RDRAM device has 2^BankBits banks. | | bit 19-16 | **RowBits:** Number of row address bits, or said differently, declares that this RDRAM devices has 2^RowBits rows per bank. | | bit 7-4 | **Version:** RDRAM version.
0001 = Extended architecture (Base RDRAM protocol)
0010 = Concurrent RDRAM device (not used on N64, as far as we know). | | bit 3-0 | **Type:** Device type.
0000 = RDRAM device | #### 0x01 - DeviceId * * * | DeviceId `0x01` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | U-0 | U-0 | | IdField\[25:20\] | | | | | | — | — | | 23:16 | RW-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | IdField\[26\] | — | — | — | — | — | — | — | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | IdField\[34:27\] | | | | | | | | | 7:0 | RW-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | IdField\[35\] | — | — | — | — | — | — | — | | | | | --- | --- | | bit 7,15-8,23,31-26 | **IdField\[35:k\]:** Compared to AdrS\[35:k\] to select RDRAM.
k = 21 for 16/18Mbit RDRAM.
k = 20 for 8/9Mbit RDRAM.
That is, bit 20 is ignored for 2 MiB RDRAM modules (which means that they can only be mapped to even device IDs and thus aligned to their size). | #### 0x02 - Delay * * * | Delay `0x02` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | RW-1 | RW-0 | RW-0 | R-0 | R-1 | R-1 | | — | — | AckWinDelay | | | AckWinBits | | | | 23:16 | U-0 | U-0 | RW-0 | RW-0 | RW-1 | R-0 | R-1 | R-1 | | — | — | ReadDelay | | | ReadBits | | | | 15:8 | U-0 | U-0 | U-0 | RW-0 | RW-0 | R-0 | R-1 | R-0 | | — | — | — | AckDelay | | AckBits | | | | 7:0 | U-0 | U-0 | RW-1 | RW-0 | RW-0 | R-0 | R-1 | R-1 | | — | — | WriteDelay | | | WriteBits | | | | | | | --- | --- | | bit 29-27 | **AckWinDelay\[2:0\]:** Adjusts the size of the acknowledge window. Normally set to 5 = 101b.
101b = 5 tcycles
110b = 6 tcycles
111b = 7 tcycles
000b = 8 tcycles
001b = 9 tcycles
010b = 10 tcycles
011b = 11 tcycles
100b = 12 tcycles | | bit 26-24 | **AckWinBits\[2:0\]:** Read-only. Number of bits of AckWinDelay (3). | | bit 21-19 | **ReadDelay\[2:0\]:** Delay between end of request and start of Read data packet. Normally set to 7 = 111b. Defaults to 8 = 001b after reset.
111b = 7 tcycles
000b = 8 tcycles
001b = 9 tcycles
010b = 10 tcycles
011b = 11 tcycles
100b = 12 tcycles
101b = 13 tcycles
110b = 14 tcycles | | bit 18-16 | **ReadBits\[2:0\]:** Read-only. Number of bits of ReadDelay (3). | | bit 12-11 | **AckDelay\[1:0\]:** Delay between end of request and start of Ack data packet. Normally set to 3 = 11b.
11b = 3 tcycles
00b = 4 tcycles
01b = 5 tcycles
10b = 6 tcycles | | bit 10-8 | **AckBits\[2:0\]:** Read-only. Number of bits of AckDelay (2). | | bit 5-3 | **WriteDelay\[2:0\]:** Delay between end of request and start of Write data packet. Normally set to 1 = 001b. Defaults to 4 = 100b after reset.
001b = 1 tcycles
010b = 2 tcycles
011b = 3 tcycles
100b = 4 tcycles
101b = 5 tcycles
110b = 6 tcycles
111b = 7 tcycles
000b = 8 tcycles | | bit 2-0 | **WriteBits\[2:0\]:** Read-only. Number of bits of WriteDelay (3). | ##### **Reset Complications** RI is hardwired to use a write delay of 1 TCycle, this means the Write request packet is send starting from (TCycle 0, RCP Cycle 0), finishing after 3 TCycles. 64bits of Data is send starting at (TCycle 4, RCP Cycle 1), finishing at (TCycle7, RCP Cycle 1.75) But the WriteDelay defaults to 4 TCycles after reset _(probably\[1\])_, Attempting to write a register will result in the Rambus device sampling 32bits of data during TCycles 7 and 8 (which is RCP Cycle 1.75 and 2.0), which is too late, It's going to be zeros or some other garbage. Before we can do anything else at all, we need to somehow set the WriteDelay to the correct value of 001b, despite the fact the WregB command results in the Rambus device reading garbage. IPL3 gets around this problem by using MI's Repeat mode (called "Init mode" in some documentation), see [MI\_MODE](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0000_-_MI_MODE "MIPS Interface") for more details about MI's various modes. By configuring a 16 byte repeat (`MI_MODE_REG] = 0x10f`), the next word written to an Rambus device register (or memory) will be repeated over 16 bytes. This causes RI to emit a 128bit burst request, with the data on the bus during TCycle 4-11. As the Rambus device is sampling the bus during TCycle 7 and 8, it will get valid data we control. But it's reading data halfway between two words. So IPL3 has to rotate the value by 16 bits to get the correct result. It wants to write `0x2838_1808`, but it actually writes `[0xA3F80000 + RDRAM_DELAY_REG] = 0x1808_2838`. After updating WriteDelay with a broadcast write to all device, all future operations will have the correct timings. _\[1\] The Toshiba 8Mbit datasheet confirms this as the default. It default to 4, because some devices (like the Toshiba 18Mbit) only have 2 bits, so can only support a maximum write delay of 4. Devices with more bits default to 0b100 for compatibility._ #### 0x03 - Mode * * * | Mode `0x03` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-1 | RW-1 | RW-0 | R-0 | RW-0 | RW-1 | RW-0 | RW-0 | | CE | X2 | PL | SV | SK | AS | DE | LE | | 23:16 | RW-1 | RW-1 | U-0 | U-0 | RW-0 | U-0 | U-0 | U-0 | | C5 | C2 | — | — | AD | — | — | — | | 15:8 | RW-1 | RW-1 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | C4 | C1 | — | — | — | — | — | — | | 7:0 | RW-1 | RW-1 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | C3 | C0 | — | — | — | — | — | — | | | | | --- | --- | | bit 31 | **CE / CCEnable:** Current Control Enable.
0 = manual
1 = auto | | bit 30 | **X2 / CCMult:** Should be 1. Inverted when read. (Toshiba datasheet states that it select wether X1 or X2 register is used for the current control register). | | bit 29 | **PL:** Select PowerDown Latency | | bit 28 | **SV / SkipValue:** For tests. 0 | | bit 27 | **SK / Skip:** For tests. 0 | | bit 26 | **AS / AutoSkip:** For tests. 1 | | bit 25 | **DE / DeviceEnable:** Enable RDRAM device. When disabled, only broadcast register requests can be executed.
0 = disabled
1 = enabled | | bit 24 | **LE:** Enable PowerDown mode for RDRAM that supports it to reduce power consumption. | | bit 19 | **AD / AckDis:** For low latency RDRAM only. Allows to supress acknowledge response when set to 1. | | bit 23,15,7,22,14,6 | **C\[5:0\] / CCValue:** Current Control value which controls _in fine_ the output current IOL.
In manual mode (CE=0), IOL is proportional to (63-CC) with IOL ~ (0.95±0.3)×(63-CC) mA, for CC = 0..63. (These coefficients derive from Imax±△/63 and vary between models)
This field is inverted when read.
In auto mode (CE=1), IOL is proportional to (63-CC) with IOL ~ (1.25±0.1)×(63-CC) mA, for CC = 31..63. (These coefficients derive from I40±△/(63-31) and vary between models)
An internally generated value is returned when read. | #### 0x04 - RefInterval * * * | RefInterval `0x04` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | | ? | | | | | | | | | 23:16 | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | | ? | | | | | | | | | 15:8 | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | | ? | | | | | | | | | 7:0 | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | ?-? | | ? | | | | | | | | | | | | --- | --- | | bit 31-0 | **?:** Unknown format | #### 0x05 - RefRow * * * | RefRow `0x05` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | U-0 | | RowField\[7:1\] | | | | | | | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | RW-? | U-0 | U-0 | U-0 | | — | — | — | — | BankField | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-? | RW-? | | — | — | — | — | — | — | RowField\[9:8\] | | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 9-8,31-25 | **RowField:** Current row being refreshed. | | bit 19 | **BankField:** Current bank being refreshed. | This register is normally read or written only for testing purpose. #### 0x06 - RasInterval * * * | RasInterval `0x06` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | RW-? | RW-? | RW-? | RW-? | RW-? | | — | — | — | RowPrecharge\[0:4\] | | | | | | 23:16 | U-0 | U-0 | U-0 | RW-? | RW-? | RW-? | RW-? | RW-? | | — | — | — | RowSense\[0:4\] | | | | | | 15:8 | U-0 | U-0 | U-0 | RW-? | RW-? | RW-? | RW-? | RW-? | | — | — | — | RowImpRestore\[0:4\] | | | | | | 7:0 | U-0 | U-0 | U-0 | RW-? | RW-? | RW-? | RW-? | RW-? | | — | — | — | RowExpRestore\[0:4\] | | | | | | | | | --- | --- | | bit 24-28 | **RowPrecharge:** Specify RowPrecharge timing. | | bit 16-20 | **RowSense:** Specify RowSense timing. | | bit 8-12 | **RowImpRestore:** Specify RowImpRestore timing. | | bit 0-4 | **RowExpRestore:** Specify RowExpRestore timing. | NOTE: all fields are in bit reversed order (bit 4 is LSB, bit 0 is MSB). #### 0x07 - MinInterval * * * | MinInterval `0x07` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | R-0 | R-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | MAD\[3\] | MRD\[3\] | MWD\[3\] | — | — | — | — | — | | 23:16 | R-0 | R-1 | R-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | MAD\[2\] | MRD\[2\] | MWD\[2\] | — | — | — | — | — | | 15:8 | R-1 | R-1 | R-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | MAD\[1\] | MRD\[1\] | MWD\[1\] | — | — | — | — | — | | 7:0 | R-1 | R-1 | R-1 | W-0 | W-0 | W-0 | W-0 | W-0 | | MAD\[0\] | MRD\[0\] | MWD\[0\] | SpecFunc\[4:0\] | | | | | | | | | --- | --- | | bit 31,23,15,7 | **MinAckDelay:** Minimum of AckDelay of RDRAM. | | bit 30,22,14,6 | **MinReadDelay:** Minimum of ReadDelay of RDRAM. | | bit 29,21,13,5 | **MinWriteDelay:** Minimum of WriteDelay of RDRAM. | | bit 4-0 | **SpecFunc:** Performs various commands when written, see table below. | As SpecFunc is the only writable field in the register, you can just write the command. **SpecFunc Commands:** | | | | | --- | --- | --- | | `00000` | \- **Nop** | \- Do nothing | | `xxxx1` | \- **SetRR** | \- Manual refresh. The device immediately preforms a single a burst refresh of two rows per bank | | `x0x10` | \- **ClrRE** | \- Disable automatic refresh | | `x01xx` | \- **SetPD** | \- Enter the powerdown state | | `×1x0x` | \- **SetRE** | \- Enable automatic refresh | | `1xxxx` | \- **Reserved** | | _The N64 implements refresh by broadcasting one SetRR command whenever VI emits a horizontal sync pulse._ #### 0x08 - AddressSelect * * * | AddressSelect `0x08` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | U-? | | SwapField\[6:0\] | | | | | | | \- | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | RW-0 | RW-0 | | \- | \- | \- | \- | \- | \- | SwapField\[8:7\] | | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | \- | \- | \- | \- | \- | \- | \- | \- | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | \- | \- | \- | \- | \- | \- | \- | \- | | | | | --- | --- | | bit 31-25,16-15 | **SwapField:** Each bit swaps two bits of the address. When all bits are 0, there is no swaping. | **Extra Details:** The address swapping feature allows banks to be interleaved. For example, Bank 0 row 0 can be followed by Bank 1 row 0 and so on. This can improve performance for many memory access patterns. However, RI doesn't appear to support this feature. It expects Bank zero to be in the first megabyte of address space, Bank one in the second megabyte, and so on. #### 0x09 - DeviceManufacturer * * * | DeviceManufacturer `0x09` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | ManufactureCode\[7:0\] | | | | | | | | | 23:16 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | ManufactureCode\[15:8\] | | | | | | | | | 15:8 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | Manufacture\[7:0\] | | | | | | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | Manufacture\[15:8\] | | | | | | | | | | | | --- | --- | | bit 31-24,23-16 | **ManufactureCode:** Manufacture is allowed to put whatever they want here? | | bit 15-8,7-0 | **Manufacture:** Code specifying the manufacturing company, see table below. | | Manufacture | ID Code | | --- | --- | | Toshiba | `0x0002` | | Fujitsu | `0x0003` | | NEC | `0x0005` | | Hitachi | `0x0007` | | Oki | `0x0009` | | LG Semicon | `0x000a` | | Samsung | `0x0010` | | Hyundai | `0x0013` | RDRAM addressing ================ Warning : In this paragraph, we describe RDRAM addressing within the RDRAM protocol. This is not to be confused with RDRAM addresses "as seen" by the CPU or RCP. See [RI memory addressing](https://n64brew.dev/wiki/RDRAM_Interface#Memory_address "RDRAM Interface") paragraph for details about how the RI converts addresses between the two address spaces. RDRAM protocol addresses RDRAM memory and registers using a 36bit address and a variety of commands : * many types of memory read * many types of memory write * register read * register write * broadcast register write (all connected RDRAM will write the same value to the specified register) The higher part of the address identify an RDRAM device, the lower part is an offset within the device (in register-space for register commands, and memory space for memory commands). The procedure of identifying which RDRAM device is addressed by a given command + address is call Id matching. It works as follow: Given a 36 bit address Adr\[35:0\], we compute a "partially bit-swapped" AdrS\[35:0\] such that bits \[28:20\] and bits \[19:11\] are swapped on a bit by bit bases based on the value of SwapField (from AddressSelect register). Bits \[35:29\] and \[10:0\] are left untouched. This swapping of bits provides a flexible way of remapping addresses across banks of a given device and across devices to benefit from internal row caching. This can help increase DRAM hit rate in several applications. The upper 16 bits (or 15bits for 2x{8,9}Mbit devices) of AdrS are then compared to IdField contained in DeviceId register. If both are equal the RDRAM device has a Id Match. More formally this can be written as follow : AdrS[35:29] = Adr[35:29] Remark : An IdMatch doesn't mean necessarily that the RDRAM device will act on the request, and conversely a non matching RDRAM device can still act on a request. Other factors such as DeviceEnable bit from ModeRegiter, SIn pinout can inhibit a request, and the broadcast register write can force a request even on non matching RDRAM device. Current Control calibration =========================== Any RDRAM device (module and controller) wishing to "talk" on the RDRAM channel must configure its output current IOL controlled by the current control (**CC** for short) register. 2 modes are possible to configure the current control register : 1. Manual mode. In this mode, the value of the current control register is linearly correlated to IOL, such that IOL @ CC=63 -> 0mA, and IOL @ CC=0 -> Imax (Imax will vary between RDRAM due to process differences). In this mode, fluctuation due to temperature, change over time and are not compensated, so it may require a manual periodic readjustment. Note also that, in this mode, the CC register value read will be inverted. 2. Automatic mode. In this mode, small fluctuations are automatically corrected, so no further readjustment should be required. The relation between CC value and IOL is still mostly linear but with a different slope. Note also that, in this mode, the CC register value read will be an internally generated one, not the one used to program the CC register. The purpose of the CC calibration procedure is to find the CC value in Automatic mode that maximize the signal margin. One possible approach to do so is described below : We define the quantity CCi = 63-CC (= CC^63 = CC "inverted") which is more natural to use because IOL is proportional to CCi. We define a memtest80 function which writes an octbyte with all bits set to '1' (eg. UINT64\_C(0xffffffffffffffff)) at the start of the RDRAM device to test, and read back the 6th byte of the previously written octbyte. It then counts how many bits were set. This write / readback is done 10 times, and the cumulated number of '1' bits read is returned. For a non calibrated RDRAM device, the number is less than 80 (eg. the device can't always transfer back '1' because of inadequate VOL). Basically, the returned value gives a score (over 80) of the quality of the RDRAM device transmission with the current CC value. 1\. Estimate the value of CCi in manual mode which gives a VOL almost equal to VREF. This can be done by writing increasing CCi values in manual mode and accumulating the weighted difference CCi \* (memtest80 - previous\_memtest80) for CCi = 0..N (N being the first value of CCi which allows to read all 80 bits during memtest80; N <= 63). This weighted sum of CCi\*(memtest80-previous\_memtest80) for CCi = 0..N, divided by 80 (minus 0.5 to account for accumulation/rounding errors) is an estimate of CCi which gives VOL ~ VREF. 2\. Multiply this value by 2.2: doubling the CCi value, with 10 percent margin, should give a reasonable estimate of CCi such that VOL is symmetric to VOH with respect to VREF (eg. it maximizes signal margin). 3\. Convert the obtained manual CCi value to auto CCi. Here the procedure is again iterative and tries to find the value CCi to write in Auto mode which minimizes the absolute difference between the CCi value read in auto mode (remember this is an internally generated value different from the CCi value written) and the target manual CCi. 4\. Repeat this whole procedure 4 times and average the obtained auto CCi value. In practice steps 1., 2. and 3. avoid usage of floating points and rescale some values with an appropriate scaling factor (here 80x10) to avoid loss of precision due to integer computations. Known RDRAM Console Chip Configurations ======================================= | | | | | | | | --- | --- | --- | --- | --- | --- | | N64 Version | Board revision | Region | Number of RDRAM Chips | Size per Chip(Mbytes) | Size per Chip(Mbits) | | NUS-001 | (P) - 01 | PAL | 2 | 2.25Megabytes | 18Mbit | | NUS-002 | (P) - 02 | PAL | 1 | 4.5Megabytes | 36Mbits | Known RDRAM Expansion Pak Configurations ======================================== | | | | | | --- | --- | --- | --- | | Expansion Pak Type | Number of RDRAM Chips | Size per Chip(Mbytes) | Size per Chip(Mbits) | | Jumper Pak (Nintendo Official) | 0 | 0 | 0 | | Expansion Pak (Nintendo Official) | 1 | 4.5Megabytes | 36Mbits | There are 3rd party Expansion Paks that have 2 chips which are both 2.25Megabytes each. Please provide images and makers here. Initialization Sequence ======================= This Initialization sequence is based on the 6102 CIC boot code **[File:Cncrntug.pdf](https://n64brew.dev/wiki/File:Cncrntug.pdf "File:Cncrntug.pdf") ** RDRAM Initialization procedure as implemented in IPL3: 1. a. Enable RI Auto Current b. let it settle by waiting using countdown(8800) c. load RI CC value 2. Enable RI T/R select 3. a. Force RI\_MODE reset, disable R/T stop b. wait using countdown(4) 4. a. Force RI\_MODE standby, enable R/T stop b. wait using countdown(32) 5. a. Set MI INIT mode + length=15 by writing 0x10f to \[MI\_MODE\_REG\] b. Setup all RDRAM delays (AckWin=5,Read=7,Ack=3,Write=1) by writing 0x18082838 to \[0xa3f80004\] \[bcast\] 6. a. Setup all RDRAM refresh row to 0 \[bcast\] b. Move all RDRAM modules to top of address space deviceid = 0x80000000 \[bcast\] 7. a. compute rdram reg space size (reg\_step) based on RCP version (RCPv1: 128, RCPv2: 256) b. init top rdram reg pointer (RDRAM\_REGS\_BASE + 32 \* reg\_step) 8. First pass which walk through at most 8 RDRAMs and for valid ones: a. place them at next 2MB boundary (eg. rdram\_deviceid = i \* 0x08000000) b. compute optimal (auto) current calibration value for RDRAM module and apply it c. exit first pass loop if cc value is zero, eg. no RDRAM module is present d. read device description registers (device\_type + manufaturer). These reads must be surrounded by MI\_MODE= SET\_DRAM\_REG and CLR\_DRAM\_REG because individual rdram registers are accessed. e. based on device description, setup optimal RAS timing f. store RDRAM parameters (CC, geometry {eg. col, bank, row fields from device\_type}) for second pass g. update values which tracks how to reorder all 2MB RDRAM modules before the 1MB, how many modules are effectively presents and the 2MB\_bitfield (=2^(number of 2MB modules)-1, because all 2MB banks will be placed first) 9. a. Disable all RDRAM modules (rdram\_mode = 0xc4000000) \[bcast\] b. and move them all back to top of address space (rdram\_deviceid = 0x80000000) \[bcast\] 10. Second pass iterate through all modules discovered during the first pass and: a. reorder them so that all 2MB modules are placed before 1MB modules b. write previously computed optimal CC for each module c. touch RDRAM modules to settle their timing circuits. 1MB modules undergo 4 consecutive reads (ptr+k\*0x00080000, k=0..1) x 2 2MB modules undergo 8 consecutive reads (ptr+k\*0x00080000, k=0..3) x 2 11. a. setup RI refresh register = 0x63634 | 2MB\_bitfield << 19 b. do a dummy read of RI refresh reg 12. Return amount of detected RDRAM. Trivia: there is very likely a copy-paste error in the original IPL3 code when incrementing t6 in the second pass. It should have been t8 so we can place next 1MB module at next slot. But I guess it went unnoticed because retail models don't use 1MB modules. Expansion Pak Detection ======================= The typical way to detect how much memory is installed is to probe it. LibUltra provides a function called osGetMemSize() which does this. The function writes different values at addresses in the uncached KSEG1 direct map, starting at 0xa0300000, and then reads the values back. It tries successively higher addresses, jumping by 1 MB each time through the loop. It returns the amount of RAM which it successfully wrote and read back, rounded up to a number of megabytes. // C-like pseudocode... u32 osGetMemSize(void) { // Base address of RAM in kseg1. uintptr\_t base\_addr = 0xa0000000; uintptr\_t megabyte = 1024 \* 1024; // Address where we will probe. uintptr\_t cur\_addr = kseg1 + 3 \* megabyte; while (true) { write to addr; read from addr; if (value read != value written) { break; } cur\_addr += megabyte; } return cur\_addr - base\_addr; } During boot, IPL3 will also write the amount of RAM available, in bytes, to a 32-bit value at address 0x80000318 (or 0x800003f0, for CIC 6105). On retail hardware, this should always have the value 0x400000 (no expansion pak) or 0x800000 (expansion pak). When using LibUltra, this variable can be accessed with the name osMemSize, which is defined like this: extern u32 osMemSize; LibDragon provides the the amount of memory installed with the get\_memory\_size() function. Drawbacks and Limitations ========================= #### Opinion > RDRAM has excellent data transfer speed for the era (bytes per second) but due to the protocol used and serial interface, memory transactions were somewhat slower (how much time it took from starting a read/write operation to finishing it). In practice, you may find that the available memory bandwidth is a limiting factor for the performance of your game. See: [How fast was Rambus compared to regular EDO RAM?](https://retrocomputing.stackexchange.com/questions/17564/how-fast-was-rambus-compared-to-regular-edo-ram) > > — Vanadium Datasheets ========== Several manufacturers produced compatible "Base RDRAM" modules such as : * [LG GM73V1892AH16L](https://www.datasheetarchive.com/pdf/download.php?id=ba4c2602c3e903353ab08f0b5ce7b7ba8aff76&type=O&term=GM73V1892AH16L) * [NEC uPD488170L](https://www.datasheetarchive.com/pdf/download.php?id=80d868b3d8d4b492b8255b3cf45587bffb5c32&type=P&term=uPD488170L) * [OKI MSM5718B70](https://www.datasheetarchive.com/pdf/download.php?id=98c391ce728eda7ff786fc9edfaa7c8d71859d&type=M&term=MSM5718B70) * [Toshiba TC59R1809VK TC59R1809HK (18Mbit chip)](https://www.datasheetarchive.com/pdf/download.php?id=187da05b037831407d99bb6a7a103aa0d6dc80&type=O&term=TC59R1809) * [Toshiba TC59R0808HK (8Mbit chip)](https://datasheet.datasheetarchive.com/originals/scans/Scans-067/DSA2IH00203571.pdf) Reference : [\[2\]](https://bitbuilt.net/forums/index.php?threads/n64-expansion-paks-ram-part-numbers.3943/) iQue Player =========== The iQue Player replaced RDRAM with an off-the-shelf K4D263238E-GC33 16MiB DDR SDRAM differentially clocked at 192MHz for a peak bandwidth of up to 1536MB/s, about 3x that of N64's RDRAM. The bus latency is also much lower as it does not need to implement a packet protocol for transferring control signals, addresses and data over a small number of shared pins in the way RDRAM does; the SDRAM has a 32-bit data bus, a 12-bit address bus for both row and column addresses, and additional lines for other control signals. However this SDRAM does not have an extra bit per byte as in RDRAM, this had to be emulated. This appears to have been achieved by halving the amount of available RAM to an effective 8MiB while using the remaining 8MiB to (very inefficiently) store coverage bits. The memory controller in the iQue Player SoC probably performs 2x32-bit transfers for each 32-bit word transferred, in which the first transfer contains the data and the second transfer contains coverage bits in the least significant bit of each byte. In the virtual memory map, addresses `0x81xxxxxx` can be accessed to bypass the memory controller's translation step to get a raw view of the memory, which looks like: | 0x0 | 0x4 | 0x8 | 0xC -----------+-------------------------+---------------------------+-------------------------+--------------------------- 0x81000000 | \[32 bits at 0x80000000\] | \[Coverage for 0x80000000\] | \[32 bits at 0x80000004\] | \[Coverage for 0x80000004\] 0x81000010 | \[32 bits at 0x80000008\] | \[Coverage for 0x80000008\] | \[32 bits at 0x8000000C\] | \[Coverage for 0x8000000C\] ... For example writing two words at `0x80000000` to: | 0x0 | 0x4 -----------+------------+------------ 0x80000000 | 0xFF00FF00 | 0x00FF00FF produces the following when read back at `0x81000000`: | 0x0 | 0x4 | 0x8 | 0xC -----------+------------+------------+------------+------------ 0x81000000 | 0xFF00FF00 | 0x01000100 | 0x00FF00FF | 0x00010001 Writing to `0x80000000` causes changes in both `0x81000000` and `0x81000004`. It is also possible to write directly to `0x81000000`: writing to data words are mirrored in `0x80000000` without disturbing coverage, while writing coverage leaves the corresponding data unchanged. If only using the CPU this allows all 16MiB of SDRAM to be used, however it is not very practical as it does not behave well with respect to DMA. Retrieved from "[https://n64brew.dev/wiki/RDRAM?oldid=5529](https://n64brew.dev/wiki/RDRAM?oldid=5529) " --- # Reality Coprocessor - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Coprocessor#) Reality Coprocessor =================== The **Reality Coprocessor**, or **RCP**, is one of the two main processors on the Nintendo 64 board. Split into two components, the [Reality Signal Processor](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor") and [Reality Display Processor](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") , it cooperates with the other main processor, the [VR4300 CPU](https://n64brew.dev/wiki/VR4300 "VR4300") to draw 3D graphics, perform matrix calculations, and produce audio. The RCP is also connected to the onboard RDRAM, providing direct access to memory. It is located underneath the control deck shell's ventilation slots that are in front of the cartridge slot. [![N64 RCP Decapped](https://static.wikitide.net/n64wiki/thumb/e/e2/N64_RCP_Decapped.jpg/600px-N64_RCP_Decapped.jpg)](https://n64brew.dev/wiki/File:N64_RCP_Decapped.jpg) **N64 RCP Decapped** RCP Interface ------------- Main article: [RCP Interface](https://n64brew.dev/wiki/RCP_Interface?action=edit&redlink=1 "RCP Interface (page does not exist)") RCP is the core component of the N64 architecture. It is connected to all other peripherals (VR4300, ROM, RDRAM), and handles communications in all directions. For instance, VR4300 must go through RCP to access RDRAM. RCP is thus in charge of implementing the [full VR300 Memory Map](https://n64brew.dev/wiki/Memory_map "Memory map") . Please check [VR4300 interface](https://n64brew.dev/wiki/MIPS_R4300_interface "MIPS R4300 interface") for a detailed description of the bus between the CPU and the RCP at the electric level. RSP --- Main article: [Reality Signal Processor](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor") The **Reality Signal Processor**, or **RSP**, is the portion of the RCP responsible for matrix math, lighting calculations, clipping, shading, and other highly parallel graphics tasks, as well as audio processing. It is a programmable MIPS processor with a custom set of SIMD instructions for vectorized fixed point operations (exposed as COP2 -- a group of reserved instructions in the standard MIPS instruction set). The RSP is also able to directly drive the RDP (the hardware rasterizer) by accessing its registers, so that it can terminate the graphic pipeline by telling the RDP to draw triangles into the framebuffer. RDP --- Main article: [Reality Display Processor](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") The **Reality Display Processor**, or **RDP** is a large fixed-functionality internal block within RCP that is responsible for the final steps of video processing. The RDP receives instructions from the RSP (reality signal processor) and a rasteriser, texture unit (paired with 4KB of Texture memory), color combiner and blending unit complete each frame. The resulting data is then sent to the framebuffer in RAM before being fetched by the CPU and sent to the video encoder for display. Retrieved from "[https://n64brew.dev/wiki/Reality\_Coprocessor?oldid=5481](https://n64brew.dev/wiki/Reality_Coprocessor?oldid=5481) " --- # FAQ - N64brew Wiki [](https://n64brew.dev/wiki/FAQ#) FAQ === This page tries to provide a detailed answer to frequently asked questions on Nintendo 64. Some of these have spawned myths over the years in online communities, so this page hopefully answers properly and debunks some of them. Contents -------- * [1 What is the maximum cartridge size (ROM) supported by a Nintendo 64?](https://n64brew.dev/wiki/FAQ#What_is_the_maximum_cartridge_size_(ROM)_supported_by_a_Nintendo_64?) * [2 Would it be possible to create a larger RAM expansion for N64, to go beyond the total 8 MiB we can get today with the Expansion Pak?](https://n64brew.dev/wiki/FAQ#Would_it_be_possible_to_create_a_larger_RAM_expansion_for_N64,_to_go_beyond_the_total_8_MiB_we_can_get_today_with_the_Expansion_Pak?) * [3 Is it true that the RSP has hardware MPEG-1 acceleration? Is it used by full motion videos in Resident Evil 2?](https://n64brew.dev/wiki/FAQ#Is_it_true_that_the_RSP_has_hardware_MPEG-1_acceleration?_Is_it_used_by_full_motion_videos_in_Resident_Evil_2?) ### What is the maximum cartridge size (ROM) supported by a Nintendo 64? **Short answer:** 4 GiB, without bank switchers. Unlimited otherwise. In practice, though, people can hardly run ROMs bigger than 64 MiB on real hardware using existing tools. Most people seem to believe that Nintendo 64 is somehow limited to cartridge of 64 MiB. The origin of this belief is likely the fact that 64 MiB is the maximum size used by retail games (such as Conker: Bad Fur Day). In fact, there is no such a hardware limit. The cartridge is accessed via the [PI bus](https://n64brew.dev/wiki/PI "PI") , a multiplexed parallel bus that allows for full 32-bit addresses, accessible via DMA: so a hardware cartridge can reply with data to any address in the 32-bit range, that is a total of 4 GiB. ROMs must have a valid header at address `1000'0000`, but besides that, there is absolutely no constraint: a cartridge could also reply with data in the range `0000'0000` - `0FFF'FFFF`, as long as the application itself knows about it and retrieves it when necessary. Others seem to believe the maximum size is 252 MiB. That belief comes from the fact that some of the address space of the PI bus is also [memory-mapped](https://n64brew.dev/wiki/Memory_map "Memory map") to the VR4300 (and that includes normally the whole ROM too), and in fact 252 MiB of it is memory mapped in the physical range `0x10000000-0x1FBFFFFF` but that is not a limit or a constraint in any way. There is no technical constraints preventing a cartridge to expose a larger area to the PI bus (up to 4 GiB). It is true that this full 32-bit address space is accessible only via DMA, but that is actually the main (and almost only) way a ROM is normally accessed: direct I/O accesses via CPUs in memory mapped areas are rather slow, cannot be cached, and only work correctly with 32-bit access size. So in practice they are rarely used. At the hardware level, the presence of a parallel bus means that it is possible to split ROM contents across a different array of chips if required; as long as the PI bus decoding logic knows how to map each address to the correct chip, it will be fine. Homebrew developers should anyway carefully consider going past 64 MiB with their ROMs. In fact, most emulators do not support ROMs beyond that limit (even though the modification is expected to be quick once they are made aware that it is not a hardware limit), and flashcarts commonly used to play home-brew productions on hardware (such as EverDrive 64 or SummerCart 64) only have about 64 MiB of SDRAM to keep ROM contents, so they do not support larger ROMs as well. ### Would it be possible to create a larger RAM expansion for N64, to go beyond the total 8 MiB we can get today with the Expansion Pak? **Short answer:** Currently**,** it is thought to be impossible because of a physical hardware limit of the RDRAM controller (RI) within the RCP chip. The RDRAM chips are connected to the RCP via a bus called RAMBUS. This bus allows to connect multiple chips to a controller; the controller can then talk to each chip and configure it to reply to a certain range of addresses (that is, "map it" into a memory map). The RDRAM initialization is performed by [IPL3](https://n64brew.dev/wiki/IPL3 "IPL3") , a piece of the Nintendo 64 secure boot code (there are a few variants to its contents but the differences are not related to RAM management). IPL3 does the RDRAM chip initialization using a process called "current calibration", and then map them into the (phyisical) address space, by giving to each chip its own address. Nintendo's IPL3 can correctly handle up to 4 2-MiB chips (it also has buggy support for 1 MiB chips, which probably were never released by Rambus, so the code was never tested).  [](https://n64brew.dev/wiki/File:Custom_8_MiB_expansion_pak.jpg) Custom 8 MiB expansion pak For a long time, it was then believed that changing IPL3 would be enough to allow more chips to be mapped, assuming somebody built an expansion pak card with more chips in it. Instead, late work on [Libdragon's open source IPL3](https://github.com/DragonMinded/libdragon/tree/preview/boot) led to more reverse engineering of the RI chip, which eventually proven that RI _internally_ runs a state machine that tracks RDRAM bank status, and only has enough room for 8 1-MiB banks (each 2-MiB chip is logically made of 2 1-MiB bank). More details can be found in the [RDRAM Interface#Bank Status Tracking](https://n64brew.dev/wiki/RDRAM_Interface#Bank_Status_Tracking "RDRAM Interface") wiki section. On top of this, Rasky (who authored Libdragon's IPL3) also got the chance to test a custom made expansion pak with 8 MiB of RAM in it, designed by LambBrainz. This pak uses donor chips from official expansion paks, and is correctly recognized by Nintendo IPL3, though just as a 4 MiB pak. Anyway, by hacking Libdragon's open source IPL3, it can be seen that the extra banks were indeed present on the bus, but it turned out to be inaccessible after bus mapping. This is a further proof that RI is not actually able to handle more than 8 MiB of RAM. ### Is it true that the RSP has hardware MPEG-1 acceleration? Is it used by full motion videos in Resident Evil 2? **Short answer:** RSP has two couples of opcodes (`VMULQ`/`VMACQ` and `VRNDN`/`VRNDP`) that are meant to simplify implementation of a very small part of the MPEG-1 decoder (inverse quantization and oddification of IDCT coefficients). They are not used by Resident Evil 2 or any other commercial game though. RSP is the vector coprocessor in Nintendo 64. It is well designed to also accelerate video codecs such as those of the MPEG family. To do so, though, careful assembly code must be written using the specific RSP vector opcodes to perform the various operations required for a video decoder. When RSP was designed by SGI (in the early 90s), only MPEG1 existed as a finished standard (MPEG2 was finalized in 1994, after the RSP design was frozen), so SGI designers decided to add a couple of opcodes to the instruction set to help implementing a part of the MPEG1 decoder: specifically, the algorithm that performs inverse quantization and oddificaton of IDCT coefficients. This is actually a quite specific part of the whole pipeline, and it is not even the most resource intensive one. It is hard to guess why the SGI designers thought it was important to specifically add these instructions or speed up this specific part of the pipeline. Resident Evil 2, which is a marvelous example of careful code crafting, did manage to cram full motion videos together with the whole game in the limited 64 MiB cartridge. To do so, they encoded them with MPEG1 at a low resolution, bitrate and frame rate (around 15 fps), and then performed a nice interpolation (cross-fade) between frames using RDP. The MPEG decoder was not specifically accelerated though (if not for the final YUV to RGB conversion, which is technically not even part of MPEG): they simply recompiled a C player that worked good enough at that bitrate, so they did not get to use the special RSP instructions for it. In modern times, libdragon provides a fully accelerated MPEG decoder that uses RSP and also [uses the special RSP instructions](https://github.com/DragonMinded/libdragon/blob/afc35325e33d2bd3a73a01405aa5ecf1b367c5a9/src/video/rsp_mpeg1.S#L342-L343) . This can also be used as further proof that the instructions SGI designed were indeed useful for their goal, albeit for a very small part of the whole algorithm. Retrieved from "[https://n64brew.dev/wiki/FAQ?oldid=5764](https://n64brew.dev/wiki/FAQ?oldid=5764) " --- # VR4300 - N64brew Wiki [](https://n64brew.dev/wiki/VR4300#) VR4300 ====== The **VR4300** is the CPU of the Nintendo 64, and is a NEC VR4300 with slight modifications. Running at 93.75 MHz, the VR4300 handles game logic, reading responding to controller input from the [PIF](https://n64brew.dev/wiki/PIF "PIF") , and preparing display lists and audio command lists for the [RCP](https://n64brew.dev/wiki/RCP "RCP") to draw and synthesize audio. The VR4300 also contains an FPU that the VR4300 identifies as a co-processor (CP1) despite not being one. The FPU speeds up operations with 32-bit and 64-bit floating-point numbers. The VR4300 integrates another coprocessor called The System Control Coprocessor (CP0). The system control processor contains a Memory Management Unit (MMU) and a Translation Lookaside Buffer (TLB). [VR4300 Users Manual](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf "File:VR4300-Users-Manual.pdf") [![VR4300 Decapped Edited by BGcrain03, Original by ChrisPVille](https://static.wikitide.net/n64wiki/thumb/2/20/VR4300_DieShot.jpg/600px-VR4300_DieShot.jpg)](https://n64brew.dev/wiki/File:VR4300_DieShot.jpg) Die Shot of VR4300 Contents -------- * [1 Naming History](https://n64brew.dev/wiki/VR4300#Naming_History) * [2 Revision Identifiers](https://n64brew.dev/wiki/VR4300#Revision_Identifiers) * [3 Modifications](https://n64brew.dev/wiki/VR4300#Modifications) * [4 Microarchitecture](https://n64brew.dev/wiki/VR4300#Microarchitecture) * [4.1 Load Delay Interlock](https://n64brew.dev/wiki/VR4300#Load_Delay_Interlock) * [5 Known Bugs](https://n64brew.dev/wiki/VR4300#Known_Bugs) * [5.1 Multiplication Bug](https://n64brew.dev/wiki/VR4300#Multiplication_Bug) * [5.2 32-bit Shift Right Arithmetic Bug](https://n64brew.dev/wiki/VR4300#32-bit_Shift_Right_Arithmetic_Bug) * [5.3 Sign extension bugs](https://n64brew.dev/wiki/VR4300#Sign_extension_bugs) Naming History ============== In 1993, [MIPS Technologies, Inc.](https://en.wikipedia.org/wiki/MIPS_Technologies "w:MIPS Technologies") (MTI), the same company that designed the MIPS III architecture, developed the [R4200](https://en.wikipedia.org/wiki/R4200 "w:R4200") microprocessor. Later in 1995, they made the R4300i, a derivative of the R4200. When MTI licensed it to NEC and Toshiba, those companies renamed the chips the VR4300 and TX4300 respectively. The microprocessor used in the N64's CPU, is a derivative of NEC's VR4300. Over the years, these model names have been incorrectly identified. There is no such thing as a VR4300i, nor is there a NEC R4300. Revision Identifiers ==================== **Warning: Not all board revisions have been tested. It is possible that some board revisions contain different revision identifiers than what is documented here so far.** The VR4300 has two revision identifier registers: the Coprocessor 0 PRId (Processor Revision Identifier) Register and the Coprocessor 1 FCR0 implementation/revision register. PRId bits \[15:8\] is the processor id number and bits \[7:0\] is the revision number. The revision number is further split into major and minor revisions, in 4 bits each. All VR4300 units will report 0x0B (11) for the processor id number, however the revision number may vary: retail N64 units have _so far_ been found to report either 0x10 (1.0, early units) or 0x22 (2.2, later units), and the iQue Player reports 0x40 (4.0). FCR0 bits \[15:8\] is the implementation number and bits \[7:0\] is the revision number. All VR4300 units will report 0x0B (11) for the implementation number. Retail N64 units and the iQue Player have _so far_ been found to report 0x00 for the revision number. Modifications ============= The VR4300 used in the N64 has been modified compared to the original VR4300 chip. Currently the known differences include: * Six pins are in different locations | | Original | N64 | | --- | --- | --- | | /INT1 | 58 | 57 | | JTCK | 57 | 58 | | /EValid | 105 | 104 | | /Reset | 104 | 105 | | DivMode0 | 116 | 112 | | DivMode1 | 112 | 116 | _There may be other differences, more research is required._ Microarchitecture ================= Load Delay Interlock -------------------- The VR4300 will stall for 1 cycle following a memory load instruction if the instruction appears to use the result of the load. This is intended to catch cases such as `lbu t0, ...` immediately followed by `addu t2, t1, t0`. However the logic employed by the VR4300 is not precise, other instruction sequences that only appear to use the result of the load can also trigger a stall. The logic only checks for whether the instructions touch the same set of registers and whether the second instruction uses the preceding instruction's `rt` register in either of its `rs` or `rt` instruction fields, whether or not they are actually used as a source for that instruction. Load instructions that target GPRs only interlock with non-float instructions, load instructions that target FPRs only interlock with float instructions. Also note that the zero register for GPRs is exempt, a load into the zero register never triggers a load delay interlock. This catches a superset of the required cases. Examples of cases that are unnecessarily interlocked as the results are independent of the loaded value: * Two sequential loads into the same register will stall as the destination register for loads is encoded in the `rt` field, the logic interprets this as a dependency and stalls. * A `lui` instruction whose destination in `rt` or unused bits in `rs` match the load destination, either case counts as a dependency to the load interlock detection logic. Known Bugs ========== Multiplication Bug ------------------ Some VR4300 CPUs contain the “VR4300 multiplication bug”. This causes incorrect results to be generated, under certain circumstances, after computing a floating-point multiplication. The bug was fixed in later processor [steppings](https://en.wikipedia.org/wiki/Stepping_level) , and affects early model Nintendo 64 consoles generally NUS-01 (Japan Only), NUS-02 (Japan Only), NUS-03 (First US Revision). GCC accepts the `-mfix4300` flag, which tells GCC to generate code with a workaround for this bug—two `nop` instructions are inserted after every `mul.s (fp)`, `mul.d (fp)`, or `mult (integer)`. For example, consider this function: `float mul(float x, float y) { return x * y; }` Without the fix it may generate this code: jal mul nop mul.s $f1,$f13,$f15 mul: jr $31 mul.s $f0,$f12,$f14 The mul.s after the nop (red) may produce unexpected results, if the operands in the mul.s after the jr (yellow) include NaN, Zero or Infinity. With the fix it may generate this code: mul.s $f1,$f13,$f15 nop jal mul nop nop mul: mul.s $f0,$f12,$f14 jr $31 nop Depending on the other instructions that can be reordered the nops could be other instructions that perform work, so this is a worst case scenario. There may be a ROM which tests if your hardware is affected by this bug, but determining based on the Motherboard revision is easier. 32-bit Shift Right Arithmetic Bug --------------------------------- The `sra` and `srav` instructions do not work as the VR4300 processor manual describes. The processor manual claims that when an arithmetic right shift is performed the most significant bits of the lower 32 bits are filled with copies of bit 31, the 32-bit sign bit, and then bit 31 is sign-extended into the upper 32 bits of the register. In practice, the most significant bits are first filled with the bits from the upper 32 bits of the register, and then the new bit 31 is used for sign extension into the upper 32 bits of the register. This leaks 64-bit state that should always be inaccessible when executing this instruction, regardless of whether the processor is running in 32-bit or 64-bit mode. Since most 32-bit instructions will sign-extend into the upper 32 bits of the register, it is rare for this bug to cause visible effects if a program only uses 32-bit instructions. For example, consider executing sra with inputs 0x0123456789ABCDEF and 16. If we follow the manual, we would get 0xFFFFFFFFFFFF89AB by shifting right by 16 and populating the upper 48 bits with the original sign bit which is 1. In C, this would be `rd = (uint64_t)(int32_t)((int32_t)rt >> sa)`. On hardware, we instead get 0x00000000456789AB by shifting right by 16 and populating the upper 32 bits with the new sign bit which is now 0. In C, this would be `rd = (uint64_t)(int32_t)((int64_t)rt >> sa)`. This is considered a bug because it breaks the idea of ISA backwards compatibility. Earlier MIPS CPUs that were internally 32-bit would follow the calculation outlined in the processor manual simply because there is no 64-bit state to leak and the ALU was designed to do the correct 32-bit sign extension. In order to maintain compatibility with these earlier 32-bit only ISAs the same behavior would be expected regardless of whether the CPU has become 64-bit internally, however this is not the case hence it breaks strict compatibility. It is not known if this bug was ever fixed, it is present in more consoles than the multiplication bug. Sign extension bugs ------------------- 32-bit signed integer multiplication (`mult`) and division (`div`) do not work as expected when the input registers are not properly sign-extended 32-bit values. The expected result would be for the processor to sign-extend both inputs from 32-bit to 64-bit, filling the upper 32 bits with a copy of bit 31, however this does not happen. `mult` acts as a 64-bit by 35-bit signed multiplication, meaning the second operand is sign-extended on bit 34 before computing a 64-bit multiplication. `div` usually acts as a 32-bit by 35-bit signed division (the dividend is sign-extended on bit 31 while the divisor is sign-extended on bit 34 before computing a 64-bit division) except when bits 63 and 31 of the divisor are not equal. When bits 63 and 31 of the divisor are not equal, the quotient output to the LO register is not correct. It is currently unclear how the outputs of this last case are arrived at. The remainder output to the HI register is at least related to the inputs and the quotient in the expected way: `remainder = (int32_t)(dividend - quotient * divisor)`, where the operations are carried out as 64-bit. It's not clear if this should really be called a "bug", it's more like undefined behavior, however the results are highly counter-intuitive and leaks 64-bit processor state into the 32-bit operating environment. It is not known if this behavior is different on other processor revisions. These results are for processor revision 2.2 (see the **Revision Identifiers** section). Retrieved from "[https://n64brew.dev/wiki/VR4300?oldid=5733](https://n64brew.dev/wiki/VR4300?oldid=5733) " --- # Reality Signal Processor - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Signal_Processor#) Reality Signal Processor ======================== The **Reality Signal Processor**, or **RSP**, is the portion of the RCP responsible for matrix math, lighting calculations, clipping, shading, and other highly parallel graphics tasks as well as audio processing. It is a programmable MIPS processor with a custom set of SIMD instructions for vectorized fixed point operations (exposed as COP2 -- a group of reserved instructions in the standard MIPS instruction set). The RSP is also able to directly drive the RDP (the hardware rasterizer) by accessing its registers, so that it can terminate the graphic pipeline by telling the RDP to draw triangles into the framebuffer. RSP has two different banks of onboard dedicated memories: IMEM (4KB) for instructions, and DMEM (4KB) for data. It has no external memory buses but has a DMA engine capable to copy code/data from/into DMEM/IMEM and the main RDRAM. The DMA engine can be driven by either the main CPU or the RSP itself. The code running on the RSP is usually called "microcode", but it's a standard MIPS program. The RSP can be programmed in custom microcode to handle specific tasks, though most commercial games leveraged one of several stock microcodes made available by Nintendo at the time. Contents -------- * [1 Specs](https://n64brew.dev/wiki/Reality_Signal_Processor#Specs) * [2 RSP CPU core](https://n64brew.dev/wiki/Reality_Signal_Processor#RSP_CPU_core) * [3 RSP CPU pipeline](https://n64brew.dev/wiki/Reality_Signal_Processor#RSP_CPU_pipeline) * [4 RSP interface](https://n64brew.dev/wiki/Reality_Signal_Processor#RSP_interface) Specs ----- | | | | --- | --- | | | Discription | | CPU Type | Cut down version of the MIPS4000 CPU | | Clock Speed | 62.5mhz | | Instruction Size | 32bit (1 Word) | | Dual Instruction | Yes (one scalar and one vector opcode at once) | | Pipeline Stages | 5 stage pipeline for both the Scalar and Vector Pipelines

IF, RD and WB stages are shared between the two pipelines | | IMEM Data Path | 64bit (This allows a dual instruction to happen) This can only be double word aligned for reads | | Scalar Register Size | 32 entries of 32bit in size (Word Writable) | | Vector Register Size | 32 entries of 128bit is size (8bit to 128bit Mask Writable File) | | DMEM Scalar Data Path | Up to 32bit Loads and Stores | | DMEM Vector Data Path | Up to 128bit Loads and Stores | | Scalar ALU Size | 32bit in side only | | Vector ALU Size | 8x 16bit vector ALU pipelines (48bit Final Accumulator) | RSP CPU core ------------ Main article: [Reality Signal Processor/CPU Core](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core "Reality Signal Processor/CPU Core") The RSP CPU core is made by a stripped-down MIPS 32-bit core (without a few more advanced opcodes) referred to as Scalar Unit (SU), composed with a coprocessor (configured as COP2) that can perform SIMD operations on a separate set of vector registers, referred to as Vector Unit (VU). RSP CPU pipeline ---------------- Main article: [Reality Signal Processor/CPU Pipeline](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline "Reality Signal Processor/CPU Pipeline") The RSP CPU pipeline is made of two different units: SU and VU. It allows to run two instructions in a single clock cycle, when following a specific coding pattern. RSP interface ------------- Main article: [Reality Signal Processor/Interface](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface "Reality Signal Processor/Interface") The RSP interface is made of several memory-mapped registers and memory areas that allows the VR4300 to control the RSP. VR4300 is able to read and write to the internal IMEM/DMEM memory of the RSP to be able to upload the microcode to be run and fetch the results if required. Retrieved from "[https://n64brew.dev/wiki/Reality\_Signal\_Processor?oldid=4813](https://n64brew.dev/wiki/Reality_Signal_Processor?oldid=4813) " --- # Homebrew Projects - N64brew Wiki [](https://n64brew.dev/wiki/Homebrew_Projects#) Homebrew Projects ================= Show us your N64 projects! Know of any homebrew, flashcarts, accessories, software, or anything else made for the N64 that isn't listed below? If yes, please add it so that others can find it too! Contents -------- * [1 Games](https://n64brew.dev/wiki/Homebrew_Projects#Games) * [1.1 Unsorted](https://n64brew.dev/wiki/Homebrew_Projects#Unsorted) * [1.1.1 Historical](https://n64brew.dev/wiki/Homebrew_Projects#Historical) * [1.1.2 Open Source / In Development](https://n64brew.dev/wiki/Homebrew_Projects#Open_Source_/_In_Development) * [1.1.3 Emulators](https://n64brew.dev/wiki/Homebrew_Projects#Emulators) * [1.1.4 Ports](https://n64brew.dev/wiki/Homebrew_Projects#Ports) * [1.1.5 Other](https://n64brew.dev/wiki/Homebrew_Projects#Other) * [2 Demos](https://n64brew.dev/wiki/Homebrew_Projects#Demos) * [3 Tools](https://n64brew.dev/wiki/Homebrew_Projects#Tools) * [4 Hardware](https://n64brew.dev/wiki/Homebrew_Projects#Hardware) Games ----- | Title | Description | Author(s) | Date | Category | Multiplayer | Screenshot | Download | Source | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 2048 64 | Reproduction of the phone/web game | Gabrielle Cirulli | 2021 | Puzzle | No | [](https://n64brew.dev/wiki/File:2048.png) | [https://itch.io/queue/c/1920454/n64-homebrew?game\_id=859060](https://itch.io/queue/c/1920454/n64-homebrew?game_id=859060) | | | Super Snooper Spookers | Entry in the [N64brew Game Jam 2022](https://n64brew.dev/wiki/N64brew_Game_Jam_2022 "N64brew Game Jam 2022")

2nd Place | 0xB0C5 | 2022 | Arcade | | [](https://n64brew.dev/wiki/File:Super_snooper_spookers.png) | [2022 Google Drive](https://drive.google.com/drive/folders/1CgUezxQI-ioCpGxY9jyCCYmTpgXDdDBJ) | [2022 Github](https://github.com/N64brew-Game-Jam-2022) | | Dead Ritual | Entry in the [N64brew Game Jam 2022](https://n64brew.dev/wiki/N64brew_Game_Jam_2022 "N64brew Game Jam 2022") | matthewcpp | 2022 | First Person Shooter | | [](https://n64brew.dev/wiki/File:Deadritual.png) | [2022 Google Drive](https://drive.google.com/drive/folders/1CgUezxQI-ioCpGxY9jyCCYmTpgXDdDBJ) | [2022 Github](https://github.com/N64brew-Game-Jam-2022) | | Spirit Harvest | Entry in the [N64brew Game Jam 2022](https://n64brew.dev/wiki/N64brew_Game_Jam_2022 "N64brew Game Jam 2022")

3rd Place | WadeMalone et al | 2022 | Adventure | No | [](https://n64brew.dev/wiki/File:Spirit_harvest.png) | [2022 Google Drive](https://drive.google.com/drive/folders/1CgUezxQI-ioCpGxY9jyCCYmTpgXDdDBJ) | [2022 Github](https://github.com/N64brew-Game-Jam-2022) | | Nightlight Vampire / Fangers Nightmare | Entry in the [N64brew Game Jam 2022](https://n64brew.dev/wiki/N64brew_Game_Jam_2022 "N64brew Game Jam 2022") | adamcate | 2022 | Arcade | No | [](https://n64brew.dev/wiki/File:Vampire.png) | [2022 Google Drive](https://drive.google.com/drive/folders/1CgUezxQI-ioCpGxY9jyCCYmTpgXDdDBJ) | [2022 Github](https://github.com/N64brew-Game-Jam-2022) | | Slime Heist | Entry in the [N64brew Game Jam 2022](https://n64brew.dev/wiki/N64brew_Game_Jam_2022 "N64brew Game Jam 2022") | Yoshimaster96 | 2022 | | | | [2022 Google Drive](https://drive.google.com/drive/folders/1CgUezxQI-ioCpGxY9jyCCYmTpgXDdDBJ) | [2022 Github](https://github.com/N64brew-Game-Jam-2022) | | Styx | Entry in the [N64brew Game Jam 2022](https://n64brew.dev/wiki/N64brew_Game_Jam_2022 "N64brew Game Jam 2022")

First Place | Ultrarare | 2022 | Arcade | | [](https://n64brew.dev/wiki/File:Styx.png) | [2022 Google Drive](https://drive.google.com/drive/folders/1CgUezxQI-ioCpGxY9jyCCYmTpgXDdDBJ) | [2022 Github](https://github.com/N64brew-Game-Jam-2022) | | Wizard of the Board | Entry in the [N64brew Game Jam 2021](https://n64brew.dev/wiki/N64brew_Game_Jam_2021 "N64brew Game Jam 2021")

First Place | danbolt | 2021 | Puzzle | No | [](https://n64brew.dev/wiki/File:Wotb.png) | [2021 Google Drive](https://drive.google.com/drive/folders/1U_6317lJdqZCzJdE47csRvR-KneHpMhj) | [2021 Github](https://github.com/N64brew-Game-Jam-2021) | | Voidblade | Entry in the [N64brew Game Jam 2021](https://n64brew.dev/wiki/N64brew_Game_Jam_2021 "N64brew Game Jam 2021")

3rd Place | anacierdem | 2021 | Arcade | No | [](https://n64brew.dev/wiki/File:Voidblade.png) | [2021 Google Drive](https://drive.google.com/drive/folders/1U_6317lJdqZCzJdE47csRvR-KneHpMhj) | [2021 Github](https://github.com/N64brew-Game-Jam-2021) | | Tandem Trouble | Entry in the [N64brew Game Jam 2021](https://n64brew.dev/wiki/N64brew_Game_Jam_2021 "N64brew Game Jam 2021") | matthewcpp | 2021 | | | | [2021 Google Drive](https://drive.google.com/drive/folders/1U_6317lJdqZCzJdE47csRvR-KneHpMhj) | [2021 Github](https://github.com/N64brew-Game-Jam-2021) | | Fission Failure | Entry in the [N64brew Game Jam 2021](https://n64brew.dev/wiki/N64brew_Game_Jam_2021 "N64brew Game Jam 2021") | VRGL117 Games | 2021 | Puzzle | No | [](https://n64brew.dev/wiki/File:Fission.png) | [2021 Google Drive](https://drive.google.com/drive/folders/1U_6317lJdqZCzJdE47csRvR-KneHpMhj) | [2021 Github](https://github.com/N64brew-Game-Jam-2021) | | Mission Lost Control | Entry in the [N64brew Game Jam 2021](https://n64brew.dev/wiki/N64brew_Game_Jam_2021 "N64brew Game Jam 2021")
2nd Place | Ultrarare | 2021 | Strategy | Yes | [](https://n64brew.dev/wiki/File:MISSION.png) | [2021 Google Drive](https://drive.google.com/drive/folders/1U_6317lJdqZCzJdE47csRvR-KneHpMhj) | [2021 Github](https://github.com/N64brew-Game-Jam-2021) | | Power Struggle | Entry in the [N64brew Game Jam 2021](https://n64brew.dev/wiki/N64brew_Game_Jam_2021 "N64brew Game Jam 2021") | TheShaders | 2021 | | | | [2021 Google Drive](https://drive.google.com/drive/folders/1U_6317lJdqZCzJdE47csRvR-KneHpMhj) | [2021 Github](https://github.com/N64brew-Game-Jam-2021) | | [64noid](https://github.com/N64brew-Game-Jam-2020/64noid) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | gamemasterplc | 2020 | | | | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Big Burger](https://github.com/N64brew-Game-Jam-2020/Big-Burger) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | Allie | 2020 | Rhythm | No | [](https://n64brew.dev/wiki/File:Burger.png) | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Castle64](https://github.com/N64brew-Game-Jam-2020/Castle64) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | manfried | 2020 | Adventure | No | [](https://n64brew.dev/wiki/File:Castle64.png) | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [JUIC'N 64](https://github.com/N64brew-Game-Jam-2020/Juicn-64) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | kivan117 | 2020 | | | | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Kumi-Daiko Beatoff 64](https://github.com/N64brew-Game-Jam-2020/Kumi-Daiko-Beatoff-64)
([itch.io](https://zhamul.itch.io/kumi-daiko-beatoff-64)
) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020")

2nd Place (Tie) | Samuli Jääskeläinen ([Zhamul](https://twitter.com/Zhamul)
), Petteri Timonen, Pyry Takkunen ([DevinTappy](https://twitter.com/DevinTappy)
), and RufioV | 2020 | Physics-based combat | Yes | [](https://n64brew.dev/wiki/File:Kumi-daiko-beatoff-64.png) | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Lunar Assault 64](https://n64brew.dev/wiki/Lunar_Assault_64 "Lunar Assault 64")
([itch.io](https://danbolt.itch.io/lunar-assault-64)
) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | danielface | 2020 | Monster hunter | No | [](https://n64brew.dev/wiki/File:Lunar_Assault_64_Screenshot_4.png) | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Retro Dash](https://github.com/N64brew-Game-Jam-2020/Retro-Dash) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | SpiritOf1776 | 2020 | | | | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Sblobber64](https://github.com/N64brew-Game-Jam-2020/Sblobber64) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020")

2nd Place (Tie) | VRGL117 Games | 2020 | 2D Puzzle | No | [](https://n64brew.dev/wiki/File:Slobber64.png) | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Shrunk in the Wash? Just add water!](https://github.com/N64brew-Game-Jam-2020/Just-Add-Water) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | joeldipops | 2020 | | | | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Tecto](https://github.com/N64brew-Game-Jam-2020/Tecto) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | InTheBeef and Wiseguy | 2020 | | | | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Telocation](https://github.com/N64brew-Game-Jam-2020/Telocation)
([itch.io](https://jtn191.itch.io/telocation-gemini)
) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020")

1st Place | lambertjamesd, jtn191, and Cobra | 2020 | Puzzle/Platformer | Yes | [](https://n64brew.dev/wiki/File:Telocation-gemini.png) | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [The Swoop 64](https://github.com/N64brew-Game-Jam-2020/The-Swoop-64) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | arookas, Catonator, miluaces, and Zest | 2020 | | | | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | | [Thornmarked](https://github.com/N64brew-Game-Jam-2020/thornmarked) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | Dietrich Epp and Alastair Low | 2020 | | | | [2020 Google Drive](https://drive.google.com/drive/folders/1CNCA_4966stG4mpkGCZjMSuZ-C7r8cqC) | [2020 Github](https://github.com/N64brew-Game-Jam-2020) | ### Unsorted These is a list of assorted games which still need to be processed into the above table. #### Historical * 1998 Presence Of Mind Demo Competition Entries: [https://web.archive.org/web/19980613235114/http://www.dextrose.com/pom.htm](https://web.archive.org/web/19980613235114/http://www.dextrose.com/pom.htm) * 1999 Presence Of Mind Demo Competition Entries: [https://www.pouet.net/party.php?which=403&when=1999](https://www.pouet.net/party.php?which=403&when=1999) * Penguins Luv Melons game+Source [https://n64squid.com/files/penguins-luv-melons/](https://n64squid.com/files/penguins-luv-melons/) * Twintris [https://n64squid.com/simon-64/](https://n64squid.com/simon-64/) * Tetris Demo Beta [https://n64squid.com/tetris-demo-beta/](https://n64squid.com/tetris-demo-beta/) * Dexanoid [https://n64squid.com/dexanoid/](https://n64squid.com/dexanoid/) #### Open Source / In Development * [https://github.com/allie/brick64](https://github.com/allie/brick64) * [https://github.com/jsdf/goose64](https://github.com/jsdf/goose64) * [https://github.com/lambertjamesd/portal64](https://github.com/lambertjamesd/portal64) * [https://github.com/einhov/shibamatch](https://github.com/einhov/shibamatch) * [https://github.com/murachue/ochim](https://github.com/murachue/ochim) * [https://github.com/jsdf/n64-gameoflife](https://github.com/jsdf/n64-gameoflife) * [https://github.com/1r3n33/paniclab64](https://github.com/1r3n33/paniclab64) * [https://github.com/jnmartin84/aw64](https://github.com/jnmartin84/aw64) * [https://github.com/vieux/Memory64-N64](https://github.com/vieux/Memory64-N64) * [https://github.com/meeq/FlappyBird-N64](https://github.com/meeq/FlappyBird-N64) * [https://github.com/gameblabla/evilaustralians](https://github.com/gameblabla/evilaustralians) #### Emulators * [https://github.com/lambertjamesd/gb64](https://github.com/lambertjamesd/gb64) * [https://github.com/hcs64/neon64v2](https://github.com/hcs64/neon64v2) * [https://github.com/Hydr8gon/sodium64](https://github.com/Hydr8gon/sodium64) * [https://github.com/joshiggins/chip8-n64](https://github.com/joshiggins/chip8-n64) * [https://github.com/rasky/mvs64](https://github.com/rasky/mvs64) * [https://github.com/Dillonb/n64-gba](https://github.com/Dillonb/n64-gba) #### Ports * Commander Keen: [https://github.com/Ryzee119/Omnispeak64](https://github.com/Ryzee119/Omnispeak64) * 64 Doom: [https://github.com/jnmartin84/64doom](https://github.com/jnmartin84/64doom) * Tyrian: [https://github.com/jnmartin84/opentyrian](https://github.com/jnmartin84/opentyrian) * Raptor: [https://github.com/RetroGamer02/raptor-consoles/tree/bswap-sys](https://github.com/RetroGamer02/raptor-consoles/tree/bswap-sys) #### Other * N64FlashcartMenu: [https://github.com/Polprzewodnikowy/N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu) Demos ----- | Title | Description | Author(s) | Date | | --- | --- | --- | --- | | [MGC2011 Entry](https://www.pouet.net/prod.php?which=56794) | 3D demo written for the Midwest Gaming Classic 2011 | Marshal H. ([Retroactive](http://retroactive.be/)
) | 2011 | | [Nacho64](https://www.pouet.net/prod.php?which=61915) | Winner of the Presence of Mind 1999 homebrew compo | SPLiT | 1999 | | [Test 3rd Person](https://github.com/N64brew-Game-Jam-2020/Test-3rd-Person-Demo) | Entry in the [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") | WadeMalone and Geist | 2020 | Tools ----- | Title | Description | Author(s) | Date | | --- | --- | --- | --- | Hardware -------- | Title | Description | Author(s) | Latest Version | | --- | --- | --- | --- | | [Sanni Cart Reader](https://github.com/sanni/cartreader) | Arduino Mega shield | sanni | v7.3 released Nov 29, 2021 | | [SummerCart64](https://github.com/Polprzewodnikowy/SummerCart64) | Open Source/Hardware N64 "Flash" Cartridge with fast USB and 64DD emulation | Polprzewodnikowy | v2.16.0 released Jun 30, 2023 | | [PicoCart64](https://github.com/kbeckmann/PicoCart64) | Open Source/Hardware N64 "Flash" Cartridge using off the shelf Raspberry Pi Foundation Pico Microcontrollers and a simple PCB aimed at homebrew games rather than running retail roms | kbeckmann | in development | | [Dreamdrive64](https://github.com/khill25/Dreamdrive64) | Open Source/Hardware N64 "Flash" Cartridge using off the shelf Raspberry Pi Foundation Pico Microcontrollers and a simple PCB, aimed at homebrew games rather than running retail roms forked from PicoCart64, can be purchased and is supplied with 16MB of Flash for larger homebrew cartridges | khill25 | [v1 lite](https://dreamcraftindustries.com/products/picocart64-v1-lite) | | [N64 GameShark Clone](https://github.com/RWeick/REF1329-N64-Gameshark-Clone) | Open Source/Hardware N64 GameShark Clone mirroring all original functionality to include the parallel port, 7 segment display, and GS button. | RWeick | v1 | Retrieved from "[https://n64brew.dev/wiki/Homebrew\_Projects?oldid=5788](https://n64brew.dev/wiki/Homebrew_Projects?oldid=5788) " --- # Reality Display Processor - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Display_Processor#) Reality Display Processor ========================= The **Reality Display Processor**, or **RDP**, is the portion of the [RCP](https://n64brew.dev/wiki/RCP "RCP") responsible for graphics tasks such as Z-buffering, texturing, blending, anti-aliasing, etc. It contains 4KB of texture memory (TMEM,) and is sent commands called "primitives". Contents -------- * [1 RDP interface](https://n64brew.dev/wiki/Reality_Display_Processor#RDP_interface) * [2 RDP commands](https://n64brew.dev/wiki/Reality_Display_Processor#RDP_commands) * [3 RDP pipeline](https://n64brew.dev/wiki/Reality_Display_Processor#RDP_pipeline) * [4 RDP hazards](https://n64brew.dev/wiki/Reality_Display_Processor#RDP_hazards) RDP interface ------------- Main article: [Reality Display Processor/Interface](https://n64brew.dev/wiki/Reality_Display_Processor/Interface "Reality Display Processor/Interface") The RDP interface is made of several memory-mapped registers that allows the VR4300 and RSP to control the RDP. Both VR4300 and RSP are in fact able to control RDP execution by accessing the interface: VR4300 through memory mapped registers, while RSP through its COP0. RDP commands ------------ Main article: [Reality Display Processor/Commands](https://n64brew.dev/wiki/Reality_Display_Processor/Commands "Reality Display Processor/Commands") The RDP processes a fixed-function command list comprised of primitive rendering, attribute setting, texture loading, and synchronization commands. Most commands are 64-bit words, though some are bigger. Command lists can be sent to RDP via DMA, either from RDRAM or DMEM. RDP pipeline ------------ Main article: [Reality Display Processor/Pipeline](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline "Reality Display Processor/Pipeline") The RDP is a pipelined processor with configurable pipeline modes. It is up to the user to select the appropriate operating mode and provide adequate synchronization in submitted command lists. RDP hazards ----------- Main article: [Reality Display Processor/Hazards](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards "Reality Display Processor/Hazards") In general, RDP programming is subject to many hazards due to the complex pipeline with user-provided synchronizations and invalid or incoherent render modes that can be setup. The effects can vary from rendering glitches to full RDP crashes that require a hard reset. Retrieved from "[https://n64brew.dev/wiki/Reality\_Display\_Processor?oldid=5675](https://n64brew.dev/wiki/Reality_Display_Processor?oldid=5675) " --- # Audio DAC - N64brew Wiki [](https://n64brew.dev/wiki/Audio_DAC#) Audio DAC ========= In earlier versions of the mainboard, the Audio DAC is a Rohm BU9480F interpolating [I²S](https://en.wikipedia.org/wiki/I%C2%B2S "wikipedia:I²S") DAC. It takes in three digital signals: data, bit clock, and word clock, and emits stereo audio that has been up-sampled by a factor of 2 (using the word clock to choose between "the previous sample" and "the 50% weighted interpolated sample"). In later revisions of the mainboard it's been integrated into the same package as the Video DAC in the AVDC-NUS or MAV-NUS. Either way, the RCP's [Audio Interface](https://n64brew.dev/wiki/Audio_Interface "Audio Interface") is responsible for providing data to it. Retrieved from "[https://n64brew.dev/wiki/Audio\_DAC?oldid=5535](https://n64brew.dev/wiki/Audio_DAC?oldid=5535) " --- # PIF-NUS - N64brew Wiki [](https://n64brew.dev/wiki/PIF-NUS#) 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](https://n64brew.dev/wiki/Sharp_SM5_Microcontroller "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](https://n64brew.dev/wiki/Peripheral_Interface "Peripheral Interface") is used to read/write to the game ROM and devices like the [64DD](https://n64brew.dev/wiki/64DD "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 Contents -------- * [1 Pinout](https://n64brew.dev/wiki/PIF-NUS#Pinout) * [2 Internal ROMs and RAM](https://n64brew.dev/wiki/PIF-NUS#Internal_ROMs_and_RAM) * [3 RAM-based communication protocol](https://n64brew.dev/wiki/PIF-NUS#RAM-based_communication_protocol) * [4 Joybus frame (controller and EEPROM communication)](https://n64brew.dev/wiki/PIF-NUS#Joybus_frame_(controller_and_EEPROM_communication)) * [4.1 Frame parsing and handshakes](https://n64brew.dev/wiki/PIF-NUS#Frame_parsing_and_handshakes) * [4.2 Joybus handhakes](https://n64brew.dev/wiki/PIF-NUS#Joybus_handhakes) * [4.2.1 TX byte: special flags](https://n64brew.dev/wiki/PIF-NUS#TX_byte:_special_flags) * [4.2.2 RX byte: special flags](https://n64brew.dev/wiki/PIF-NUS#RX_byte:_special_flags) * [4.2.3 Flag bits in PIF command](https://n64brew.dev/wiki/PIF-NUS#Flag_bits_in_PIF_command) * [4.3 Escape codes](https://n64brew.dev/wiki/PIF-NUS#Escape_codes) * [5 Console startup](https://n64brew.dev/wiki/PIF-NUS#Console_startup) * [5.1 IPL2 checksum algorithm](https://n64brew.dev/wiki/PIF-NUS#IPL2_checksum_algorithm) * [5.2 IPL3 checksum algorithm](https://n64brew.dev/wiki/PIF-NUS#IPL3_checksum_algorithm) * [6 Console Reset](https://n64brew.dev/wiki/PIF-NUS#Console_Reset) Pinout ------ | | | | --- | --- | | | **Notice**

This section requires more research. Pin names and descriptions may be inaccurate. |  [](https://n64brew.dev/wiki/File:PIF_decap_pins_labeled.png) PIF Decapped with Pins numbered | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- |PIF Pinout (28 Pin SOP Package) | | 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](https://n64brew.dev/wiki/CIC-NUS "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](https://n64brew.dev/wiki/CIC-NUS "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](https://n64brew.dev/wiki/Serial_Interface "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](https://n64brew.dev/wiki/Serial_Interface "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](https://github.com/GenericHeroGuy/pif-sm5-rom/) contains a commented disassembly and a decompiled C code for it. The [jago85/UltraPIF\_MCU](https://github.com/jago85/UltraPIF_MCU) project 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](https://n64brew.dev/wiki/Initial_Program_Load "Initial Program Load") ) 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](https://github.com/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](https://n64brew.dev/wiki/Joybus_Protocol "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](https://github.com/GenericHeroGuy/pif-sm5-rom/) 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. The meaning of the bits are different during PIF reset mode (during boot, or after the RESET button is pressed) or during normal run: | | | | | | | --- | --- | --- | --- | --- |Description of commands: PIF in reset mode | Bit | Command | Description | Arguments | Results | | 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](https://github.com/DragonMinded/libdragon/blob/0efbe60fd7bb04065c4603de02b74738bc9d605a/src/entrypoint.S#L37-L38)
).

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](https://n64brew.dev/wiki/PIF-NUS#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](https://n64brew.dev/wiki/PIF-NUS#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 | | | | | | | | --- | --- | --- | --- | --- |Description of commands: PIF in run mode | 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 + 0x08 | Joybus flag bits | These bits are used as "flag bits" for Joybus frames. In other words, these bits do not represent a new command, but affect how joybus transactions are executed.

See [Joybus frame (controller and EEPROM communication)](https://n64brew.dev/wiki/Joybus_frame_(controller_and_EEPROM_communication)?action=edit&redlink=1 "Joybus frame (controller and EEPROM communication) (page does not exist)")
for more information. | 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](https://github.com/GenericHeroGuy/pif-sm5-rom/blob/69dfe40baeb806271e55a3bb69733ce08b1390c3/cmodel.c#L772-L795) 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 is `0x01 - 0x3F`, and the top 2 bits are ignored) * `RX` is the number of bytes to received from the device (valid range is `0x00 - 0x3F`, and the top 2 bits are ignored) * `tt` is the data to transmit (must be exactly `TX` bytes) * `rr` is the space where received data will be written (must be exactly `RX` 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](https://n64brew.dev/wiki/Joybus_Protocol "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](https://n64brew.dev/wiki/Joybus_Protocol#Command_List "Joybus Protocol") 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 device is reset (using the same reset functionality performed by the escape code 0xFD, see below). | #### 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, or when the command is not otherwise known/understood by the device. | #### Flag bits in PIF command During the execution phase, PIF also checks bits 0x02 and 0x03 of the PIF command byte (offset 63 in RAM). These bits allow to tweak how the PIF behaves after sending the TX bytes, before starting receive the RX bytes. | | | | | | --- | --- | --- | --- |Flag bits in PIF command byte | Bit 2 | Bit 3 | Delay between TX and RX | Description | | 0 | 0 | ~7 µs | Send the stop bit, then proceed receiving bytes | | 0 | 1 | ~7 µs | Send the stop bit, then proceed receiving bytes | | 1 | 0 | ~520 µs | Send the stop bit, wait, and then proceed receiving bytes | | 1 | 1 | ~520 µs | Wait, then proceed receiving bytes | Normally, those bits will be set to 0, so the standard joybus protocol will be executed (with the stop bit). By tweaking those bits, you can add an explicit wait loop, or even disable the stop bit from being sent. Notice that these flag bits affect all the channels in the PIF frame, it is not possible to configure different behavior per each channel. ### 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 instructs the PIF to emit a special reset signal on the line (the line is pulled down for a 1ms). The exact behavior of the various devices after receiving this signal is currently unknown. | | 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 --------------- 1. PIF and VR4300 boot at power on. 2. VR4300 starts running code from address `0xBFC0 0000` which is mapped to PIF ROM via SI interface. 3. PIF starts communicating with the [CIC](https://n64brew.dev/wiki/CIC-NUS "CIC-NUS") inside the cartridge 1. CIC sends 1 nibble (4-bits): region identifier (0x1 = NTSC, 0x5 = PAL) 2. 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 3. CIC sends a 6 byte checksum (again, slightly obfuscated). This is the expected result for the [IPL2 checksum algorithm](https://n64brew.dev/wiki/PIF-NUS#IPL2_checksum_algorithm) (see below). 4. 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. 1. If the values don't match, the PIF stops the boot by freezing the CPU (halting it via the NMI line) 5. PIF writes several booting information (including the two seeds) to the PIF-RAM word at offset `0x24-0x27` (mapped at `0xBFC0 07E4`), so that the CPU can later access them. 6. PIF writes bit 0x80 in the command byte to signal VR4300 that the data is now available in PIF-RAM. 7. Meanwhile, the VR3000 is executing the IPL1 code directly fetching opcodes from PIF-ROM. 1. These instructions are executed in this very slow manner. Thankfully IPL1 is only 52 instructions + some looping. 2. It performs some really basic hardware initialization. 3. 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. 4. Jump to RSP IMEM to execute IPL2 8. IPL2 is executed by the VR4300 reading the instructions from RSP IMEM 1. More general hardware initialization 2. 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. 3. If the booting information says that it is a 64DD disk, it will jump to `0xA600 0000` 4. Send command 0x10 to PIF, and PIF disables access to PIF-ROM (IPL1). 5. Load IPL3 from the [cartridge ROM](https://n64brew.dev/wiki/ROM_Header "ROM Header") (offset 0x40-0x1000) into the RSP DMEM 6. Run the [IPL2 checksum algorithm](https://n64brew.dev/wiki/PIF-NUS#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. 7. 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. 8. Jump to RSP DMEM to execute IPL3. 9. IPL3 is executed by the VR4300 reading the instructions from the RSP DMEM. 1. Initialize RDRAM 2. Depending on reset type 1. Power On: Invalidate VR4300 ICache & DCache 2. Reset : Writeback VR4300 ICache & DCache 3. 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. 4. 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. 5. Run the [IPL3 checksum algorithm](https://n64brew.dev/wiki/PIF-NUS#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. 6. The 8-byte checksum is compared against the checksum stored at offset 0x10 in the ROM (part of the [ROM Header](https://n64brew.dev/wiki/ROM_Header "ROM Header") ). If it doesn't match, VR4300 halts itself. 7. Reset RSP 8. Clear Interrupts 9. Clear IPL3 from DMEM 10. Clear IPL2 from IMEM 11. Jump to Game code in RDRAM. The initial PC is stored at offset 0x08 in the ROM (part of the [ROM Header](https://n64brew.dev/wiki/ROM_Header "ROM Header") ), though in some IPL3 variants it is slightly descrambled first. 10. 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. 1. The PIF (on console) and [CIC](https://n64brew.dev/wiki/CIC-NUS "CIC-NUS") (on cartridge) begins doing a communication protocol which follows the challenge/response authentication pattern. 2. 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](https://github.com/jago85/PifChecksum/blob/master/PifChecksum.c) 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](https://n64brew.dev/wiki/ROM_Header "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](http://n64dev.org/n64crc.html) . 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](http://n64dev.org/n64crc.html) ) 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: 1. User presses Console Reset button 2. PIF receives an interrupt signaling that the button was pressed 3. PIF toggles VR4300 Interrupt 2 (INT2) also known as "pre-NMI". 1. 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. 4. PIF sends the RESET command to [CIC](https://n64brew.dev/wiki/CIC-NUS "CIC-NUS") (command `0b11`) 5. CIC waits for 500ms (grace time) 6. CIC acknowledges the RESET command to PIF by writing a 0 bit. 7. PIF waits (indefinitely) until the reset button is released. 8. PIF toggles VR4300 Non-Maskable Interrupt (NMI) which resets it. 1. PIF also unlocks the internal PIF ROM so that the boot process can start executing [IPL1](https://n64brew.dev/wiki/Initial_Program_Load "Initial Program Load") . Retrieved from "[https://n64brew.dev/wiki/PIF-NUS?oldid=5759](https://n64brew.dev/wiki/PIF-NUS?oldid=5759) " --- # Video DAC - N64brew Wiki [](https://n64brew.dev/wiki/Video_DAC#) Video DAC ========= The **Video DAC** is a chip on the N64 motherboard that converts the 7-bit-wide synchronous [Video Interface](https://n64brew.dev/wiki/Video_Interface "Video Interface") output to analog video. Early revisions pair a discrete Video DAC (VDC-NUS or VDC-NUS A) with an external encoder (ENC-NUS or S-RGB A) to generate video output.[\[1\]](https://n64brew.dev/wiki/Video_DAC#cite_note-1) Other revisions consolidate these functions into a single integrated chip (DENC-NUS, AVDC-NUS, MAV-NUS).[\[2\]](https://n64brew.dev/wiki/Video_DAC#cite_note-2) Contents -------- * [1 Bus Operation](https://n64brew.dev/wiki/Video_DAC#Bus_Operation) * [2 Precision](https://n64brew.dev/wiki/Video_DAC#Precision) * [3 Video Clock (VCLK)](https://n64brew.dev/wiki/Video_DAC#Video_Clock_(VCLK)) * [4 Hardware Variants](https://n64brew.dev/wiki/Video_DAC#Hardware_Variants) * [5 References](https://n64brew.dev/wiki/Video_DAC#References) * [6 Footnotes](https://n64brew.dev/wiki/Video_DAC#Footnotes) Bus Operation ------------- The Video DAC operates at four times the pixel clock frequency. Four consecutive VI clock cycles are multiplexed over the VI bus to form one 21-bit color value per emitted pixel. * 1 pixel = 4 VI clocks * 7 bits × 3 channels = 21-bit RGB  [](https://n64brew.dev/wiki/File:N64videosys.png) Video DAC bus and waveform diagram. Image from: [Tim Worthington - RGB Video DAC for Nintendo 64](https://web.archive.org/web/20090103080549/http://members.optusnet.com.au/eviltim/n64rgb/n64rgb.html) The 4-cycle sequence carries both pixel data and control signals and restarts whenever !DSYNC[\[3\]](https://n64brew.dev/wiki/Video_DAC#cite_note-3) is low. The Video Interface transmits control signals every clock during blanking by holding !DSYNC low across multiple VI clock cycles. | | Cycle 0 | Cycle 1 | Cycle 2 | Cycle 3 | | --- | --- | --- | --- | --- | | !DSYNC | **Low** | **High** | **High** | **High** | | D0 | !Csync | Red 0 | Green 0 | Blue 0 | | D1 | !Hsync | Red 1 | Green 1 | Blue 1 | | D2 | !Clamp[\[4\]](https://n64brew.dev/wiki/Video_DAC#cite_note-4) | Red 2 | Green 2 | Blue 2 | | D3 | !Vsync | Red 3 | Green 3 | Blue 3 | | D4 | — | Red 4 | Green 4 | Blue 4 | | D5 | — | Red 5 | Green 5 | Blue 5 | | D6 | — | Red 6 | Green 6 | Blue 6 | Precision --------- The Video DAC outputs 21-bit RGB color. Each pixel period transmits 28 bits (7 bits × 4 cycles), of which 21 bits are used for RGB data. Four of the remaining bits are used for control signaling during blanking; three bits are unused.[\[5\]](https://n64brew.dev/wiki/Video_DAC#cite_note-5) Video Clock (VCLK) ------------------ The Video DAC and encoder are driven by two clocks, each derived from the X1 crystal resonator by the synthesizer (Macronix MX8330MC / MX9911MC / MX8350, depending on board revision): the video clock (VCLK)[\[6\]](https://n64brew.dev/wiki/Video_DAC#cite_note-6) and the color subcarrier (FSC). The video clock frequency is calculated as: V C L K \= X 1 × Multiplier 5 {\\displaystyle {\\displaystyle VCLK={\\frac {X1\\times {\\text{Multiplier}}}{5}}}}   The multiplier is selected by the FSEL (NTSC/!PAL)[\[7\]](https://n64brew.dev/wiki/Video_DAC#cite_note-7) pin: High → 17×, Low → 14×. There are three video clock configurations corresponding to NTSC, PAL, and MPAL (a.k.a. [PAL-M](https://en.wikipedia.org/wiki/PAL-M) ): | Standard | FSC (MHz) | Crystal (X1) (MHz) | FSEL | Multiplier | VCLK (MHz) | | --- | --- | --- | --- | --- | --- | | **NTSC** | 3.57954545 | 14.31818182 | High | 17 | 48.68181818 | | **PAL** | 4.43361875 | 17.734475 | Low | 14 | 49.65653 | | **MPAL** | 3.57561189 | 14.30244755 | High | 17 | 48.62832167 | The encoder receives the FSC clock (X1 ÷ 4) via the subcarrier input (SCIN) pin in order to provide the correct colorburst frequency for the corresponding broadcast standard. Crystal frequency tolerance is ±30 ppm at 25°C.[\[8\]](https://n64brew.dev/wiki/Video_DAC#cite_note-8) This error propagates to all derived clocks (see [Clock Timing](https://n64brew.dev/wiki/Clock_Timing "Clock Timing") ). Hardware Variants ----------------- | Video output path | Board revision | Region | Role | Signal encoding | Notes | | --- | --- | --- | --- | --- | --- | | **VDC-NUS (A) + ENC-NUS** | NUS-CPU-01 thru 04; NUS-CPU(M)-01 thru 02 | NTSC, MPAL | Two-stage video DAC + encoder | composite + S-Video | VDC-NUS outputs RGB which is converted to composite + S-Video by ENC-NUS; VDC-NUS A revision changeover aligns with 1996/1997 boundary (alongside CPU-NUS A)[\[9\]](https://n64brew.dev/wiki/Video_DAC#cite_note-9) | | **VDC-NUS (A) + S-RGB A** | NUS-CPU(R)-01 | PAL (NUS-001(FRA) only) | Two-stage video DAC + encoder | composite + RGB[\[10\]](https://n64brew.dev/wiki/Video_DAC#cite_note-10) | RGB output not functional without modification; does not encode S-Video | | **DENC-NUS** | NUS-CPU(P)-01 | PAL (except NUS-001(FRA)) | One-stage integrated video DAC + encoder | composite + S-Video | Combines DAC and encoder functions in a single IC; converts to composite + S-Video internally (no external RGB signal) | | **AVDC-NUS** | Early NUS-CPU-05 | NTSC | One-stage integrated video DAC + encoder + audio DAC | composite + S-Video | Present on earlier NUS-CPU-05 examples | | **MAV-NUS** | Later NUS-CPU-05 and all later revisions | All regions | One-stage integrated video DAC + encoder + audio DAC | composite + S-Video | Pin-compatible with AVDC-NUS and used as drop-in replacement; present on later NUS-CPU-05 examples and all later revisions in all regions | References ---------- * [Tim Worthington - N64 RGB Mod](https://web.archive.org/web/20090103080549/http://members.optusnet.com.au/eviltim/n64rgb/n64rgb.html) (archived from [the original](http://members.optusnet.com.au/eviltim/n64rgb/n64rgb.html) ) * [Console5 Tech Wiki - RDC - NUS-CPU-03/04 Schematic](https://wiki.console5.com/wiki/File:N64_NUS-CPU-03-04.pdf) * [Datasheet Archive - Rohm BA7242F (ENC-NUS) datasheet (mirror)](https://www.datasheetarchive.com/datasheet/BA7242F/ROHM?term=BA7242F) * [KDS - AT-38/AT-49 Miniature Crystal Resonators datasheet (2011)](https://www.kds.info/wp-content/uploads/2015/11/2011-2012_032_en.pdf) * [Google Patents - US6956621B2, _High impedance digital full line video clamp_](https://patents.google.com/patent/US6956621B2/) * [modretro.com - Link83 et al - Nintendo 64 Motherboard Revisions/Serials Info Request](https://web.archive.org/web/20151105192150/http://forums.modretro.com/viewtopic.php?f=33&t=1417) (archived from [the original](http://forums.modretro.com/viewtopic.php?f=33&t=1417) ) * [consoles4you.ch - N64 S-Video Restore Install Guide](https://consoles4you.ch/guides/n64svideorestore.html) * [nfggames.com - Shadow\_Zero et al - Fix S-Video for PAL N64 NUS-CPU(P)-03-1](https://web.archive.org/web/20170107153330/http://nfggames.com/forum2/index.php?topic=5251.0) (archived from [the original](https://nfggames.com/forum2/index.php?topic=5251.0) ) * [nfggames.com - kwyjibo, Link83 et al - N64 RGB "Mod" on NUS-001(FRA)](https://web.archive.org/web/20140729192310/http://nfggames.com/forum2/index.php?topic=3083.0) (archived from [the original](https://nfggames.com/forum2/index.php?topic=3083.40) ) Footnotes --------- 1. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-1) S-RGB A is only observed on NUS-001(FRA) models. While this encoder is RGB-capable, the necessary amplification components are unpopulated on the motherboard in retail units. S-RGB A lacks S-Video encoding entirely, limiting unmodified NUS-CPU(R)-01 boards to composite output. 2. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-2) Later PAL revisions NUS-CPU(P)-03 and -03-1 lack S-Video output due to board-level omissions; the Y (luma) and C (chroma) traces from the MAV-NUS encoder to the Multi Out are severed and the supporting surface-mount components are unpopulated. S-Video output can be restored with a flex cable mod tapping directly from the MAV-NUS. 3. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-3) ! = active low. 4. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-4) The !Clamp signal is asserted during the horizontal back porch to restore the DC component of an AC-coupled signal. This "clamping" process re-establishes the black level reference (0 IRE) in this window to stabilize signal brightness. Because the color burst is transmitted concurrently, some sources refer to this period as "clamp/burst." 5. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-5) Since there are three unused bits in the multiplex sequence, it is unclear why the DAC has only 7 bits of precision instead of 8; no documentation yet found explains this. 6. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-6) The [Audio Interface](https://n64brew.dev/wiki/Audio_Interface "Audio Interface") also uses this clock. 7. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-7) This pin is named FSEL on MX8330MC/MX9911MC and NTSC/!PAL on MX8350. 8. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-8) See [KDS AT-49 datasheet](https://www.kds.info/wp-content/uploads/2015/11/2011-2012_032_en.pdf) for more detail. 9. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-9) The latest observed VDC-NUS/CPU-NUS is 1996, wk 47; the earliest observed VDC-NUS A/CPU-NUS A is 1997, wk 6. Source: original research by [Elle (meauxdal)](https://n64brew.dev/wiki/User:Elle_(meauxdal)?action=edit&redlink=1 "User:Elle (meauxdal) (page does not exist)") . 10. [↑](https://n64brew.dev/wiki/Video_DAC#cite_ref-10) RGB is encoded but not functional without modification. Retrieved from "[https://n64brew.dev/wiki/Video\_DAC?oldid=5821](https://n64brew.dev/wiki/Video_DAC?oldid=5821) " --- # Memory map - N64brew Wiki [](https://n64brew.dev/wiki/Memory_map#) Memory map ========== The Memory Management Unit (MMU) in the CPU utilizes a large virtual memory space to map to various physical addresses in different ways. All memory accesses made by the CPU, whether instruction fetches or load/store instructions, use virtual addresses. The MMU uses five virtual memory segments to decide how the addresses will be mapped to the physical memory space. Internally, all addresses are 64-bits wide. However, when in 32-bit addressing mode, the upper 32 bits are sign-extended. Contents -------- * [1 Virtual Memory Map](https://n64brew.dev/wiki/Memory_map#Virtual_Memory_Map) * [2 Physical Memory Map](https://n64brew.dev/wiki/Memory_map#Physical_Memory_Map) * [3 Physical Memory Map accesses](https://n64brew.dev/wiki/Memory_map#Physical_Memory_Map_accesses) * [3.1 Range 0x0000'0000 - 0x03EF'FFFF (RDRAM memory)](https://n64brew.dev/wiki/Memory_map#Range_0x0000'0000_-_0x03EF'FFFF_(RDRAM_memory)) * [3.2 Range 0x03F0'0000 - 0x03FF'FFFF (RDRAM registers)](https://n64brew.dev/wiki/Memory_map#Range_0x03F0'0000_-_0x03FF'FFFF_(RDRAM_registers)) * [3.3 Range 0x0400'0000 - 0x04FF'FFFF (RCP registers)](https://n64brew.dev/wiki/Memory_map#Range_0x0400'0000_-_0x04FF'FFFF_(RCP_registers)) * [3.4 Range 0x1FC0'0000 - 0x1FCF'FFFF (SI external bus)](https://n64brew.dev/wiki/Memory_map#Range_0x1FC0'0000_-_0x1FCF'FFFF_(SI_external_bus)) * [3.5 Ranges 0x0500'0000 - 0x1FBF'FFFF and 0x1FD0'0000 - 0x7FFF'FFFF (PI external bus)](https://n64brew.dev/wiki/Memory_map#Ranges_0x0500'0000_-_0x1FBF'FFFF_and_0x1FD0'0000_-_0x7FFF'FFFF_(PI_external_bus)) * [3.6 Range 0x8000'0000 - 0xFFFF'FFFF (Unmapped)](https://n64brew.dev/wiki/Memory_map#Range_0x8000'0000_-_0xFFFF'FFFF_(Unmapped)) Virtual Memory Map ------------------ The is the 32-bit virtual address space (used by most games and homebrew toolchains): | Address Range | | Name | Description | | --- | --- | --- | --- | | 0x00000000 | 0x7FFFFFFF | KUSEG | User segment, TLB mapped | | 0x80000000 | 0x9FFFFFFF | KSEG0 | Kernel segment 0, directly mapped, cached | | 0xA0000000 | 0xBFFFFFFF | KSEG1 | Kernel segment 1, directly mapped, uncached | | 0xC0000000 | 0xDFFFFFFF | KSSEG | Kernel supervisor segment, TLB mapped | | 0xE0000000 | 0xFFFFFFFF | KSEG3 | Kernel segment 3, TLB mapped | For the directly mapped segments KSEG0 and KSEG1, addresses are directly translated to physical addresses by subtracting by the base address of the respective segment. Thus they can only map to the physical address range `0x00000000 - 0x1FFFFFFF`. Refer to the [Translation lookaside buffer](https://n64brew.dev/wiki/Translation_lookaside_buffer?action=edit&redlink=1 "Translation lookaside buffer (page does not exist)") article and the [TLB mapping usage guide](https://n64brew.dev/wiki/TLB_mapping?action=edit&redlink=1 "TLB mapping (page does not exist)") for more information about TLB mapped segments. Physical Memory Map ------------------- | Bus / Device | Address Range | | Mirror mask | Name | Description | | --- | --- | --- | --- | --- | --- | | RDRAM | 0x00000000 | 0x03EFFFFF | 0x00000000 | [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM")
memory-space | RDRAM memory. See [RDRAM\_Interface#Memory\_addressing](https://n64brew.dev/wiki/RDRAM_Interface#Memory_addressing "RDRAM Interface")
and [RDRAM#RDRAM\_addressing](https://n64brew.dev/wiki/RDRAM#RDRAM_addressing "RDRAM")
for details about their mapping. | | 0x03F00000 | 0x03F7FFFF | 0x00000000 | [RDRAM Registers](https://n64brew.dev/wiki/RDRAM#Registers "RDRAM") | RDRAM registers. See [RDRAM\_Interface#Memory\_addressing](https://n64brew.dev/wiki/RDRAM_Interface#Memory_addressing "RDRAM Interface")
and [RDRAM#RDRAM\_addressing](https://n64brew.dev/wiki/RDRAM#RDRAM_addressing "RDRAM")
for details about their mapping. | | 0x03F80000 | 0x03FFFFFF | 0x00000000 | [RDRAM Registers](https://n64brew.dev/wiki/RDRAM#Registers "RDRAM")
(broadcast) | Write-only. All connected RDRAM will act on this register write request. See [RDRAM\_Interface#Memory\_addressing](https://n64brew.dev/wiki/RDRAM_Interface#Memory_addressing "RDRAM Interface")
and [RDRAM#RDRAM\_addressing](https://n64brew.dev/wiki/RDRAM#RDRAM_addressing "RDRAM")
for details. | | RCP | 0x04000000 | 0x04000FFF | 0x0003E000 | [RSP DMEM](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#DMEM_and_IMEM "Reality Signal Processor/Interface") | RSP Data Memory | | 0x04001000 | 0x04001FFF | 0x0003E000 | [RSP IMEM](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#DMEM_and_IMEM "Reality Signal Processor/Interface") | RSP Instruction Memory | | 0x04040000 | 0x040BFFFF | 0x0007FFE0 | [RSP Registers](https://n64brew.dev/wiki/RSP "RSP") | RSP DMAs, status, semaphore, program counter, IMEM BIST status | | 0x040C0000 | 0x040FFFFF | 0x00000000 | Unmapped | This area is completely ignored by the RCP. Any access in this area will freeze the CPU as the RCP will ignore the read/write and the CPU will never receive a reply. | | 0x04100000 | 0x041FFFFF | 0x001FFFE0 | [RDP Command Registers](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") | RDP DMAs, clock counters for: clock, buffer busy, pipe busy, and TMEM load | | 0x04200000 | 0x042FFFFF | ? | RDP Span Registers | TMEM BIST status, DP Span testing mode | | 0x04300000 | 0x043FFFFF | 0x001FFFF0 | [MIPS Interface](https://n64brew.dev/wiki/MIPS_Interface "MIPS Interface")
(MI) | Init mode, ebus test mode, RDRAM register mode, hardware version, interrupt status, interrupt masks | | 0x04400000 | 0x044FFFFF | 0x001FFFC0 | [Video Interface](https://n64brew.dev/wiki/Video_Interface "Video Interface")
(VI) | Video control registers | | 0x04500000 | 0x045FFFFF | 0x001FFFE0 | [Audio Interface](https://n64brew.dev/wiki/Audio_Interface "Audio Interface")
(AI) | Audio DMAs, Audio DAC clock divider | | 0x04600000 | 0x046FFFFF | 0x001FFFC0 | [Peripheral Interface](https://n64brew.dev/wiki/Peripheral_Interface "Peripheral Interface")
(PI) | Cartridge port DMAs, status, Domain 1 and 2 speed/latency/page-size controls | | 0x04700000 | 0x047FFFFF | 0x001FFFE0 | [RDRAM Interface](https://n64brew.dev/wiki/RDRAM_Interface "RDRAM Interface")
(RI) | Operating mode, current load, refresh/select config, latency, error and bank status | | 0x04800000 | 0x048FFFFF | 0x001FFFC0 | [Serial Interface](https://n64brew.dev/wiki/Serial_Interface "Serial Interface")
(SI) | SI DMAs, PIF status | | 0x04900000 | 0x04FFFFFF | 0x00000000 | Unmapped | This area is completely ignored by the RCP. Any access in this area will freeze the CPU as the RCP will ignore the read/write and the CPU will never receive a reply. | | PI external bus | 0x05000000 | 0x05FFFFFF | ? | N64DD Registers | Contains the N64DD I/O registers.

Accesses here are forwarded to the [PI bus](https://n64brew.dev/wiki/PI "PI")
, with the same address within the PI address space, using the "Domain 1" configuration set. When not present, this is a [PI open bus](https://n64brew.dev/wiki/Peripheral_Interface#Open_bus_behavior "Peripheral Interface")
area. | | 0x06000000 | 0x07FFFFFF | ? | N64DD IPL ROM | Contains the N64DD ROM used during boot, sometimes called IPL4. This is executed whenever the console is turned on with a N64DD connected, in place of the [IPL3](https://n64brew.dev/wiki/PIF-NUS#Console_startup "PIF-NUS")
.

Accesses here are forwarded to the [PI bus](https://n64brew.dev/wiki/PI "PI")
, with the same address within the PI address space, using the "Domain 1" configuration set. When not present, this is a [PI open bus](https://n64brew.dev/wiki/Peripheral_Interface#Open_bus_behavior "Peripheral Interface")
area. | | 0x08000000 | 0x0FFFFFFF | ? | Cartridge SRAM/FlashRAM | When the cartridge uses SRAM or [FlashRAM](https://n64brew.dev/wiki/Flash "Flash")
for save games, this is conventionally exposed at this address range.

Accesses here are forwarded to the [PI bus](https://n64brew.dev/wiki/PI "PI")
, with the same address within the PI address space, using the "Domain 2" configuration set. This is one of the few address ranges which are in Domain 2, probably because it is common to access SRAM/FlashRAM with a different (slower) protocol. When not present, this is a [PI open bus](https://n64brew.dev/wiki/Peripheral_Interface#Open_bus_behavior "Peripheral Interface")
area. | | 0x10000000 | 0x1FBFFFFF | 0x00000000 | [Cartridge ROM](https://n64brew.dev/wiki/ROM_Header "ROM Header") | The cartridges expose the ROM at this address. Normally, games will load assets and overlays via PI DMA for speed concerns, but the ROM is nonetheless memory mapped. Notice that cache accesses are not allowed here (and in all PI external bus accesses, see below for details), so while it is possible to run code directly from ROM, it will be extremely slow as it would not leverage the instruction cache.

Accesses here are forwarded to the [PI bus](https://n64brew.dev/wiki/PI "PI")
, with the same address within the PI address space, using the "Domain 1" configuration set. When not present (eg: when booting a disk-only N64DD game without a cartridge), this is a [PI open bus](https://n64brew.dev/wiki/Peripheral_Interface#Open_bus_behavior "Peripheral Interface")
area. | | SI external bus | 0x1FC00000 | 0x1FC007BF | ? | PIF ROM ([IPL1/2](https://n64brew.dev/wiki/Initial_Program_Load "Initial Program Load")
) | Executed on boot | | 0x1FC007C0 | 0x1FC007FF | ? | [PIF](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS")
RAM | Controller and EEPROM communication, and during IPL1/2 is used to read startup data from the PIF | | 0x1FC00800 | 0x1FCFFFFF | ? | Reserved | Unknown usage | | PI external bus | 0x1FD00000 | 0x1FFFFFFF | 0x00000000 | Unused | Accesses here are forwarded to the [PI bus](https://n64brew.dev/wiki/PI "PI")
, with the same address within the PI address space, using the "Domain 1" configuration set.

No known PI device uses this range, so it will normally be a [PI open bus](https://n64brew.dev/wiki/Peripheral_Interface#Open_bus_behavior "Peripheral Interface")
area. | | 0x20000000 | 0x7FFFFFFF | 0x00000000 | Unused | Accesses here are forwarded to the [PI bus](https://n64brew.dev/wiki/PI "PI")
, with the same address within the PI address space, using the "Domain 1" configuration set.

No known PI device uses this range, so it will normally be a [PI open bus](https://n64brew.dev/wiki/Peripheral_Interface#Open_bus_behavior "Peripheral Interface")
area.

NOTE: this range can be accessed by CPU only via TLBs or via direct 64-bit addressing, using the directly mapped, uncached segment (virtual 64-bit address: `0x9000_0000_nnnn_nnnn`). | | | 0x80000000 | 0xFFFFFFFF | 0x00000000 | Unmapped | This area is completely ignored by the RCP. Any access in this area will freeze the CPU as the RCP will ignore the read/write and the CPU will never receive a reply. | Physical Memory Map accesses ---------------------------- The physical memory map is implemented by RCP, as the VR4300 only talks directly to RCP. The bus between VR4300 and RCP is called [SysAD](https://n64brew.dev/wiki/SysAD_Interface "SysAD Interface") . The RCP behaves differently with different access sizes depending on the specific area of the map and the subcomponent in charge of implementing it. The SysAD bus is described at the hardware level in the [SysAD page](https://n64brew.dev/wiki/SysAD_Interface "SysAD Interface") , but to understand the effects on memory map it is sufficient to understand how data is marshalled for reads and writes. Since SysAD is a 32-bit bus, 32-bit accesses are the "native" ones, and the other access sizes are made in a weird way built upon a 32-bit data exchange. * Reads: VR4300 puts the address on the bus and the size of the access (8, 16, 32, 64). The RCP typically returns a full (aligned) 32-bit word address (or two, in case of a 64-bit read), from which the VR4300 extracts the correct portion. For instance, when reading 8-bit from address `0x0000'0001`, the RCP will put on the bus the 32-bit values at `0x0000'0000 - 0x0000'0003`, and the VR4300 will then just isolate the requested 8 bits. * Writes: VR4300 puts the address on the bus, the size of the access, and then the 32-bit value to be written. When the access is made using 8 or 16 bits, the value on the bus is prepared to match with the aligned 32-bit address. This is the same of what happens for reads, but this time it is the VR4300 that prepares the data. For instance, if register `S0=0x1234'5678`, `A0=0x0400'0001` and the opcode `SB S0, 0(A0)` is run, the VR4300 puts on the bus the value `S0 << 8`, that is `0x3456'7800`. RCP ignores the lower 2 bits of the address and the **access size**, so any RCP register or Mapped memory will treat it as 32bit write of `0x3456'7800` to `Address & 0xfffffffc`, So even if it's an 8 bit write opcode, the upper bits of register `S0` leak onto the bus. However, the lower bits of **address** and **access size** are passed on to the RDRAM devices (see below). Notice that misaligned address are forbidden by MIPS architecture and they will result in an Address Exception. So all accesses that go through the memory map are always aligned to the access size (eg: aligned to 2 bytes for 16-bit reads/writes). ### Range 0x0000'0000 - 0x03EF'FFFF (RDRAM memory) The accesses in this area are handled by RCP via RI (Ram Interface). When the VR4300 reads or writes a location in this range, it gets stalled while the RI communicates with the RDRAM via the RAMBUS serial protocol. As soon as the read or write is finished, the VR4300 is released. Effectively, all reads and writes are synchronous (blocking) from the point of view of the VR4300, as you would expect when accessing a RAM. All access sizes work correctly: 8-bit, 16-bit, 32-bit, 64-bit. Support smaller access sizes is implemented the RDRAM device, which uses the lower bits of **address** and **access size** passed from RCP to generate a byte mask. The RDRAM area is the only areas in the memory map where the RCP supports **cached** accesses. This allows the VR4300 to issue the cache fills/flushes at the SysAD level to leverage the internal data and instruction cache (either via the KSEG0 directly-mapped segment, or through a TLB configured with the cache setting). Instead cache requests are ignored for all the other address ranges, and they will thus freeze the CPU requiring a hard reset. ### Range 0x03F0'0000 - 0x03FF'FFFF (RDRAM registers) The accesses in this area are handled by RCP via RI (Ram Interface). When the VR4300 reads or writes a location in this range, it gets stalled while the RI communicates with the RDRAM via the RAMBUS serial protocol. As soon as the read or write is finished, the VR4300 is released. Effectively, all reads and writes are synchronous (blocking) from the point of view of the VR4300, as you would expect when accessing a RAM. ### Range 0x0400'0000 - 0x04FF'FFFF (RCP registers) The accesses in this area are handled by RCP itself without going to an external bus, and are dispatched internally to the correct subsystem. Access to a register might optionally stall the VR4300 if the subsystem is designed to do so (eg: to perform a long blocking operation on write), but in general for standard registers, they are quite fast and take only 5-6 PClock cycles (MI regs are a bit faster and take about 2 cycles). Accesses in this area are affected by a simplified hardware implementation of the RCP SysAD bus, so _**access size is ignored**_. Only uncached accesses are allowed; cached accesses will be ignored by RCP causing a VR4300 freeze, requiring a hard reboot. This means that: * Reads: RCP will ignore the requested access size and will just put the requested 32-bit word on the bus. Luckily, this is the correct behavior for 8-bit and 16-bit accesses (as explained above), so the VR4300 will be able to extract the correct portion. 64-bit reads instead will completely freeze the VR4300 (and thus the whole console), because it will stall waiting for the second word to appear on the bus that the RCP will never put. * Writes: RCP will ignore the requested access size and just write the word that was put on the bus directly into the hardware register. For 8-bit and 16-bit accesses, this means that the shifted value prepared by the VR4300 is the one that will be written verbatim. Reprising the example above, if `S0=0x1234'5678`, `A0=0x0460'0011`, running `SB S0, 0(A0)` will write the value `0x5678'0000` to the RCP hardware register `0x0460'0010`. For 64-bit accesses, as they are written on the bus MSB-first, the RCP will write the MSB to the hardware register, ignoring the LSB. ### Range 0x1FC0'0000 - 0x1FCF'FFFF (SI external bus) NOTE: this section is very similar to the one about PI external bus. The two areas behave exactly the same wrt the interface between VR4300 and RCP, despite the access being forward to different buses. All accesses made by the VR4300 in these ranges are forward externally by RCP on the external SI bus. This allows the CPU to access the onboard memory of the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") device; normally this is the onboard RAM, but also ROM can be accessed during boot (it is then locked out for security reasons). Accesses in this area are affected by the same simplified SysAD implementation described above, so **access size is ignored.** Only uncached accesses are allowed; cached accesses will be ignored by RCP causing a VR4300 freeze, requiring a hard reboot. The effect is the same described before. Moreover, there is one important additional detail: * All writes are performed **asynchronously** by the SI. Making a write in this area will in fact just cause the SI to latch the value internally, and release the VR4300 immediately. The write will then happen in background. The status of the ongoing write will be reflected by the SI "I/O busy" status bit, which will be set to 1 until the write is finalized. While a write is ongoing, further writes are ignored, and reads (from any address) are automatically delayed until the write is finished. For further information on this, please check the [SI page](https://n64brew.dev/wiki/SI "SI") . Notice that the SI doesn't know that a certain range of addresses are mapped to ROM and thus read-only, so even writes in the ROM area follow this pattern; they are just ignored by the PIF itself. ### Ranges 0x0500'0000 - 0x1FBF'FFFF and 0x1FD0'0000 - 0x7FFF'FFFF (PI external bus) NOTE: this section is very similar to the one about SI external bus. The two areas behave exactly the same wrt the interface between VR4300 and RCP, despite the access being forward to different buses. All accesses made by the VR4300 in these ranges are forward externally by RCP on the external PI bus. This allows the CPU to access external devices connected to the parallel bus like the cartridge ROM, SRAM and FlashRAM. Accesses in this area are affected by the same simplified SysAD implementation described above, so **access size is ignored.** Only uncached accesses are allowed; cached accesses will be ignored by RCP causing a VR4300 freeze, requiring a hard reboot. The effect is the same described before. Moreover, there are two important additional details: * All writes are performed **asynchronously** by the PI. Making a write in this area will in fact just cause the PI to latch the value internally, and release the VR4300 immediately. The write will then happen in background. The status of the ongoing write will be reflected by the PI "I/O busy" status bit, which will be set to 1 until the write is finalized. While a write is ongoing, further writes are ignored, and reads (from any address) return the 32-bit value that is being written. For further information on this, please check the [PI page](https://n64brew.dev/wiki/Peripheral_Interface "Peripheral Interface") . Notice that the PI doesn't know whether a certain device is read-only, so even writes in the ROM area follow this pattern; they are just ignored by the ROM itself. * The external PI bus is 16-bit. Given that the RCP only knows of 32-bit accesses (as access size is ignored), this means that each read or write performed by the VR4300 will cause exactly two reads or two writes on the PI bus: first the MSB at the address specified by the CPU (ignoring bit 0, so that the address is aligned to 16 bit), then the LSB at address+2. This might seem a small implementation detail, but it does actually cause an important and visible bug. For instance, if the VR4300 requests a 16-bit read at address `0x1000'0002`, the RCP (that ignores access sizes) will do two 16-bit reads on the cartridge bus at `0x1000'0002` and `0x1000'0004`, and will put on the SysAD bus the 32-bit word at `0x1000'0002 - 0x1000'0005`. This is a violation of the SysAD protocol explained above: in fact, in reply to a 16-bit read at `0x1000'0002`, the RCP should have put on the bus the 32-bit word at `0x1000'0000 - 0x1000'0003` instead. Because of this, effectively a 16-bit read at `0x1000'0002` returns the 16-bit word at `0x1000'0004` instead. ### Range 0x8000'0000 - 0xFFFF'FFFF (Unmapped) This range is not handled by RCP. All writes are ignored, and reads lock up the VR4300 because the RCP is stalled and does not return any data on the bus. In official documentation this region of the physical memory map is named "External SysAD Device". The designers appear to have intentionally neglected to have the RCP generate a response to this address range to allow an alternate responder on the SysAD bus handle the request instead. On a retail N64 there are no alternate responders, the SysAD bus exclusively connects the VR4300 and the RCP so in all cases an access to this region does nothing interesting, but this may not be the case on other devices such as the Indy dev board or the Aleck64. Examples of alternate responders / external SysAD devices for these are the RDB debug interface on the Indy dev board and additional (CPU access only) SDRAM on the Aleck64. Retrieved from "[https://n64brew.dev/wiki/Memory\_map?oldid=5763](https://n64brew.dev/wiki/Memory_map?oldid=5763) " --- # Audio Interface - N64brew Wiki [](https://n64brew.dev/wiki/Audio_Interface#) Audio Interface =============== The Audio Interface (or **AI**) is one of multiple I/O interfaces in the RCP, which is used to playback audio samples. It is a very simple audio processor: it fetches samples via DMA from RDRAM at a specified rate, and then outputs them. It performs absolutely no conversion on the samples: any audio processing functionality (decompression, mixing, etc.) must be performed by either the CPU or the RSP. Memory mapped registers are used to configure the AI and initiate DMA transfers. The base address for these registers is `0x0450 0000`, also known as AI\_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 `0xA000 0000` to the address. As an example, to directly write to the AI\_LENGTH register, use address `0xA450 0004`. Contents -------- * [1 DMA](https://n64brew.dev/wiki/Audio_Interface#DMA) * [1.1 Delayed-carry hardware bug](https://n64brew.dev/wiki/Audio_Interface#Delayed-carry_hardware_bug) * [2 Registers](https://n64brew.dev/wiki/Audio_Interface#Registers) * [2.1 0x0450 0000 - AI\_DRAM\_ADDR](https://n64brew.dev/wiki/Audio_Interface#0x0450_0000_-_AI_DRAM_ADDR) * [2.2 0x0450 0004 - AI\_LENGTH](https://n64brew.dev/wiki/Audio_Interface#0x0450_0004_-_AI_LENGTH) * [2.3 0x0450 0008 - AI\_CONTROL](https://n64brew.dev/wiki/Audio_Interface#0x0450_0008_-_AI_CONTROL) * [2.4 0x0450 000C - AI\_STATUS](https://n64brew.dev/wiki/Audio_Interface#0x0450_000C_-_AI_STATUS) * [2.5 0x0450 0010 - AI\_DACRATE](https://n64brew.dev/wiki/Audio_Interface#0x0450_0010_-_AI_DACRATE) * [2.6 0x0450 0014 - AI\_BITRATE](https://n64brew.dev/wiki/Audio_Interface#0x0450_0014_-_AI_BITRATE) DMA --- AI allows to playback samples via a DMA channel. The CPU prepares a buffer of samples in RDRAM, then writes the AI registers to setup a DMA transfer specifying the buffer address and the length. The AI starts playing back those samples in background. Like the RSP and the RDP DMAs, also the AI DMAs has a double-buffering mechanism, so it is possible to enqueue a second buffer while the first one is playing back. This allows for continuous playback: as soon as a buffer is finished, a second is hopefully ready for playback so that the audio is uninterrupted. The AI does not have an internal RAM holding samples: the DMA is directly connected to the DAC. This means that the DMA will progress as samples are physically put through the DAC, and the configured playback rate. Connected to the DMA channel, there is a [IRQ triggered via MI](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0008_-_MI_INTERRUPT "MIPS Interface") . Contrary to the usual working of DMAs, the AI IRQ is triggered when a DMA transfer **starts**, not when it ends. This can be intuitively explained: the AI wants to notify the CPU to prepare and enqueue a new buffer **while** a buffer is currently playing, so that the hopefully the new one will be ready by the time the current one finishes, to allow uninterrupted playback. If the AI generated the interrupt at the end of the buffer, that would be too late to enqueue a new one without audio crackings, which in turns means that the CPU wouldn't be able to rely on the IRQ for audio pacing. AI only supports 16-bit stereo samples. ### Delayed-carry hardware bug The AI DMA has a hardware bug that triggers whenever the last sample of a DMA transfer ends exactly at the boundary of a 8KiB (0x2000) page. When this happens, AI will add 0x2000 to the address of the **next** buffer that will be played back, effectively playing back samples from a different address than that programmed by the CPU. The most likely explanation for this hardware bug is a delayed carry: maybe the AI has some internal address register split in two halves, with the lower half being 13 bits. When the lower-half register overflows (producing a carry), the carry is meant to be added to the higher-half register, but it might be that this happens 1 cycle later: if the DMA transfer ends exactly at that moment, the carry is added to the higher half at the beginning of the **next** transfer itself. Libdragon has a [workaround for this bug](https://github.com/DragonMinded/libdragon/blob/14bb8848947e0e0e03fad9e12db7765c139e2c02/src/audio.c#L271-L282) . 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 #### 0x0450 0000 - AI\_DRAM\_ADDR * * * | AI\_DRAM\_ADDR `0x0450 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | DRAM\_ADDR\[23:16\] | | | | | | | | | 15:8 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | DRAM\_ADDR\[15:8\] | | | | | | | | | 7:0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | DRAM\_ADDR\[7:3\] | | | | | 0 | 0 | 0 | | | | | --- | --- | | bit 23-0 | **DRAM\_ADDR\[23:0\]:** RDRAM address used for next DMA transfer | **Extra Details:** **Read access** The register is write-only. Reading it returns a mirror of AI\_LENGTH. #### 0x0450 0004 - AI\_LENGTH * * * | AI\_LENGTH `0x0450 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | RW-? | RW-? | | — | — | — | — | — | — | LENGTH\[17:16\] | | | 15:8 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | LENGTH\[15:8\] | | | | | | | | | 7:0 | RW-? | RW-? | RW-? | RW-? | RW-? | R-? | R-? | R-? | | LENGTH\[7:3\] | | | | | 0 | 0 | 0 | | | | | --- | --- | | bit 17-0 | **LENGTH\[17:0\]:** Number of bytes of audio to send via DMA | Reads return the number of bytes remaining #### 0x0450 0008 - AI\_CONTROL * * * | AI\_CONTROL `0x0450 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | W-? | | — | — | — | — | — | — | — | DMA\_ENABLE | | | | | --- | --- | | bit 0 | **DMA\_ENABLE:** Enables AI DMA | **Extra Details:** **Read access** The register is write-only. Reading it returns a mirror of AI\_LENGTH. #### 0x0450 000C - AI\_STATUS * * * | AI\_STATUS `0x0450 000C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-? | R-? | U-? | U-? | U-? | U-? | R-? | U-1 | | FULL | BUSY | 0 | 0 | — | — | ENABLED | 1 | | 23:16 | U-? | U-? | U-? | U-1 | U-? | U-? | U-? | U-? | | — | — | — | 1 | WC | 0 | 0 | BC | | 15:8 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | 0 | COUNT\[13:7\] | | | | | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | COUNT\[6:0\] | | | | | | | FULL | | | | | --- | --- | | bit 31 | **FULL:** Set to 1 when there is a DMA transfer pending in the DMA register in addition to a DMA already in progress. | | bit 30 | **BUSY:** Set to 1 when there is a DMA transfer currently in progress. | | bit 25 | **ENABLED:** Reflects the bit written to AI\_CONTROL | | bit 19 | **WC:** Word clock readback. This is toggled at DACRATE/2 like COUNT, though it is out-of-phase of exactly half of the period. | | bit 16 | **BC:** Bit clock readback. This is (probably) toggled for each bit shifted in, at BITRATE. | | bit 14-1 | **COUNT:** internal counter for DAC output | | bit 0 | **FULL:** Same as other copy | This register is read-only. Writes to it acknowledge the AI interrupt. **Extra Details:** **COUNT** Count shows the internal counter used by AI to emit the various samples to the DAC. It ticks downwards at the VI clock frequency, starts at `DACRATE/2`, and ticks towards 0, and then reloads. It always ticks unless `AI_BITRATE` is 0. It is not affected by `DMA_ENABLE` in `AI_CONTROL`. **BC** It is believed that this is the status of the physical BCLK line to the N64's BU9480 audio DAC. (It is only "believed" because the CPU cannot reliably sample it rapidly enough even when `BITRATE` is set to 15.) If it is BCLK, the DAC receives the next bit of audio when this changes from 0 to 1. **WC** The status of the BU9480's LRCK line. (1 = left channel, 0 = right channel, rising edge = emit interpolated sample, falling edge = emit next sample) #### 0x0450 0010 - AI\_DACRATE * * * | AI\_DACRATE `0x0450 0010` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | W-? | W-? | W-? | W-? | W-? | W-? | | — | — | DACRATE\[13:8\] | | | | | | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | DACRATE\[7:0\] | | | | | | | | | | | | --- | --- | | bit 13-0 | **DACRATE\[13:0\]:** Sample period | The register is write-only. Reading it returns a mirror of AI\_LENGTH. The sample rate is the Video clock, divided by one more than this number. For example, a value of 1103 would result in a sample rate of 44136 Hz on an NTSC console. #### 0x0450 0014 - AI\_BITRATE * * * | AI\_BITRATE `0x0450 0014` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | U-? | U-? | U-? | U-? | W-? | W-? | W-? | W-? | | — | — | — | — | BITRATE\[3:0\] | | | | | | | | --- | --- | | bit 3-0 | **BITRATE\[3:0\]:** Half of bit clock period of I²S output to DAC | The register is write-only. Reading it returns a mirror of AI\_LENGTH. The bit clock is the clock that sends single bits of each sample into the DAC. The external DAC requires some "framing" so it actually expects two clocks: one to shift each bit, and the second one (AI\_DACRATE) to "close" a sample. The bit clock rate is the Video clock, divided by one more than this number. A written value of 0 instead stops the clock (see also \`COUNT\` in the AI\_STATUS register). The bit clock rate must at least 66 times faster than the DAC rate.  [](https://n64brew.dev/wiki/File:Example_of_clocks_going_into_the_DAC.png) Retrieved from "[https://n64brew.dev/wiki/Audio\_Interface?oldid=5700](https://n64brew.dev/wiki/Audio_Interface?oldid=5700) " --- # Train Controller - N64brew Wiki [](https://n64brew.dev/wiki/Train_Controller#) Train Controller ================ The **Train Controller** was developed by Taito. The only game that uses it is _Densha De Go_. It has a select and start button, a throttle with 5 positions, a break lever with 8 positions, 3 circular buttons, and an emergency break. It can hold a Japan rail pocketwatch. In the game menus, the throttle moves the focus up or down. The C button controls the train horn. The train controller plugs into controller port #3. Retrieved from "[https://n64brew.dev/wiki/Train\_Controller?oldid=5133](https://n64brew.dev/wiki/Train_Controller?oldid=5133) " --- # Mouse - N64brew Wiki [](https://n64brew.dev/wiki/Mouse#) Mouse ===== This a standard 2-button ball mouse. A few 64DD games support it: * Mario Artist: Paint Studio * Mario Artist: Talent Studio * Mario Artist: Polygon Studio * Mario Artist: Communication Kit Most other N64 games see the mouse as a controller, and a few of them are playable like this. Mouse movements register as analog stick movements. The left button maps to the A button, and the right mouse button maps to B. There is a single microcontroller that handles everything. This mouse appears to be based on contemporary Mitsumi PS/2 mice, which have nearly identical guts. Closest match is the ECM-S3902 Retrieved from "[https://n64brew.dev/wiki/Mouse?oldid=239](https://n64brew.dev/wiki/Mouse?oldid=239) " --- # Fishing Rod - N64brew Wiki [](https://n64brew.dev/wiki/Fishing_Rod#) Fishing Rod =========== The N64 Rumble Rod is a fishing controller by Madcatz. On the top, it has a grey control stick in the middle, yellow C stick on the right, blue A and green B buttons on the left, and red start button in the center. On the left side it has a D-pad. On the right side the crank functions as the L button. **TODO:** Is there an R button? The Rumble Rod takes two AAA batteries and has a built in rumble pak to simulate fishing. The Rumble Rod can be used with all N64 games as an exotic controller, but the only fishing games relased on the system are _In-Fisherman Bass Hunter 64_ and _Bass Masters 2000_. Retrieved from "[https://n64brew.dev/wiki/Fishing\_Rod?oldid=4318](https://n64brew.dev/wiki/Fishing_Rod?oldid=4318) " --- # Voice Recognition Unit - N64brew Wiki [](https://n64brew.dev/wiki/Voice_Recognition_Unit#) Voice Recognition Unit ====================== The **Voice Recoginition Unit** (VRU) is a microphone for the N64 developed by Ambrella. The VRU prototype was built from parts in Akihabara. The VRU is region locked, and known as the VRS (voice recognition system) in Japan. It was released December 12, 1998 in Japan and November 6, 2000 in North America. In North America, the only game supporting the VRU (and includes and requires it, in fact) is _Hey You, Pikachu!_ In Japan, the game _Densha de Go!_ (meaning "Go by train" in english) supports the VRU as well. The VRU consists of a microphone covered in a yellow foam ball with a 3.5mm audio jack. The microphone can be clipped onto the N64 controller and clamped around the Controller Pak port. Alternatively, the VRU can he hung from the neck. The VRU is calibrated for higher pitched voices. The VRU plugs into controller port #4 and the cable is 6 feet long. The VRU unit has 4 tripoint screws and contains the following chips (at this time, only speculation on the purpose of each chip is known): * **NEC d9930g (uPD9930 audio CODEC)**: Converts analog signals from the mic to digital samples that are forwarded to VRD-NUS, gain settings and some other registers are directly programmable via joybus command 13/0x0D * **VRD-NUS (VRS model) / EVR-NUS (VRU model)**: Probably a repackaged NEC uPD7701x 16-bit DSP, the pinout of the VRD-NUS matches up very well (explaining why there are so many unused pins) and its capabilities line up with expectation: it has an 8-bit bus connecting to VCI-NUS and a serial interface compatible with uPD9930. If so, there are instruction and data ROMs inside. uPD7701x has a JTAG interface but it isn't well documented so whether it can dump the ROM without decapsulation is unclear. * **VCI-NUS**: Controls joybus wire transactions and acts as the host controller for VRD-NUS and uPD9930. Retrieved from "[https://n64brew.dev/wiki/Voice\_Recognition\_Unit?oldid=5566](https://n64brew.dev/wiki/Voice_Recognition_Unit?oldid=5566) " --- # Transfer Pak - N64brew Wiki [](https://n64brew.dev/wiki/Transfer_Pak#) Transfer Pak ============ The Transfer Pak is an accessory that plugs into the controller and allows the Nintendo 64 to transfer data between its own games and Game Boy or Game Boy Color games. The Transfer Pak has a Game Boy Color slot and a part that fits onto the expansion port of the N64 controller. It was included with the game Pokémon Stadium, as the game's main feature is importing Pokémon teams from Game Boy games. In the Nintendo 64 Programming Manual, the Transfer Pak is referred to as the N64 Game Boy Pak. ### References * [https://ultra64.ca/files/documentation/online-manuals/man/pro-man/pro26/index26.7.html](https://ultra64.ca/files/documentation/online-manuals/man/pro-man/pro26/index26.7.html) Retrieved from "[https://n64brew.dev/wiki/Transfer\_Pak?oldid=4372](https://n64brew.dev/wiki/Transfer_Pak?oldid=4372) " --- # Doctor V64 - N64brew Wiki [](https://n64brew.dev/wiki/Doctor_V64#) Doctor V64 ========== The **Doctor V64** is a backup device developed by Bung Enterprises Limited. It was manufactured in Hong Kong and released in 1997 for $450. It contains an IDE-CD drive and plugs into the N64's external connector. The first model was 8x speed, but later models with 16x and 32x speed were released. At the heart of the Doctor V64 is a MOS Technology 6502 CPU. It originally was released with 16 MB of RAM, which later revisions increased to 32 MB. It can function as an external video CD player and development kit. Akklaim used the Doctor V64 as an unofficial dev kit on Turok 3. The Doctor V64 can dump roms (with .v64 extension) from cartridges. When lik-sang.com began to sell Bung products, Nintendo was made aware of the company. On November 21st 1997, Nintendo of America filed a lawsuit against Bung Enterprises Ltd. Nintendo was awarded 7 million dollars in damages and Bung ceased selling the Doctor V64. Retrieved from "[https://n64brew.dev/wiki/Doctor\_V64?oldid=5134](https://n64brew.dev/wiki/Doctor_V64?oldid=5134) " --- # Libdragon - N64brew Wiki [](https://n64brew.dev/wiki/Libdragon#) Libdragon ========= Libdragon is an open-source SDK for Nintendo 64. It aims for a complete N64 programming experience while providing programmers with modern approach to programming and debugging. Libdragon development happens in the [DragonMinded/libdragon GitHub repo](https://github.com/DragonMinded/libdragon) . More information can be found in the README and in the [libdragon wiki](https://github.com/DragonMinded/libdragon/wiki) . Retrieved from "[https://n64brew.dev/wiki/Libdragon?oldid=5796](https://n64brew.dev/wiki/Libdragon?oldid=5796) " --- # IQue SDK - N64brew Wiki [](https://n64brew.dev/wiki/IQue_SDK#) IQue SDK ======== ### Overview While this compiler and tools collection was originally used on 32-bit Linux, it works well on 64-bit Linux and Windows 10 with Windows Subsystem for Linux (WSL) ### References More details available on the dedicated Modern SDK page: [https://crashoveride95.github.io/modernsdk/index.html](https://crashoveride95.github.io/modernsdk/index.html) Retrieved from "[https://n64brew.dev/wiki/IQue\_SDK?oldid=5807](https://n64brew.dev/wiki/IQue_SDK?oldid=5807) " --- # N64 IRIX - N64brew Wiki [](https://n64brew.dev/wiki/N64_IRIX#) N64 IRIX ======== ### tl;dr (Too Long;Didn't Read) Just don't, not worth the time or money. ### Overview The **N64 IRIX** SDK is part of Nintendo's original development options, although was deprecated by most studios after a few years, because Windows PC had increased in performance and dropped in price. N64 Development on an SGI Workstation (Any) is not easy. IRIX and it's available software (i.e. Web Browser) hasn't been updated in over 15 years and a 100 to 200 MHz processor is not going to perform as well as a modern computer for compiling. The only way an SGI Indy (very specific) could be a benefit is if you are using the Ultra64 Dev board that was produced by Nintendo this could possibly allow for source level debugging. Retrieved from "[https://n64brew.dev/wiki/N64\_IRIX?oldid=5137](https://n64brew.dev/wiki/N64_IRIX?oldid=5137) " --- # SGI Audio Tools - N64brew Wiki [](https://n64brew.dev/wiki/SGI_Audio_Tools#) SGI Audio Tools =============== Contents -------- * [1 Introduction](https://n64brew.dev/wiki/SGI_Audio_Tools#Introduction) * [2 Prerequisites](https://n64brew.dev/wiki/SGI_Audio_Tools#Prerequisites) * [3 Authoring a Song with the SGI Audio Tools](https://n64brew.dev/wiki/SGI_Audio_Tools#Authoring_a_Song_with_the_SGI_Audio_Tools) * [3.1 Compressing Sequence Data](https://n64brew.dev/wiki/SGI_Audio_Tools#Compressing_Sequence_Data) * [3.1.1 Converting Your MIDI File(s)](https://n64brew.dev/wiki/SGI_Audio_Tools#Converting_Your_MIDI_File(s)) * [3.1.2 Compressing Your MIDI File(s)](https://n64brew.dev/wiki/SGI_Audio_Tools#Compressing_Your_MIDI_File(s)) * [3.1.3 Compiling Your MIDI File(s)](https://n64brew.dev/wiki/SGI_Audio_Tools#Compiling_Your_MIDI_File(s)) * [3.2 Compressing Sounds](https://n64brew.dev/wiki/SGI_Audio_Tools#Compressing_Sounds) * [3.2.1 Converting samples with SoX](https://n64brew.dev/wiki/SGI_Audio_Tools#Converting_samples_with_SoX) * [3.2.2 Creating a code book for each file](https://n64brew.dev/wiki/SGI_Audio_Tools#Creating_a_code_book_for_each_file) * [3.2.3 Compressing each sample](https://n64brew.dev/wiki/SGI_Audio_Tools#Compressing_each_sample) * [3.3 Authoring the Instrument Bank File](https://n64brew.dev/wiki/SGI_Audio_Tools#Authoring_the_Instrument_Bank_File) * [3.3.1 Before we Begin](https://n64brew.dev/wiki/SGI_Audio_Tools#Before_we_Begin) * [3.3.2 The Instrument Bank File](https://n64brew.dev/wiki/SGI_Audio_Tools#The_Instrument_Bank_File) * [3.3.3 envelope](https://n64brew.dev/wiki/SGI_Audio_Tools#envelope) * [3.3.4 keymap](https://n64brew.dev/wiki/SGI_Audio_Tools#keymap) * [3.3.5 sound](https://n64brew.dev/wiki/SGI_Audio_Tools#sound) * [3.3.6 instrument](https://n64brew.dev/wiki/SGI_Audio_Tools#instrument) * [3.3.7 bank](https://n64brew.dev/wiki/SGI_Audio_Tools#bank) * [3.3.8 Percussion Sounds](https://n64brew.dev/wiki/SGI_Audio_Tools#Percussion_Sounds) * [3.3.9 N64 SDK Example Instrument Bank](https://n64brew.dev/wiki/SGI_Audio_Tools#N64_SDK_Example_Instrument_Bank) * [3.3.10 Compiling the Instrument Bank](https://n64brew.dev/wiki/SGI_Audio_Tools#Compiling_the_Instrument_Bank) * [3.4 Finishing up](https://n64brew.dev/wiki/SGI_Audio_Tools#Finishing_up) * [4 Playing a song with NuSystem](https://n64brew.dev/wiki/SGI_Audio_Tools#Playing_a_song_with_NuSystem) * [4.1 Linking the Audio Library](https://n64brew.dev/wiki/SGI_Audio_Tools#Linking_the_Audio_Library) * [4.2 Setting up playback](https://n64brew.dev/wiki/SGI_Audio_Tools#Setting_up_playback) * [4.3 Starting/stoping playback](https://n64brew.dev/wiki/SGI_Audio_Tools#Starting/stoping_playback) * [5 Making an Instrument Bank for Sound Effects](https://n64brew.dev/wiki/SGI_Audio_Tools#Making_an_Instrument_Bank_for_Sound_Effects) * [5.1 Looping](https://n64brew.dev/wiki/SGI_Audio_Tools#Looping) * [5.1.1 Looping Compressed Audio](https://n64brew.dev/wiki/SGI_Audio_Tools#Looping_Compressed_Audio) * [5.1.2 Looping Non-Compressed Audio](https://n64brew.dev/wiki/SGI_Audio_Tools#Looping_Non-Compressed_Audio) Introduction ------------ The Nintendo 64 SDK comes with two "batteries included" audio libraries, the SGI Audio Tools and the N64SoundTools. The SGI Audio Tools are a collection of command-line tools for preparing samples and MIDI sequences for playback on the Nintendo 64. The N64SoundTools was written by Acclaim Studios Manchester (formerly Software Creations) and encompasses a kind of DAW for authoring and editing songs for the Nintendo 64. This article hopes to give a step-by-step reference for authoring sounds and music with the SGI Audio Tools and playing them in a NuSystem-based game. While not as intuitive and straightforward as the N64SoundTools, there are advantages to having a collection of command-line tools as they're entirely scriptable and can help automate compiling/editing of sound data. Prerequisites ------------- This article assumes you're familiar with the following terms/concepts: * Audio samples * ADSR and envelopes * Sample rate * MIDI * Linear predictive coding * AIFF format * the PATH environment variable (for program quick access) If you're a bit unfamiliar, a quick search, tutorial, or Wikipedia skim should suffice. This article assumes you have the SGI Audio Tools as part of the Nintendo 64 SDK. The programs in particular you're going to need are: * `tabledesign` * `vadpcm_enc` * `ic` * `midicvt` * `midicomp` * `sbc` This article also assumes that you're using the aforementioned programs in a Windows 95-like environment. An emulator, such as [Oracle VirtualBox](https://www.virtualbox.org/) works fine too. The n64decomp project has decompiled `tabledesign` and `adpcm` [here](https://github.com/n64decomp/sdk-tools) . It's possible to build those two particular programs yourself and run them in the environment of your choice, which might make your life a little easier! A good warm-up for this article might be to compile and run the `nu3` NuSystem sample, as it's more or less a "hello world" that plays the sort of audio files we're looking to generate. Keep note of the Makefile including the audio library and the spec file adding the `sbk`, `ctl`, and `tbl` files to the ROM. If you're able to compile/run `nu3`, even in an emulator, you'll be in a good place to test/debug/iterate an issues that pop up in your program. Authoring a Song with the SGI Audio Tools ----------------------------------------- ### Compressing Sequence Data #### Converting Your MIDI File(s) MIDI files are generally either **Type 0** or **Type 1**. The former specifies all of the notes in a single "track" while the latter has multiple tracks, typically for each instrument. The SGI tools require MIDI files to be in Type 0 and provides the `midicvt` tool to convert to it. Programs such as [MuseScore](https://musescore.org/) likely export in Type 1, so it's usually a good idea to convert to Type 0 before continuing.  [](https://n64brew.dev/wiki/File:Midicvt_sample_image.png "Screenshot of midicvt run successfully.") For each of your original MIDI files, run the following: `midicvt some_midi.mid some_midi_converted.mid` `some_midi_converted.mid` is the name of the converted file in this example. #### Compressing Your MIDI File(s) Once your MIDI files have been converted to Type 0, we'll be converting them to a compressed sequence format specialized for embedded playback on the Nintendo 64. The NuSystem library is written to use compressed MIDI files for songs. If you inspect the library, you'll notice that it uses a `ALCSPlayer` for storing/playing songs. It is possible to use uncompressed Type 0 MIDI, but you'll need to look into editing/rebuilding NuSystem or your own audio code with Nintendo's core audio library.  [](https://n64brew.dev/wiki/File:Midicomp_usage.png "Midicomp being successfully used.") For each of your converted MIDI files, run the following: `midicomp some_midi_converted.mid some_midi_compressed.cmf` You'll now have various `cmf` files for each of your songs. #### Compiling Your MIDI File(s) Now that we've compressed each MIDI file, its time to compile them into one "song bank". This will be added to your ROM and loaded in at runtime. To do this, we'll be using the `sbc` tool. Run the following command with each of your `cmf` files as parameters. `sbc -Osongs.sbk first_song_compressed.cmf second_song_compressed.cmf third_song_compressed.cmf`  [](https://n64brew.dev/wiki/File:Sbc_used.png "sbc successfully used here") The **ordering is important** here! Keep note of the order of each parameter, as when you're selecting your songs in your game's source code, you'll be indexing them as they're ordered here (eg: `first_song_compressed.cmf` will be `0`, second\_song\_compressed.cmf will be `1`, etc.). Note the lack of space between the `-O` flag and the output file name. This seems to be intended. 🤷 ### Compressing Sounds #### Converting samples with SoX [SoX](http://sox.sourceforge.net/) bills itself as _the Swiss Army knife of sound processing programs_. Its uses include (but aren't limited to) converting audio between formats, providing effects, and even recording. Given that SoX is an open-source tool, it's well worth including into any game developer's setup. The `tabledesign` and `vadpcm_enc` tools require audio samples to be in AIFF or AIFC. If the samples you're using are in a different format, such as WAV, you can use SoX to batch-convert your samples. It's also a good idea to resample each effect to the same sample rate, such as 32000Hz. If you're generating your instrument bank file via a script, you can hardcode the sample rate which will let you spend less time coding/debugging. If we want to convert an arbitrary WAV file to AIFF with a sample rate of 32000 and in mono we can enter: `sox some_file.wav -r 32000 -c 1 converted_file.aiff` This article assumes that the reader is converting their files to AIFF with SoX. #### Creating a code book for each file You'll want to create a code book for each AIFF sample you want to use in your song. To do this, you'll run the `tabledesign` command on each of your samples and save the output of that program to a file. For clarity, we'll be suffix-ing each code book with `.table` but it's not necessary to do. On each of your samples, run the following: `tabledesign song_sample.aiff > song_sample.table` It's worth noting that by default `tabledesign` will print to `STDOUT`. The `>` operator for writing to a file should work both on Unix-like and Windows here. #### Compressing each sample Once we've created our code book(s), we'll want to convert our AIFF samples to Nintendo's compressed AIFC formats. To do that, we'll be using `vadpcm_enc`. On each of your samples, run the following: `vadpcm_enc -c song_sample.table song_sample.aiff compressed_song_sample.aifc` The following `.aifc` file(s) will be compiled in to make a sound bank. ### Authoring the Instrument Bank File #### Before we Begin This is likely the most tricky and confusing parts of the SGI Audio Tools, so be sure to take a break if you're finding yourself frustrated. Take comfort in that what you're feeling is pretty normal, and that others have been in the same spot. Section 18.1.12 of the _Nintendo 64 Programming Manual_ is a pretty comfortable overview of what each section of an instrument bank file does. It's not "correct" in certain areas though, and copy/pasting the shown examples won't always work with `ic`. A particular example is that the manual says to reference each instrument in your `bank` section with `program` when in fact you'll need to use `instrument` instead. If you're looking for a reference of a working instrument bank, it's best to check the example banks at `ultra/usr/src/pr/assets/banks/` included with the SDK. They'll run through `ic` fine and help clarify things for you. #### The Instrument Bank File An instrument bank file usually has the file extension of `.ins`. Inside it contains one or more of each of the following: * `envelope` section(s), indicating an [ADSR](https://en.wikipedia.org/wiki/Envelope_(music)) * `keymap` section(s), indicating the range of "piano keys" a sound occupies, as well as other data * `sound` section(s), indicating a sampled sound as well as the `envelope` and `keymap` it uses * `instrument` section(s), indicating a "MIDI instrument" with a volume, pan, and various `sound`s * A single `bank` section, indicating the sample rate, and which `instrument`s correspond to which MIDI instrument numbers in your sequences. This will include a specialized instrument for the drumset channel. #### envelope The SGI Audio Tools represent an [ADSR](https://en.wikipedia.org/wiki/Envelope_(music)) with `envelope`s. Volume for each of the ADSR points ranges from `0` to `127`. Time is modelled in microseconds for each of the ADSR points. Different samples and sounds can use the same `envelope`, but your tracks will generally sound better if you ensure that each sample has a matching envelope. If you're unsure, it doesn't An `envelope` looks like the following: envelope AnExampleEnvelope { attackTime = 10000; attackVolume = 127; decayTime = 500000; decayVolume = 100; releaseTime = 200000; } In the example above, the volume goes from `0` to `127` in 10000 microseconds (the attack), then decays to 100 over `500000` microseconds. When the sound using is envelope ends, the sound fades out over `200000` microseconds. This should generally match up to your sample. For more information, review the N64 SDK Documentation at [18.1.2.5](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-05) . #### keymap A `keymap` represents a range of "keys" for a sound to cover. The [MIDI Standard](https://www.inspiredacoustics.com/en/MIDI_note_numbers_and_center_frequencies) represents each of the western music pitches from `0` to `127`. `60` can be considered middle C. A `keymap` looks like the following: keymap AnExampleKeymap { velocityMin = 0; velocityMax = 127; keyMin = 0; keyMax = 127; keyBase = 60; detune = 0; } The example above maps to every available pitch as `keyMin` is `0` and `keyMax` is `127`. `keyBase` represents the "reference pitch" to scale when changing keys. In the example above, a sample with the pitch of middle C should be used. Samples at different frequencies will require a different `keyBase` value. For more information, review the N64 SDK Documentation at [18.1.2.4](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-04) . #### sound A `sound` combines a `keymap`, `envelope`, and a compressed sample file together into a unit. A `sound` also has properties for stereo panning and volume from `0` to `127` each. An example might look like: sound AnExampleSound { use ("your/particular/path/to/compressed\_song\_sample.aifc"); pan = 64; volume = 127; keymap = AnExampleKeymap; envelope = AnExampleEnvelope; } Note how the `keymap` and `envelope` parts correspond to names of our examples above. For more information, review the N64 SDK Documentation at [18.1.2.3](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-03) . #### instrument An `instrument` models a single MIDI instrument. It consists of one or more `sound`s. An example `instrument` might look like: instrument AnExampleInstrument { volume = 127; pan = 64; sound = AnExampleSound; } Note how `sound` property matches the name of an existing sound above. An `instrument` can specify multiple `sound`s. For example, `GenMidiBank.inst` in the N64 SDK uses four sounds for a MIDI Cello: instrument Cello { volume = 127; pan = 64; vibratoType = 128; /\* 128, 129, 130, 131 \*/ vibratoRate = 222; /\* 0 to 255 \*/ vibratoDepth = 6; /\* 0 to 255 \*/ vibratoDelay = 1; /\* 1 to 255 \*/ sound = Cello00; sound = Cello01; sound = Cello02; sound = Cello03; } Each of the corresponding `sound`s have different `keymap`s and samples that cover different ranges of notes. This can produce nicer-quality audio as the pitch of a sample doesn't need to be distorted as much. The tradeoff being more audio memory required for your `instrument`, especially at higher sampling frequencies such as 44100Hz. For more information, review the N64 SDK Documentation at [18.1.2.2](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-02) . #### bank The `bank` section is a collection of `instrument` and the final piece of the puzzle for our file. Each [MIDI instrument number](http://fmslogo.sourceforge.net/manual/midi-instrument.html) gets assigned a particular `instrument`. An example `bank` might look like: bank SongBank { sampleRate = 32000; percussionDefault = Percussion\_Kit; instrument \[0\] = AnExampleSound; instrument \[65\] = AnExampleAltoSax; instrument \[107\] = AnExampleKoto; } Here we associate various `instrument`s with different MIDI instrument numbers. `0` represents MIDI notes that are played with an Acoustic Grand Piano, and we've told the audio library that we should use `AnExampleSound` as the voice of Acoustic Grand Piano. MIDI notes that use instrument `65` get associated with an instrument called `AnExampleAltoSax`. You'll want the value for `sampleRate` to match the frequency your sample files are tuned to. This is the reason we converted all of our samples to the same rate with SoX above. `percussionDefault` is explained in the following section. Note that you aren't required to have an `instrument` associated with every MIDI instrument number. If your song, for example, is only a solo piano piece then you'd only need to worry about an `instrument` for `0`. It's best to only include sounds for MIDI instruments that you need. Anything else is audio memory that could be better spent elsewhere, such as sound effects or higher-frequency samples. For more information, review the N64 SDK Documentation at [18.1.2.1](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-01) . Note that the example for `bank` in the manual says to use `program` for referencing an `instrument`. This isn't correct and the `ic` tool will give you an error if `program` is used. #### Percussion Sounds Percussion in MIDI is a bit unique in that [channel 10 is reserved for percussion](https://en.wikipedia.org/wiki/General_MIDI#Percussion) and that each note maps to a specific instrument. "Middle C" has a note number of 60 which is always a high bongo sound on the percussion channel. To accommodate this, we create a special `instrument` for percussive sounds. Each different instrument will have its own `sound`, `envelope`, and `keymap` that only covers its corresponding key. An example percussion setup for an Electric Base Drum (MIDI key `36`) might look like the following: keymap Percussive\_Bass\_Drum\_1Keymap { velocityMin = 0; velocityMax = 127; keyMin = 36; keyMax = 36; keyBase = 36; detune = 0; } sound Percussive\_Bass\_Drum\_1Sound { use ("electric\_bass\_drum\_sample.aifc"); pan = 64; volume = 127; keymap = Percussive\_Bass\_Drum\_1Keymap; envelope = SomeBassDrumEnvelope; } Which would then integrate into an example percussion `instrument`: instrument Percussion\_Kit { volume = 127; pan = 64; sound = Percussive\_Bass\_Drum\_1Sound; sound = Percussive\_Acoustic\_SnareSound; sound = Percussive\_Low\_TomSound; sound = Percussive\_Open\_Hi\_HatSound; sound = Percussive\_High\_Mid\_TomSound; sound = Percussive\_Crash\_Cymbal\_1Sound; sound = Percussive\_High\_TomSound; sound = Percussive\_Ride\_Cymbal\_1Sound; sound = Percussive\_High\_BongoSound; sound = Percussive\_Low\_BongoSound; sound = Percussive\_CabasaSound; sound = Percussive\_MaracasSound; sound = Percussive\_ShakerSound; } The `bank` would then set `percussionDefault` to be `Percussion_Kit`. #### N64 SDK Example Instrument Bank The N64 SDK has reference Instrument Banks at `ultra/usr/src/pr/assets/banks`. If you're ever stuck on how something should look or are getting errors with `ic`, they can be a helpful guide to see how things are done. #### Compiling the Instrument Bank We use the instrument compiler program (`ic`) to turn our Instrument Bank file into `.tbl` and `.ctl` files for running ingame. Run `ic` on your `.ins` file like the following: ic -OSongBank SongBank.ins Note that `SongBank` in this case whatever you called your `.ins` file. Also, note the lack of space before the `-O` argument. This seems to be correct for the tool. If your `.ins` file doesn't have any errors, you should see an output like this:  [](https://n64brew.dev/wiki/File:Ic_success.png "A bunch of garbled output from the instrument compiler, but no specific line numbers.") You should also then have `.tbl` and `.ctl` files in the same directory with the name you put before the `-O` parameter.  [](https://n64brew.dev/wiki/File:Ic_new_files.png "The CTL and TBL output files shown via the DIR command.") If there are syntax or other errors in your `.ins` file, you might get an error message like this:  [](https://n64brew.dev/wiki/File:Ic_error.png "The instrument compiler showing an error.") Even though the output looks garbled, try not to be discouraged! The final bit of output will show the line number of the error. The first place to look is often the associated line.  [](https://n64brew.dev/wiki/File:Screen_Shot_2020-10-06_at_10.05.28_PM.png "An Instrument Bank file with a missing semicolon on line 28/29.") In this case, the example error message was caused by a missing semicolon on line 28/29. Much like the C-family of programming languages, semicolons indicate the start/end of statements. If you're missing one, the instrument compiler might associate two lines as a whole. Be sure to check the lines above and below if you're not initially sure where the error might be. ### Finishing up Once we've completed the steps above, we should now have the following: * A `sbk` that consists of our converted/compressed MIDI sequences * `ctl` and `tbl` files for our samples We'll be including the above files into our ROM's spec file, then requesting the audio library to load and play them. Playing a song with NuSystem ---------------------------- TODO ### Linking the Audio Library TODO ### Setting up playback TODO ### Starting/stoping playback TODO Making an Instrument Bank for Sound Effects ------------------------------------------- TODO ### Looping #### Looping Compressed Audio TODO See [20.5](http://n64devkit.square7.ch/pro-man/pro20/20-05.htm#01) of the N64 SDK for more information on this. #### Looping Non-Compressed Audio As NuSystem is primarily compiled to use compressed sequenced audio, this is outside the scope of this article. However, [17.3.4](http://n64devkit.square7.ch/pro-man/pro17/17-03.htm#04) in the N64 SDK can help with non-compressed sequences. Retrieved from "[https://n64brew.dev/wiki/SGI\_Audio\_Tools?oldid=3975](https://n64brew.dev/wiki/SGI_Audio_Tools?oldid=3975) " --- # Partner-N64 - N64brew Wiki [](https://n64brew.dev/wiki/Partner-N64#) Partner-N64 =========== The **Partner-N64** (stylised as **PARTNER-N64**) is a development kit for real-time debugging developed by Kyoto Micro Computer. It was released in versions for use with Windows 9x, Window NT and SGI workstations. The development kits included 4 parts: * the Partner-N64 cartridge * a modified N64 console * an interface card * a parallel cable The interface card is designed to allow software running on the PC or SGI workstation to communicate with the Partner-N64 cartridge via the parallel cable. Versions based around PCI and 16-bit ISA were produced; there is some evidence that PCMCIA and 8-bit ISA versions were designed, but it is unknown whether any were ever sent out to developers. The interface card provides the software with a simple interface to communicate with the Partner-N64 cartridge, based around an 8-bit address bus and 8-bit data bus, with a small number of control signals. The card also provides 5V power to the cartridge, and the User's Guide suggests that it draws approximately 400mA; this has not been verified. The SGI version of the kit included an external box that connected to the workstation via a network cable instead of an internal card. The parallel cable is a 40-pin ribbon cable with keyed IDC connectors, similar to IDE. (Note that connectors from IDE cables sometimes block pin 20 as an additional form of keying, which is incompatible with the Partner-N64.) Contents -------- * [1 Connector Pinout](https://n64brew.dev/wiki/Partner-N64#Connector_Pinout) * [2 Notes](https://n64brew.dev/wiki/Partner-N64#Notes) * [3 The modified console](https://n64brew.dev/wiki/Partner-N64#The_modified_console) * [4 The cartridge](https://n64brew.dev/wiki/Partner-N64#The_cartridge) * [4.1 Jumpers](https://n64brew.dev/wiki/Partner-N64#Jumpers) * [4.2 Programming the CPLD](https://n64brew.dev/wiki/Partner-N64#Programming_the_CPLD) * [4.3 Registers exposed via the parallel connector](https://n64brew.dev/wiki/Partner-N64#Registers_exposed_via_the_parallel_connector) * [4.3.1 0x00 - N64\_ROM\_DATA](https://n64brew.dev/wiki/Partner-N64#0x00_-_N64_ROM_DATA) * [4.3.2 0x01 - N64\_ROM\_ADR0](https://n64brew.dev/wiki/Partner-N64#0x01_-_N64_ROM_ADR0) * [4.3.3 0x02 - N64\_ROM\_ADR1](https://n64brew.dev/wiki/Partner-N64#0x02_-_N64_ROM_ADR1) * [4.3.4 0x03 - N64\_ROM\_ADR2](https://n64brew.dev/wiki/Partner-N64#0x03_-_N64_ROM_ADR2) * [4.3.5 0x04 - N64\_ROM\_ADR3](https://n64brew.dev/wiki/Partner-N64#0x04_-_N64_ROM_ADR3) * [4.3.6 0x05 - N64\_MEM\_CTRL](https://n64brew.dev/wiki/Partner-N64#0x05_-_N64_MEM_CTRL) * [4.3.7 0x08 - N64\_MON\_ADR0](https://n64brew.dev/wiki/Partner-N64#0x08_-_N64_MON_ADR0) * [4.3.8 0x09 - N64\_MON\_ADR1](https://n64brew.dev/wiki/Partner-N64#0x09_-_N64_MON_ADR1) * [4.3.9 0x0A - N64\_MON\_DATA](https://n64brew.dev/wiki/Partner-N64#0x0A_-_N64_MON_DATA) * [4.3.10 0x10 - N64\_RX0 (R) / N64\_TX0 (W)](https://n64brew.dev/wiki/Partner-N64#0x10_-_N64_RX0_(R)_/_N64_TX0_(W)) * [4.3.11 0x11 - N64\_RX1 (R) / N64\_TX1 (W)](https://n64brew.dev/wiki/Partner-N64#0x11_-_N64_RX1_(R)_/_N64_TX1_(W)) * [4.3.12 0x12 - N64\_RX2 (R) / N64\_TX2 (W)](https://n64brew.dev/wiki/Partner-N64#0x12_-_N64_RX2_(R)_/_N64_TX2_(W)) * [4.3.13 0x13 - N64\_RX3 (R) / N64\_TX3 (W)](https://n64brew.dev/wiki/Partner-N64#0x13_-_N64_RX3_(R)_/_N64_TX3_(W)) * [4.3.14 0x14 - N64\_HSTAT (R) / N64\_HCTRL (W)](https://n64brew.dev/wiki/Partner-N64#0x14_-_N64_HSTAT_(R)_/_N64_HCTRL_(W)) * [4.3.15 0x18 - N64\_RTC0](https://n64brew.dev/wiki/Partner-N64#0x18_-_N64_RTC0) * [4.3.16 0x19 - N64\_RTC1](https://n64brew.dev/wiki/Partner-N64#0x19_-_N64_RTC1) * [4.3.17 0x1A - N64\_RTC2](https://n64brew.dev/wiki/Partner-N64#0x1A_-_N64_RTC2) * [4.3.18 0x1B - N64\_RTC3](https://n64brew.dev/wiki/Partner-N64#0x1B_-_N64_RTC3) * [4.3.19 0x1C - N64\_RTC\_CLR](https://n64brew.dev/wiki/Partner-N64#0x1C_-_N64_RTC_CLR) * [4.3.20 0x20 - N64\_MEM\_STAT (R) / N64\_MEM\_SIZE (W)](https://n64brew.dev/wiki/Partner-N64#0x20_-_N64_MEM_STAT_(R)_/_N64_MEM_SIZE_(W)) * [4.3.21 0x21 - N64\_PROT\_CTRL](https://n64brew.dev/wiki/Partner-N64#0x21_-_N64_PROT_CTRL) * [4.3.22 0xA0 - F\_CONFIG](https://n64brew.dev/wiki/Partner-N64#0xA0_-_F_CONFIG) * [4.4 Registers exposed to the N64](https://n64brew.dev/wiki/Partner-N64#Registers_exposed_to_the_N64) * [4.5 Reset](https://n64brew.dev/wiki/Partner-N64#Reset) * [4.6 Models](https://n64brew.dev/wiki/Partner-N64#Models) * [5 The ISA card](https://n64brew.dev/wiki/Partner-N64#The_ISA_card) ### Connector Pinout | | | | | | | --- | --- | --- | --- | --- | | Name | Pin | | Pin | Name | | D0 | 1 | | 2 | GND | | D1 | 3 | | 4 | GND | | D2 | 5 | | 6 | GND | | D3 | 7 | | 8 | GND | | D4 | 9 | | 10 | GND | | D5 | 11 | | 12 | GND | | D6 | 13 | | 14 | GND | | D7 | 15 | | 16 | GND | | A0 | 17 | | 18 | GND | | A1 | 19 | | 20 | GND | | A2 | 21 | | 22 | 5V | | A3 | 23 | | 24 | 5V | | A4 | 25 | | 26 | 5V | | A5 | 27 | | 28 | 5V | | DIR | 29 | | 30 | 5V | | CLK | 31 | | 32 | 5V | | A6 | 33 | | 34 | 5V | | A7 | 35 | | 36 | 5V | | Detect? | 37 | | 38 | N/C? | | N/C? | 39 | | 40 | N/C? | ### Notes * Pins 27, 29, 31, 33, 35 and 37 connect to the PAL chip rather than the CPLD (see below). * All logic levels are 5V. * When writing data from the host to the cartridge, DIR is set high, the address and data are asserted on the buses and then CLK is pulsed. * When reading data from the cartridge to the host, DIR is set low, the address is asserted on the bus and then the value is read from the data bus - CLK is _not_ pulsed. The modified console -------------------- The development kits included a modified N64 console. Three pins on the PI bus were disconnected and rerouted: | | | | | --- | --- | --- | | Pin | Usual function | New connection | | 24 | LAUDIO | CPU pin 28 (/INT4) | | 46 | VIDEO\_SYNC | Reset button | | 49 | RAUDIO | PIF pin 8 (enable/power good) | The cartridge ------------- The cartridge (called the **"ROM emulator"** in the User Guide; model number **N64-DEBUGER-1**\[_sic_\]) is based around an Altera FLEX 8000 CPLD (specifically an **EPF81188AQC208-4**) and an AMD PAL (specifically a **PALCE16V8H-15 PC/4**; this is socketed, and the equations can be dumped fairly easily). Program data is stored in a stick of standard 72-pin SIMM desktop memory; the CPLD decodes the signals from the N64 and either responds with data from the memory or presents a series of registers, similar to the RCP interfaces. During normal operation, communication via the parallel connector involves primarily the CPLD, with the PAL chip only used to control the direction of the bus transceiver. However, the Altera FLEX 8000 series chips require the bitstream to be programmed at power on every time, and the PAL chip additionally manages the control logic to allow programming via the parallel connector (see below). The board has the footprints for two different types of SRAM chip (of which only one can be populated at a time), as well as a socket for an EEPROM chip and a socket for a CIC chip. The CIC chip is present in all known cases, but the EEPROM chip was seemingly not used very often. A cartridge slot is placed at the top of the board, to allow a second N64 cartridge to be inserted. The only common uses of this slot were a cartridge containing font/sound data for the 64DD IPL and cartridges with particular save chips, but the User Guide also discusses reading data from the inserted cartridge into the Partner-N64 cartridge's memory. ### Jumpers There are several jumpers on the cartridge that control various pieces of functionality. | | | | | --- | --- | --- | | Name | Normally connected? | Use | | JP1 | Yes | Unknown (wired) | | JP2 | No | Passes cartridge pin 18 (CIC DIO) to the upper cartridge slot; if combined with JP4, this means the CIC chip in the socket can be removed | | JP3 | No | Passes cartridge pin 49 (PIF power good on a modified console) to the upper cartridge slot | | JP4 | No | Passes cartridge pin 43 (CIC DCLK) to the upper cartridge slot | | JP5 | Yes | Connects the NMI signal from the CPLD to cartridge pin 24 (CPU /INT4 on a modified console) | | JP6 | Yes | Connects the enable signal from the CPLD to cartridge pin 49 (PIF power good on a modified console) | | JP7 | No | Connects the NMI signal from the CPLD to cartridge pin 44 (CPU /INT1) | | JP8 | No | Connects the CPLD to cartridge pin 20 (/ColdReset) | | JP9 | Yes | Unknown | | JP10 | Yes | Connects CPLD DATA0 to the SROM and JP11 (see below) | | JP11 | No | Not a jumper, but rather a programming header (see below) | | JP12 | No | Pulls CPLD nSTATUS to VCC via a 4.7kΩ resistor (RA9) | | JP13 | No | Pulls CPLD nCONFIG to VCC via a 4.7kΩ resistor (RA9) | | JP14 | No | Pulls CPLD DCLK to VCC via a 4.7kΩ resistor (RA9) | | JP15 | No | Unknown | | JP16 | Yes | Unknown (wired) | ### Programming the CPLD The CPLD must be programmed with its bitstream before it operates correctly. The WPTN64 tool included with the development kit software performs this programming as part of its operation, reading the complete bitstream from a file called **N64DEB1.SEQ**. In order to get the CPLD to enter programming mode, WPTN64 writes 0x02 and then 0x00 to address 0xA0. This has the effect of pulling the nCONFIG pin on the CPLD low briefly. Then, the bitstream is written, one bit at a time, by writing 0x00 or 0x01 to address 0xA0. The software reads from address 0xA0 and inspects bit 1 (mask with 0x02) for nSTATUS and bit 2 (mask with 0x04) for CONF\_DONE. See the Altera FLEX 8000 configuration manual[\[1\]](https://n64brew.dev/wiki/Partner-N64#cite_note-1) for more information; the Partner-N64 uses the 'bit-wide passive serial' programming mode. This programming mode also allows programming via a specialised programming adaptor and download cable, detailed in the configuration manual. This adaptor connects via JP11, which is not a jumper but a 2×5 pin header (not known to be populated on any Partner-N64 board). The board also has capacity for the related 'active serial' programming mode. This uses a specialised 'configuration EPROM', e.g. **EPC1213**, connected to the same pins on the CPLD, to program the CPLD automatically with no external input required. A socket labelled 'SROM' is marked on the silkscreen but not populated on any known Partner-N64 board, correctly wired to support this configuration mode. Presumably, one of the unknown jumpers configures the MSEL1 pin on the CPLD for this mode, but more work needs to be done to confirm this. In order to support autonomous programming with no PC connected, the board has spaces for components to convert the 12V supplied via the cartridge port into 5V to power the upper half of the board, but these components are not populated on any known Partner-N64 cartridge. ### Registers exposed via the parallel connector Except where noted, these names come from the partial source code included by accident in the version of WPTN64.EXE in version 1.0 of the development kit software. **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 \* = Guessed name (source code missing) #### 0x00 - N64\_ROM\_DATA * * * | N64\_ROM\_DATA\* `0x00` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | Data | | | | | | | | | | | | --- | --- | | bit 7-0 | **Data\[7:0\]:** Data written to or read from the virtual ROM address in N64\_ROM\_ADRn | #### 0x01 - N64\_ROM\_ADR0 * * * | N64\_ROM\_ADR0\* `0x01` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | ROMAddr\[7:0\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **ROMAddr\[7:0\]:** 8 bits of the virtual ROM address | #### 0x02 - N64\_ROM\_ADR1 * * * | N64\_ROM\_ADR1\* `0x02` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | ROMAddr\[15:8\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **ROMAddr\[15:8\]:** 8 bits of the virtual ROM address | #### 0x03 - N64\_ROM\_ADR2 * * * | N64\_ROM\_ADR2\* `0x03` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | ROMAddr\[23:16\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **ROMAddr\[23:16\]:** 8 bits of the virtual ROM address | #### 0x04 - N64\_ROM\_ADR3 * * * | N64\_ROM\_ADR3\* `0x04` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | \- | ROMAddr\[30:24\] | | | | | | | | | | | --- | --- | | bit 7 | **Unknown:** TODO: figure out what this does | | bit 6-0 | **ROMAddr\[30:24\]:** 7 bits of the virtual ROM address | #### 0x05 - N64\_MEM\_CTRL * * * | N64\_MEM\_CTRL `0x05` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | U-? | U-? | U-? | U-? | U-? | W-? | W-? | W-? | | \- | \- | \- | \- | \- | N64\_DRAM\_WR\_PROTECT\* | \- | \- | | | | | --- | --- | | bit 2 | **N64\_DRAM\_WR\_PROTECT\*:** TODO: figure out whether this bit enables or disables write-protect | | bit 1-0 | **Unknown:** These two bits are N64\_DRAM\_RD\_DISABLE \| N64\_EXT\_RD\_ENABLE, but it's not yet clear which is which | #### 0x08 - N64\_MON\_ADR0 * * * | N64\_MON\_ADR0\* `0x08` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | MonAdr\[7:0\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **MonAdr\[7:0\]:** 8 bits of the virtual monitor address | #### 0x09 - N64\_MON\_ADR1 * * * | N64\_MON\_ADR1 `0x09` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | N64\_MAG | MonAdr\[14:8\] | | | | | | | | | | | --- | --- | | bit 7 | **N64\_MAG:** Unknown | | bit 6-0 | **MonAdr\[14:8\]:** 7 bits of the virtual monitor address | #### 0x0A - N64\_MON\_DATA * * * | N64\_MON\_DATA\* `0x0A` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | Data | | | | | | | | | | | | --- | --- | | bit 7-0 | **Data\[7:0\]:** Data written to or read from the virtual monitor address in N64\_MON\_ADRn | #### 0x10 - N64\_RX0 (R) / N64\_TX0 (W) * * * **When Reading:** | N64\_RX0 `0x10` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | RXData\[7:0\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **RXData\[7:0\]:** 8 bits of data received via RS-232 | **When Writing:** | N64\_TX0 `0x10` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | TXData\[7:0\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **TXData\[7:0\]:** 8 bits of data to be transmitted via RS-232 | #### 0x11 - N64\_RX1 (R) / N64\_TX1 (W) * * * **When Reading:** | N64\_RX1 `0x11` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | RXData\[15:8\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **RXData\[15:8\]:** 8 bits of data received via RS-232 | **When Writing:** | N64\_TX1 `0x11` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | TXData\[15:8\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **TXData\[15:8\]:** 8 bits of data to be transmitted via RS-232 | #### 0x12 - N64\_RX2 (R) / N64\_TX2 (W) * * * **When Reading:** | N64\_RX2 `0x12` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | RXData\[23:16\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **RXData\[23:16\]:** 8 bits of data received via RS-232 | **When Writing:** | N64\_TX2 `0x12` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | TXData\[23:16\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **TXData\[23:16\]:** 8 bits of data to be transmitted via RS-232 | #### 0x13 - N64\_RX3 (R) / N64\_TX3 (W) * * * **When Reading:** | N64\_RX3 `0x13` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | RXData\[31:24\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **RXData\[31:24\]:** 8 bits of data received via RS-232 | **When Writing:** | N64\_TX3 `0x13` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | TXData\[31:24\] | | | | | | | | | | | | --- | --- | | bit 7-0 | **TXData\[31:24\]:** 8 bits of data to be transmitted via RS-232 | #### 0x14 - N64\_HSTAT (R) / N64\_HCTRL (W) * * * **When Reading:** | N64\_HSTAT `0x14` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | U-? | U-? | U-? | U-? | U-? | R-? | R-? | R-? | | \- | \- | \- | \- | \- | N64\_TVCC | N64\_RX\_RDY | N64\_TX\_RDY | | | | | --- | --- | | bit 2 | **N64\_TVCC:** 1 if 3.3V is being provided by the N64 (i.e. if the N64 is switched on) | | bit 1 | **N64\_RX\_RDY:** Unknown | | bit 0 | **N64\_TX\_RDY:** Unknown | **When Writing:** | N64\_HCTRL `0x14` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | U-? | U-? | U-? | U-? | U-? | W-? | W-? | W-? | | \- | \- | \- | \- | \- | N64\_NMIP | N64\_NMI | N64\_RESET | | | | | --- | --- | | bit 2 | **N64\_NMIP:** Unknown (but probably related to pin 49 on the cartridge connector) | | bit 1 | **N64\_NMI:** Unknown (but probably related to pin 24 on the cartridge connector) | | bit 0 | **N64\_RESET:** Unknown (but probably related to pin 46 on the cartridge connector) | #### 0x18 - N64\_RTC0 * * * | N64\_RTC0 `0x18` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | \- | | | | | | | | | | | | --- | --- | | bit 7-0 | **\-:** Unknown | #### 0x19 - N64\_RTC1 * * * | N64\_RTC1 `0x19` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | \- | | | | | | | | | | | | --- | --- | | bit 7-0 | **\-:** Unknown | #### 0x1A - N64\_RTC2 * * * | N64\_RTC2 `0x1A` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | \- | | | | | | | | | | | | --- | --- | | bit 7-0 | **\-:** Unknown | #### 0x1B - N64\_RTC3 * * * | N64\_RTC3 `0x1B` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | \- | | | | | | | | | | | | --- | --- | | bit 7-0 | **\-:** Unknown | #### 0x1C - N64\_RTC\_CLR * * * | N64\_RTC\_CLR `0x1C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | \- | | | | | | | | | | | | --- | --- | | bit 7-0 | **\-:** Unknown | #### 0x20 - N64\_MEM\_STAT (R) / N64\_MEM\_SIZE (W) * * * **When Reading:** | N64\_MEM\_STAT `0x20` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | N64\_FLEX\_REV | | | | | Size | | | | | | | --- | --- | | bit 7-3 | **N64\_FLEX\_REV:** Expected to be 1 in version 1.0, unknown in other versions | | bit 2-0 | **Size:** Emulated ROM size: 0 = 4MB, 1 = 32MB, 2 = 16MB, 3 = 8MB, 4 = 64MB | **When Writing:** | N64\_MEM\_SIZE `0x20` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | U-? | U-? | U-? | U-? | U-? | W-? | W-? | W-? | | \- | | | | | | | | | | | | --- | --- | | bit 7-0 | **\-:** Unknown | #### 0x21 - N64\_PROT\_CTRL * * * **When Reading:** | N64\_PROT\_CTRL `0x21` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | R-? | R-? | | \- | \- | \- | \- | \- | \- | N64\_OVR\_BRK\* | N64\_RWR\_BRK\* | | | | | --- | --- | | bit 1 | **N64\_OVR\_BRK\*:** Break occurred because of read outside of virtual ROM | | bit 0 | **N64\_RWR\_BRK\*:** Break occurred because of write to ROM area | **When Writing:** | N64\_PROT\_CTRL `0x21` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | W-? | W-? | | \- | \- | \- | \- | \- | \- | N64\_OVR\_BRK\_ENB | N64\_RWR\_BRK\_ENB | | | | | --- | --- | | bit 1 | **N64\_OVR\_BRK\_ENB:** Enable break on read outside of virtual ROM | | bit 0 | **N64\_RWR\_BRK\_ENB:** Enable break on write to ROM area | #### 0xA0 - F\_CONFIG * * * **When Reading:** | F\_CONFIG `0xA0` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | U-? | U-? | U-? | U-? | U-? | R-? | R-? | U-? | | \- | \- | \- | \- | \- | F\_STAT\_CONF\_DONE | F\_STAT\_nSTAT | \- | | | | | --- | --- | | bit 2 | **F\_STAT\_CONF\_DONE:** CONF\_DONE pin from CPLD | | bit 1 | **F\_STAT\_nSTAT:** nSTATUS pin from CPLD | **When Writing:** | F\_CONFIG `0xA0` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | W-? | W-? | | \- | \- | \- | \- | \- | \- | F\_CONF\_nCONFIG | F\_CONF\_D0 | | | | | --- | --- | | bit 1 | **F\_CONF\_nCONFIG:** Value to set the nCONFIG pin on the CPLD to (**note: inverted**) | | bit 0 | **F\_CONF\_D0:** Bit to write to the DATA0 pin on the CPLD | ### Registers exposed to the N64 The CPLD exposes an interface to allow N64 code to interact with the card. This interface is currently undocumented. ### Reset The capability to reset the console is included in the Partner-N64 software. This is achieved by pulling pin 46 of the cartridge connector to GND, which on a modified console is connected to the reset switch. This functionality is not present in the cartridge's PCB as manufactured, but was added with a manual bodge connecting CP4 (pin 198 of the CPLD) to pin 46 of the cartridge connector via the board's open-collector inverter. ### Models The Partner-N64 label allows each one to be marked with either 10 or 11 and a letter from A to Z. According to BehindTheCode[\[2\]](https://n64brew.dev/wiki/Partner-N64#cite_note-2) , 10 indicates 16MB of included memory and 11 indicates 32MB. The meaning of the letter has not yet been identified, but it may indicate the hardware revision, since B cartridges seem to contain the reset bodge while A cartridges do not. (Hence, A cartridges are unable to reset the console.) The ISA card ------------ **Note: this information is based on studying photographs of the card, and so is very incomplete and may contain errors.** The card is based around an Altera FLEX 8000 CPLD, similarly to the cartridge. It uses a different model, specifically an **EPF8282ALC84-4**. Unlike the cartridge, it is configured for active serial programming from a configuration EPROM. The silkscreen designates this as IC10 and its contents are currently undumped. The board contains a large number of jumpers. 1. [↑](https://n64brew.dev/wiki/Partner-N64#cite_ref-1) [https://www.artisantg.com/info/Altera\_FLEX\_8000\_Manual\_202131611294.pdf](https://www.artisantg.com/info/Altera_FLEX_8000_Manual_202131611294.pdf) 2. [↑](https://n64brew.dev/wiki/Partner-N64#cite_ref-2) [https://youtu.be/cg6VnH2J1dk?t=269](https://youtu.be/cg6VnH2J1dk?t=269) Retrieved from "[https://n64brew.dev/wiki/Partner-N64?oldid=5731](https://n64brew.dev/wiki/Partner-N64?oldid=5731) " --- # Building GCC - N64brew Wiki [](https://n64brew.dev/wiki/Building_GCC#) Building GCC ============ You can build your own toolchain with GCC, Binutils, and Newlib (optional). This is not difficult but it is tedious, and it takes several minutes to compile GCC, which can make it very time-consuming to experiment with different compilation flags. Also see: [OSDev: GCC Cross-Compiler](https://wiki.osdev.org/GCC_Cross-Compiler) . If you are using libdragon, refer to [these instructions](https://github.com/DragonMinded/libdragon/wiki/Installing-libdragon) instead of this page. Contents -------- * [1 Overview](https://n64brew.dev/wiki/Building_GCC#Overview) * [1.1 ABI](https://n64brew.dev/wiki/Building_GCC#ABI) * [1.2 Target Tuples](https://n64brew.dev/wiki/Building_GCC#Target_Tuples) * [1.3 Out of Tree Builds](https://n64brew.dev/wiki/Building_GCC#Out_of_Tree_Builds) * [2 Preparation](https://n64brew.dev/wiki/Building_GCC#Preparation) * [2.1 Sources](https://n64brew.dev/wiki/Building_GCC#Sources) * [2.2 PATH Variable](https://n64brew.dev/wiki/Building_GCC#PATH_Variable) * [2.3 Running Make](https://n64brew.dev/wiki/Building_GCC#Running_Make) * [2.4 Prerequisites](https://n64brew.dev/wiki/Building_GCC#Prerequisites) * [3 Building Binutils](https://n64brew.dev/wiki/Building_GCC#Building_Binutils) * [4 Building GCC, Part 1](https://n64brew.dev/wiki/Building_GCC#Building_GCC,_Part_1) * [5 Building Newlib](https://n64brew.dev/wiki/Building_GCC#Building_Newlib) * [6 Building GCC Part 2](https://n64brew.dev/wiki/Building_GCC#Building_GCC_Part_2) * [7 Done](https://n64brew.dev/wiki/Building_GCC#Done) Overview -------- A working toolchain contains: * [Binutils](https://www.gnu.org/software/binutils/) , which contains the linker `ld`, assembler `as`, and other tools like `objdump` and `objcopy`. * [GCC](https://gcc.gnu.org/) , the compiler. This will provide certain headers like ``, ``, and `` that define types but don’t contain library functions. * (Optional) A standard library, like [Newlib](https://sourceware.org/newlib/) , which contains headers like `` and `` that define library functions. These instructions install the toolchain in `/opt/n64`, but you can choose any path. ### ABI These instructions build GCC for the “o32” ABI, which you get by compiling with `-mabi=32`. This is the old 32-bit ABI, and it’s the ABI used by LibUltra / NuSys. If you are using something other than LibUltra, you will need to build GCC with different flags. ### Target Tuples A “target tuple” identifies a system where code can run. The general format is “`cpu-vendor-os`”. For example, the target tuple for an x86 Linux system might be `x86_64-pc-linux-gnu`, and a Mac might be `x86_64-apple-darwin18.7.0`. For the Nintendo 64, we are going to use `mips32-elf` as the target tuple, which the build scripts will automatically expand to `mips64-unknown-elf` (MIPS CPU, unknown vendor, generic ELF OS). The target tuple is used by the toolchain build scripts to configure settings like build architecture, ABI, the format for compiled objects, linker scripts, and macros predefined by the compiler. ### Out of Tree Builds The toolchain is not designed to be built in the source directory. In other words, you cannot run `./configure` and `make` inside the Binutils or GCC source directory, you have to create separate folders for the build. Preparation ----------- ### Sources Download the latest version of Binutils, GCC, and Newlib. As of November 2020, the latest versions are Binutils 2.35.1, GCC 10.2, and Newlib 3.3.0. ### PATH Variable Add `/opt/n64/bin` to your `PATH` environment variable before starting the build. $ PATH=/opt/n64/bin:$PATH ### Running Make When you run `make`, pass a `-j` option with the number of cores your computer has. If your computer has four cores, pass `-j4`. This guide will use `-j4` everywhere. ### Prerequisites The following software must be installed: * GMP * MPFR * MPC * GNU Sed On a Debian/Ubuntu system, run: $ sudo apt install build-essential libgmp-dev libmpfr-dev libmpc-dev On macOS, using Homebrew: $ brew install gmp mpfr libmpc gnu-sed $ PATH=/usr/local/opt/gnu-sed/libexec/gnubin:$PATH Building Binutils ----------------- Extract and build Binutils. $ tar xvf binutils-2.35.1.tar.xz $ mkdir build.binutils $ cd build.binutils $ ../binutils-2.35.1/configure --target=mips32-elf --prefix=/opt/n64 --with-cpu=vr4300 --disable-nls --with-sysroot=/opt/n64/mips32-elf/sysroot $ make -j4 $ sudo make install Building GCC, Part 1 -------------------- Extract and build GCC. Don’t delete the build directory, because it will be needed later. Check that you have GNU Sed and not some other version. If you run `sed --version`, it should print out “GNU sed”. Without GNU sed, the makefiles for GCC will be generated incorrectly, and you will get mysterious build errors. $ tar xvf gcc-10.2.0.tar.xz $ mkdir build.gcc $ cd build.gcc $ ../gcc-10.2.0/configure --target=mips32-elf --prefix=/opt/n64 --with-languages=c,c++ --disable-shared --disable-threads --disable-nls --without-headers --disable-multilib --with-newlib --with-sysroot=/opt/n64/mips32-elf/sysroot --with-arch=vr4300 --with-abi=32 $ make -j4 all-gcc $ sudo make install-gcc Building Newlib --------------- Extract and build Newlib. $ tar xvf newlib-3.3.0.tar.gz $ mkdir build.newlib $ cd build.newlib $ ../newlib-3.3.0/configure --prefix=/usr --target=mips32-elf --disable-threads --disable-libssp CFLAGS\_FOR\_TARGET='-march=vr4300 -mfix4300 -G 0' $ make -j4 The installation will not work correctly without the path set, and the sudo will ignore any changes to PATH, so you have to put the PATH inside the sudo command. Ignore this if you are installing without sudo. $ sudo sh -c 'PATH=/opt/n64/bin:$PATH; make DESTDIR=/opt/n64/mips32-elf/sysroot install' Newlib installs itself in not exactly the place it needs to be, so move the installed files to the correct directory: $ cd /opt/n64/mips32-elf/sysroot/usr $ sudo mv mips32-elf/\* . $ sudo rmdir mips32-elf Building GCC Part 2 ------------------- GCC also includes LibGCC, which must also be built. This contains low-level functions that may be needed by code you compile with GCC even if you don’t explicitly call functions. $ cd build.gcc $ make -j4 all-target-libgcc $ sudo make install-target-libgcc Done ---- You can now run GCC as `mips32-elf-gcc`. Retrieved from "[https://n64brew.dev/wiki/Building\_GCC?oldid=5874](https://n64brew.dev/wiki/Building_GCC?oldid=5874) " --- # Game Pak - N64brew Wiki [](https://n64brew.dev/wiki/Game_Pak#) Game Pak ======== Nintendo 64 **Game Pak** (part number NUS-006) is the brand name of the consumer ROM cartridge product that stores game data for the Nintendo 64, released in 1996. As with Nintendo's previous consoles, the Game Pak's design tradeoffs were intended to achieve maximal system speed and minimal base console cost, with a lesser storage space and a higher unit cost per game. Integrating a CD-ROM drive, with its expensive and slow moving parts, would have drastically increased the console's base price and reduced its performance. See [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") for the standard header contents found in every Game Pak ROM. ### Connector Pinout | | | | | | | --- | --- | --- | --- | --- | | Name | Pin | | Pin | Name | | GND | 1 | | 26 | GND | | GND | 2 | | 27 | GND | | AD15 | 3 | | 28 | AD0 | | AD14 | 4 | | 29 | AD1 | | AD13 | 5 | | 30 | AD2 | | GND | 6 | | 31 | GND | | AD12 | 7 | | 32 | AD3 | | /WR | 8 | | 33 | ALE\_L | | 3.3V | 9 | | 34 | 3.3V | | /RD | 10 | | 35 | ALE\_H | | AD11 | 11 | | 36 | AD4 | | AD10 | 12 | | 37 | AD5 | | 12V | 13 | | 38 | 12V | | 12V | 14 | | 39 | 12V | | AD9 | 15 | | 40 | AD6 | | AD8 | 16 | | 41 | AD7 | | 3.3V | 17 | | 42 | 3.3V | | CIC\_15 | 18 | | 43 | CIC\_14 | | 1.95MHz\_CLK | 19 | | 44 | /INT1 | | /ColdReset | 20 | | 45 | /NMI | | EEPROM\_DAT | 21 | | 46 | VIDEO\_SYNC | | GND | 22 | | 47 | GND | | GND | 23 | | 48 | GND | | LAUDIO | 24 | | 49 | RAUDIO | | GND | 25 | | 50 | GND | ### Notes * Pins 14 & 39 on the cartridge connector are missing contacts, thus these pins only apply to the EXT connector on the bottom of the console. * Pin 18 is bi-directional data between the CIC and PIF. * Pin 19 is a ~1.95 MHz clock driven by the PIF, used for both the CIC's CPU clock (pin 11) and EEPROM communication. * Pin 21 is PIF Channel 5, via PIF pins 23 & 24. * Pin 43 is the clock associated with pin 18's data, and is always driven by the PIF. It only pulses when the PIF needs to send or receive data. Retrieved from "[https://n64brew.dev/wiki/Game\_Pak?oldid=5144](https://n64brew.dev/wiki/Game_Pak?oldid=5144) " --- # Rumble Pak - N64brew Wiki [](https://n64brew.dev/wiki/Rumble_Pak#) Rumble Pak ========== The Rumble Pak (Japanese: 振動パック, Hepburn: Shindō Pakku) is a removable device from Nintendo which provides force feedback while playing video games. On the Nintendo 64, the Rumble Pak plugs into the bottom of the controller. Games that support the Rumble Pak cause it to vibrate in select situations, such as when firing a weapon or receiving damage, to immerse the player in the game. Versions of the Rumble Pak are available for the Nintendo 64, the Nintendo DS, and the Nintendo DS Lite. A select few Game Boy Color and Game Boy Advance games use a similar technology built into the game cartridge. Force feedback vibration has become a built-in standard feature in almost every home video game console controller since. Hardware -------- The OEM Rumble Pak contains a custom chip labelled VDEC-CNT, with this pinout: +CE -> |1 18| +3 /OE -> |2 17| ?? capacitor /WE -> |3 16| ?? n/c A14 -> |4 15| -> motor via BJT A15 -> |5 14| <> D7 D0 <> |6 13| <> D6 D1 <> |7 12| <> D5 D2 <> |8 11| <> D4 Gnd -- |9 10| <> D3 Software -------- When accessing the Rumble Pak via the [joybus](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") protocol, the accessory presents the following memory map: | | | | | | --- | --- | --- | --- |OEM (official) Rumble Pak memory map | Address | Direction | Description | Details | | 0x8000 - 0xBFFF | Read | Identification | 0x80 if unlocked, 0x00 otherwise | | Write | Unlock | 0x80 to unlock, all others lock | | 0xC000 - 0xFFFF | Read | Rumble status | odd numbers enabled, even numbers disabled | | Write | Rumble enable | Retrieved from "[https://n64brew.dev/wiki/Rumble\_Pak?oldid=5769](https://n64brew.dev/wiki/Rumble_Pak?oldid=5769) " --- # Video Interface - N64brew Wiki [](https://n64brew.dev/wiki/Video_Interface#) Video Interface =============== The Video Interface (or **VI**) is one of multiple I/O interfaces in the [RCP](https://n64brew.dev/wiki/Reality_Coprocessor "Reality Coprocessor") , which configures different parts of the console's video rendering and output. It provides significant flexibility to support NTSC, PAL, and M-PAL, using the same chips and registers. All memory-mapped registers are 32 bits wide and should always be written a full word (32 bits) at a time. Contents -------- * [1 Video DAC](https://n64brew.dev/wiki/Video_Interface#Video_DAC) * [2 Video Standards](https://n64brew.dev/wiki/Video_Interface#Video_Standards) * [3 Hardware](https://n64brew.dev/wiki/Video_Interface#Hardware) * [4 Configuration Registers](https://n64brew.dev/wiki/Video_Interface#Configuration_Registers) * [4.1 0x0440 0000 - VI\_CTRL](https://n64brew.dev/wiki/Video_Interface#0x0440_0000_-_VI_CTRL) * [4.2 0x0440 0004 - VI\_ORIGIN](https://n64brew.dev/wiki/Video_Interface#0x0440_0004_-_VI_ORIGIN) * [4.3 0x0440 0008 - VI\_WIDTH](https://n64brew.dev/wiki/Video_Interface#0x0440_0008_-_VI_WIDTH) * [4.4 0x0440 000C - VI\_V\_INTR](https://n64brew.dev/wiki/Video_Interface#0x0440_000C_-_VI_V_INTR) * [4.5 0x0440 0010 - VI\_V\_CURRENT](https://n64brew.dev/wiki/Video_Interface#0x0440_0010_-_VI_V_CURRENT) * [4.6 0x0440 0014 - VI\_BURST](https://n64brew.dev/wiki/Video_Interface#0x0440_0014_-_VI_BURST) * [4.7 0x0440 0018 - VI\_V\_TOTAL](https://n64brew.dev/wiki/Video_Interface#0x0440_0018_-_VI_V_TOTAL) * [4.8 0x0440 001C - VI\_H\_TOTAL](https://n64brew.dev/wiki/Video_Interface#0x0440_001C_-_VI_H_TOTAL) * [4.9 0x0440 0020 - VI\_H\_TOTAL\_LEAP](https://n64brew.dev/wiki/Video_Interface#0x0440_0020_-_VI_H_TOTAL_LEAP) * [4.10 0x0440 0024 - VI\_H\_VIDEO](https://n64brew.dev/wiki/Video_Interface#0x0440_0024_-_VI_H_VIDEO) * [4.11 0x0440 0028 - VI\_V\_VIDEO](https://n64brew.dev/wiki/Video_Interface#0x0440_0028_-_VI_V_VIDEO) * [4.12 0x0440 002C - VI\_V\_BURST](https://n64brew.dev/wiki/Video_Interface#0x0440_002C_-_VI_V_BURST) * [4.13 0x0440 0030 - VI\_X\_SCALE](https://n64brew.dev/wiki/Video_Interface#0x0440_0030_-_VI_X_SCALE) * [4.13.1 Errata](https://n64brew.dev/wiki/Video_Interface#Errata) * [4.14 0x0440 0034 - VI\_Y\_SCALE](https://n64brew.dev/wiki/Video_Interface#0x0440_0034_-_VI_Y_SCALE) * [4.14.1 Erratum](https://n64brew.dev/wiki/Video_Interface#Erratum) * [4.15 0x0440 0038 - VI\_TEST\_ADDR](https://n64brew.dev/wiki/Video_Interface#0x0440_0038_-_VI_TEST_ADDR) * [4.16 0x0440 003C - VI\_STAGED\_DATA](https://n64brew.dev/wiki/Video_Interface#0x0440_003C_-_VI_STAGED_DATA) * [5 Fixed-Point Format](https://n64brew.dev/wiki/Video_Interface#Fixed-Point_Format) * [6 How to use this information](https://n64brew.dev/wiki/Video_Interface#How_to_use_this_information) * [6.1 Interlace Mode](https://n64brew.dev/wiki/Video_Interface#Interlace_Mode) * [6.1.1 High Resolution Mode](https://n64brew.dev/wiki/Video_Interface#High_Resolution_Mode) * [6.1.2 Improve visible detail in low resolution](https://n64brew.dev/wiki/Video_Interface#Improve_visible_detail_in_low_resolution) * [6.2 60 Frames per second in Low Resolution mode](https://n64brew.dev/wiki/Video_Interface#60_Frames_per_second_in_Low_Resolution_mode) * [6.2.1 Letter Boxing](https://n64brew.dev/wiki/Video_Interface#Letter_Boxing) * [6.3 Pillar Boxing](https://n64brew.dev/wiki/Video_Interface#Pillar_Boxing) * [6.4 Reduce both Height and Width](https://n64brew.dev/wiki/Video_Interface#Reduce_both_Height_and_Width) Video DAC ========= The Video Interface emits a 28-bit signal, multiplexed over a 7-bit bus clocked at roughly 49MB/sec, with one extra signal ("DSYNC") providing framing. This is received by the [Video DAC](https://n64brew.dev/wiki/Video_DAC "Video DAC") , which converts this into standard-definition video. Since the bus is multiplexed 4 ways, the resulting pixel clock is 1/4 the data rate, roughly 12.3 megapixels/sec. This is close to ideal for 640 pixels of video in the NTSC or PAL active area. For more details about the bus contents, see [Video DAC](https://n64brew.dev/wiki/Video_DAC "Video DAC") Video Standards =============== The video standards send more than just pixel data both on each line and with additional lines at the top and bottom of a frame. Hardware ======== Most of the Video Interface is implemented inside the RCP (Reality CoProcessor), although there is a Video DAC (Digital Analog Converter) on the mainboard, and another encoder IC (ENC-NUS) which which appears to manage some of the signal differences between Composite and S-Video Output. Configuration Registers ======================= Memory mapped registers are used to configure the Video Interface. The base address for these registers is `0x0440 0000`, also known as VI\_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 `0xA000 0000` to the address. As an example, to directly write to the VI\_CTRL register, use address `0xA440 0000`. **Table Notation:** R = Readable bit W = Writable bit U = Undefined/Unused bit -n = Default value n at power on = Specifies bits x to y, inclusively #### 0x0440 0000 - VI\_CTRL * * * | VI\_CTRL `0x0440 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | | — | — | — | — | — | — | — | DEDITHER\_FILTER\_ENABLE | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | U-0 | RW-0 | RW-0 | | PIXEL\_ADVANCE\[3:0\] | | | | KILL\_WE | — | AA\_MODE\[1:0\] | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | TEST\_MODE | SERRATE | VBUS\_CLOCK\_ENABLE | DIVOT\_ENABLE | GAMMA\_ENABLE | GAMMA\_DITHER\_ENABLE | TYPE\[1:0\] | | | | | | --- | --- | | bit 31-17 | **Undefined:** Initialized to `0` | | bit 16 | **DEDITHER\_ENABLE:** Dedither Enable bit
1 = Dedithering (aka "dither filter") is enabled; normally used for 16-bit framebuffers to try to reconstruct a 32-bit image. Notice that this filter only works correctly when `AA_MODE` is set to either `REPLICATE` or `AA_ALWAYS`.
0 = Dedithering is disabled (normally used for 32-bit color) | | bit 15-12 | **PIXEL\_ADVANCE\[3:0\]:** Use `0b0011` for most effective behavior on N64. On the iQue Player a pixel advance of `0b0011` creates video glitches, applications typically use `0b0001` instead. | | bit 11 | **KILL\_WE:** Diagnostics only, possibly kills VI DMA writes to line buffers making them safe to access via the test registers. | | bit 10 | **Undefined:** Initialized to `0` | | bit 9-8 | **AA\_MODE\[1:0\]:** Anti-Alias Mode
11 / REPLICATE = AA and resampling disabled, replicate pixels without interpolation
10 / RESAMPLE = AA disabled, resampling enabled, and operate as if everything is covered
01 / AA\_NEEDED = AA enabled, resampling enabled, and only fetches extra lines as needed
00 / AA\_ALWAYS = AA enabled, resampling enabled, and will always fetch extra lines. | | bit 7 | **TEST\_MODE:** Diagnostics only, enables usage of the line buffer test registers VI\_TEST\_ADDR/VI\_STAGED\_DATA. KILL\_WE should also be set to avoid access races between the VI and CPU. | | bit 6 | **SERRATE:** Required if interlacing, permitted when progressive, often disabled
1 = Enabled
0 = Disabled | | bit 5 | **VBUS\_CLOCK\_ENABLE:** Vbus Clock Enable
1 = Enabled
0 = Disabled
    _**Warning: Always leave disabled!** Setting this bit enables a second driver, which will output on the same pin as another driver, possibly causing physical console damage._ | | bit 4 | **DIVOT\_ENABLE:** Fixes minor artifacts left over from anti-aliasing (more details below)
1 = Enabled (usually used if AA is enabled)
0 = Disabled | | bit 3 | **GAMMA\_ENABLE:** Fixes non-linear gamma in TV screens (more details below)
1 = Enabled
0 = Disabled | | bit 2 | **GAMMA\_DITHER\_ENABLE:** Adds randomized noise to the video output, in the least significant bits to remove mach banding artifacts
1 = Enabled (usually set unless banding artifacts are desired for extra effect)
0 = Disabled | | bit 1-0 | **TYPE\[1:0\]:** Video pixel size, also known as color bit depth
11 = 8/8/8/8 (32 bit color)
10 = 5/5/5/3 (16 bit color, technically 18 bits wide)
01 = reserved
00 = Turned off (no data and no sync, TV screens will either show static or nothing) | **Extra Details:** **DEDITHER\_ENABLE** When enabled, the VI will run a de-dithering algorithm, trying to reverse the effects of dithering on each pixel to produce an higher resolution color information on the analog output. This is useful when the framebuffer is 16-bit and has been dithered while drawing. To do so, VI looks at the 8 neighbors around each pixel and perform an error correction; the algorithm used works best with images that have been dithered using the "Magic Square" dithering algorithm (that the RDP can be configured to do). The VI does de-dedithering only on pixels where coverage is full; on pixels with partial coverage, the standard AA algorithm is performed. **NOTE**: this filter requires `AA_MODE` to be set to either `AA_ALWAYS` (always fetch extra lines) or `REPLICATE` (no AA/resampling); the other two configurations are not compatible, and the image becomes corrupted by vertical streaks ([as seen here](https://github.com/DragonMinded/libdragon/issues/159) ). **DIVOT\_ENABLE** When enabled, this feature fixes artifacts that the anti-aliasing algorithm leaves behind. The median color of three neighboring pixels, from any pixels on or next to silhouette edges, is selected to be displayed in place of the center pixel. Effectively removing any one pixel divots that can be seen in some fractal-based terrains. The anti-aliasing function encounters issues when multiple fragments occur on a single pixel. Since this filter is only used on edges, and not the surface of an object, texture details will not be affected. Be aware that bad quality effects can occur when the _decal line_ rendering mode is used in conjunction with this filter, as the rendering mode generates edges that the filter can detect. **GAMMA\_ENABLE** This feature is used to correct non-linear gamma found in TV screens (although this may have changed in modern TV's). To do this, the feature square roots the linear color space that the rendering pipeline uses. TV screens will raise these color values to the power of 2.2 to 2.4, which leaves a residual gamma behind of around 1.1 to 1.2. This residual value is actually preferred as a gamma slightly above 1.0 will generate more color accurate images when the TV is in darker than normal rooms. When using MPEG or JPG images, the gamma correction is included in the image data, so this feature should be turned off accordingly. **SERRATE** Serration is one the two required components for interlaced display. The other required component is an odd number of half-scanlines. Serration is required in order to delay one of the two fields by half a scanline. Together, this visually displays the beam of light "between" the scanlines of the other field. To obtain the correct effect, this must be complement by a software tweak, that is, the odd field must be adjusted to "scroll up" the contents by half a scanline as well, because otherwise it's not displayed properly wrt the original framebuffer image. To do so, it is normally sufficient to offset **Y\_OFFSET** by **Y\_SCALE / 2** (considering the **Y\_SCALE** is the fixed point step representing one scanline in the framebuffer). Note that **Y\_OFFSET** is limited to fractional values though, so if the offset goes beyond **1.0**, the integer part must be implemented by offsetting **VI\_ORIGIN** instead. #### 0x0440 0004 - VI\_ORIGIN * * * | VI\_ORIGIN `0x0440 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | ORIGIN\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | ORIGIN\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | ORIGIN\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **ORIGIN\[23:0\]:** RDRAM base address of the video output Frame Buffer. This can be changed as needed to implement double or triple buffering. | **Extra Details:** ORIGIN must be a multiple of 8 (i.e. ORIGIN\[2:0\] must be 0). Otherwise the VI output may be noisy, shifted, or weirdly interleaved. You can change ORIGIN mid-frame during horizontal blank. Notice however that VI internally keeps the running offset in the framebuffer since the frame started (accumulating VI\_V\_SCALE for each scanline) and that offset is \*not\* reset when ORIGIN changes. So the new framebuffer will be sampled starting from the same byte offset that the previous framebuffer would have sampled at. #### 0x0440 0008 - VI\_WIDTH * * * | VI\_WIDTH `0x0440 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | WIDTH\[11:8\] | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WIDTH\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-12 | **Undefined:** Initialized to `0` | | bit 11-0 | **WIDTH\[11:0\]:** This is the width in pixels of the frame buffer if you draw to the frame buffer based on a different width than what is given here the image will drift with each line to the left or right. The common values are 320 and 640, the maximum value is 4095. The same value would also be used on drawing commands for clipping or scissors. This can also be used with High Res interlacing modes to change the odd and even lines of the frame buffer to be drawn to screen by doubling the width of this value and changing the VI\_ORIGIN register to the odd or even field being displayed. | **Extra Details:** WIDTH must be a multiple of 2 (if 32bpp) or 4 (if 16bpp) such that the number of bytes from one scanline to the next is a multiple of 8. The same caveats about VI\_ORIGIN apply here, but incorrect display will only happen on some scanlines. Just like VI\_ORIGIN, you can change VI\_WIDTH mid-frame during horizontal blank. Values of WIDTH < 8 can sometimes cause VI crashes. This is not fully understood yet, see [this libdragon issue](https://github.com/DragonMinded/libdragon/issues/759) for details. #### 0x0440 000C - VI\_V\_INTR * * * | VI\_V\_INTR `0x0440 000C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-1 | RW-1 | | — | — | — | — | — | — | V\_INTR\[9:8\] | | | 7:0 | RW-1 | RW-1 | RW-1 | RW-1 | RW-1 | RW-1 | RW-1 | RW-1 | | V\_INTR\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-10 | **Undefined:** Initialized to `0` | | bit 9-1 | **V\_INTR\[9:0\]:** When VI\_V\_CURRENT reaches this half-line number, a VI Interrupt is triggered. Both libultra and libdragon set this to the value 2, which causes an interrupt to be triggered on the second line of vblank.
Default value of `0x3FF` | The actual behavior of V\_INTR depends on whether interlaced mode is on or off. The actual hardware trigger for the change in behavior is bit 0 of VI\_V\_TOTAL, that is, whether the total number of configured half-lines (`VI_V_TOTAL`) is odd or even, which is part of a correct interlacing configuration. Notice that the state of serration (`VI_CTRL.SERRATE`) does not affect VI\_V\_INTR behavior per-se. When `VI_V_TOTAL` bit 0 is 1 (progressive modes), bit 0 of `VI_V_INTR` is effectively ignored: the interrupt will trigger when `V_INTR[9:1]` matches `VI_V_CURRENT[9:1]`, irrespective of the two LSBs. When `VI_V_TOTAL` bit 0 is 0 (interlaced modes), bit 0 of `VI_V_INTR` does have an effect: * If bit 0 is set to 1, the behavior is the expected one: interrupts happen when `VI_V_INTR[9:1]` matches `VI_V_CURRENT[9:1].` For instance, setting `VI_V_INTR=15` can be used to request an interrupt on line 7 of the screen. The interrupt will trigger when `VI_V_CURRENT` is either 14 (line 7, field 0) or 15 (line 7, field 1). * If bit 0 is set to 0, interrupts in odd fields are triggered one line before the actual match. For instance, setting `VI_V_INTR=14` causes an interrupt to generated when `VI_V_CURRENT` is either 14 (line 7, field 0) or 13 (line 6, field 1). This appears to be a hardware bug, probably a side effect on internal timings. An important case to consider for this bug is when `VI_V_INTR` is set 0: in that case, the interrupt will happen on the last line of the previous even field. For instance, in a default PAL configuration with 525 lines, `VI_V_INTR=0` will cause an interrupt when `VI_V_CURRENT` is either 0 (line 0, field 0), or 524 (line 262, field 0), though the latter is supposed to be the one to signal the beginning of the odd field. In this case, reading the field number in the interrupt handler would always result in an even field. When the VI is turned off (`VI_CTRL.TYPE` is 0), no VI interrupt is ever generated. #### 0x0440 0010 - VI\_V\_CURRENT * * * | VI\_V\_CURRENT `0x0440 0010` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | V\_CURRENT\[8:7\] | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | V\_CURRENT\[6:0\] | | | | | | | FIELD | | | | | --- | --- | | bit 31-10 | **Undefined:** Initialized to `0` | | bit 9-1 | **V\_CURRENT:** The current half line, sampled once per line. | | bit 0 | **V\_FIELD:** Field number. In interlaced modes, it alternates between 0 or 1 after each frame. In non-interlaced modes, it stays constant (it can be either 0 or 1). | Writing anything to this register clears the currently triggered VI Interrupt. When `V_V_TOTAL` bit 0 is 1 (progressive modes), the `V_CURRENT` field counts up from 0 to half of the number of active display lines, rounded up. For instance, in a default PAL configuration of 525 vertical lines (see `VI_V_VIDEO`), it will count from 0 to 262, included. `FIELD` will normally be 0 in these modes, so the full registers values will be 0x0, 0x2, 0x4, ..., 0x20C. When changing resolutions from interlaced to progressive, sometimes `FIELD` stays fixed to 1 instead, in which case the full register values will be 0x1, 0x3, 0x5, ..., 0x20D. When `VI_V_TOTAL` bit 0 is 0 (interlaced modes), the behavior is similar but the `FIELD` value will alternate between 0 and 1 after each run. The even field will be one line longer in case the active vertical display area is odd. So, assuming the same PAL configuration of 525 vertical lines, the full register values will be: 0x0, 0x2, 0x4, ..., 0x20C, 0x1, 0x3, 0x5, ..., 0x20B. The register value changes right at the beginning of the hsync period. When the VI is turned off (`VI_CTRL.TYPE` is 0), this register is fixed to 0, and never changes. #### 0x0440 0014 - VI\_BURST * * * | VI\_BURST `0x0440 0014` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | BURST\_START\[9:4\] | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | BURST\_START\[3:0\] | | | | VSYNC\_HEIGHT\[3:0\] | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | BURST\_WIDTH\[7:0\] | | | | | | | | | 7:0 | RW-1 | RW-1 | RW-0 | RW-1 | RW-0 | RW-0 | RW-0 | RW-1 | | HSYNC\_WIDTH\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-30 | **Undefined:** Initialized to `0` | | bit 29-20 | **BURST\_START\[9:0\]:** Start of color burst in pixels from hsync | | bit 19-16 | **VSYNC\_HEIGHT\[3:0\]:** One less than the vertical sync duration in half lines | | bit 15-8 | **BURST\_WIDTH\[7:0\]:** Color burst width in pixels | | bit 7-0 | **HSYNC\_WIDTH\[7:0\]:** Horizontal sync width in pixels
Default value of `0x01` | **Examples:** NTSC @ any resolution is `0x03E52239` * horizontal sync width in pixels: 57 (decimal) * color burst width in pixels: 34 (decimal) * vertical sync height in half lines: 5 (decimal) (and thus 6 half-lines) * start of color burst in pixels from h-sync: 62 (decimal) PAL @ any resolution is `0x0404233A` * horizontal sync width in pixels: 58 (decimal) * color burst width in pixels: 35 (decimal) * vertical sync height in half lines: 4 (decimal) (and thus 5 half-lines) * start of color burst in pixels from h-sync: 64 (decimal) #### 0x0440 0018 - VI\_V\_TOTAL * * * | VI\_V\_TOTAL[\[1\]](https://n64brew.dev/wiki/Video_Interface#cite_note-1)
`0x0440 0018` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | V\_TOTAL\[9:8\] | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | V\_TOTAL\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-10 | **Undefined:** Initialized to `0` | | bit 9-0 | **V\_TOTAL\[9:0\]:** One less than the total number of visible and non-visible half-lines. This should match either NTSC/MPAL (non-interlaced: `525`, interlaced: `524`) or PAL (non-interlaced: `625`, interlaced: `624`) | Interlaced modes requires an odd number of scanlines to be configured (that is, an even number written to the register, given that it holds one less than the actual wanted number), in addition to serration being turned on (VI\_CTRL.SERRATE). So it is normal to write to this register to switch between interlaced and progressive modes. #### 0x0440 001C - VI\_H\_TOTAL * * * | VI\_H\_TOTAL[\[2\]](https://n64brew.dev/wiki/Video_Interface#cite_note-2)
`0x0440 001C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | LEAP\[4:0\] | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | RW-1 | RW-1 | RW-1 | RW-1 | | — | — | — | — | H\_TOTAL\[11:8\] | | | | | 7:0 | RW-1 | RW-1 | RW-1 | RW-1 | RW-1 | RW-1 | RW-1 | RW-1 | | H\_TOTAL\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-21 | **Undefined:** Initialized to `0` | | bit 20-16 | **LEAP\[4:0\]:** 5-bit leap pattern. PAL standard value is `0x15` | | bit 15-12 | **Undefined:** Initialized to `0` | | bit 11-0 | **H\_TOTAL\[11:0\]:** One less than the total length of a scanline in 1/4 pixel units. Standard values are NTSC (`3093`), PAL (`3177`), MPAL progressive (`3089`), and MPAL interlaced (`3088`)
Default value is `2047` (0x7FF) | **Extra Details:** LEAP chooses whether to use LEAP\_A or LEAP\_B on each vsync repeating every five vsyncs. The NTSC default (0) means "always use LEAP\_A". The PAL default (0x15) means "alternate using LEAP\_B, LEAP\_A, LEAP\_B, LEAP\_A, LEAP\_B" and repeat. Derivation of numbers: NTSC has 227.5 chroma periods per scanline. NTSC N64 has 13.6 VI clocks per chroma period. 227.5 × 13.6 = 3094 MPAL has 227.25 chroma periods per scanline. MPAL N64 has 13.6 VI clocks per chroma period. 227.25 x 13.6 = 3090.6 PAL (European) has 283.7516 chroma periods per scanline. PAL N64 has 11.2 clocks per chroma period. 283.75 x 11.2 = 3178 H\_TOTAL is also used by the RDRAM Interface for refresh timings. As the default is notably shorter than regular video modes, there will be a noticeable impact to memory bandwidth until H\_TOTAL is configured to a valid video mode. #### 0x0440 0020 - VI\_H\_TOTAL\_LEAP * * * | VI\_H\_TOTAL\_LEAP[\[3\]](https://n64brew.dev/wiki/Video_Interface#cite_note-3)
`0x0440 0020` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | LEAP\_A\[11:8\] | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | LEAP\_A\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | LEAP\_B\[11:8\] | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | LEAP\_B\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-28 | **Undefined:** Initialized to `0` | | bit 27-16 | **LEAP\_A\[11:0\]:** Special scanline length (H\_TOTAL), when LEAP=0 | | bit 15-12 | **Undefined:** Initialized to `0` | | bit 11-0 | **LEAP\_B\[11:0\]:** Special scanline length (H\_TOTAL), when LEAP=1 | **Extra Details:** LEAP\_n specifies an alternate scanline length (H\_TOTAL alternative value) for one scanline during vsync. Values larger than H\_TOTAL specify the length of the second scanline of vsync. Values smaller than H\_TOTAL specify the length of the first scanline of vsync and have a variety of undesired side effects, such as skipping one hsync entirely or leaving csync erroneously high for one whole scanline. Serration changes these effects subtly. Specifically, a counter is started at the start of vsync. When that counter is equal to LEAP\_n, the VI starts or restarts the second scanline of vsync without changing the status of the csync bit. The default PAL values of LEAP (`0x15`), LEAP\_A (`3182`), and LEAP\_B (`3183`) add PAL's nominal "one extra chroma period per 625 whole scanlines emitted" when averaged. 6 + 5 + 6 + 5 + 6 = 5.6; divide 5.6 by 11.2 VI clocks per chroma period = 1/2 chroma period per field. #### 0x0440 0024 - VI\_H\_VIDEO * * * | VI\_H\_VIDEO `0x0440 0024` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | H\_START\[9:8\] | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | H\_START\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | H\_END\[9:8\] | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | H\_END\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-26 | **Undefined:** Initialized to `0` | | bit 25-16 | **H\_START\[9:0\]:** Start of the active video image, in screen pixels. Typical values: NTSC (`108`) or PAL (`128`) | | bit 15-10 | **Undefined:** Initialized to `0` | | bit 9-0 | **H\_END\[9:0\]:** End of the active video image, in screen pixels from hsync. Typical values: NTSC (`748`) or PAL (`768`) | **Extra Details:** H\_START specifies when VI evaluation starts. The screen remains blanked for several pixels afterwards, while the VI loads values from RAM for filtering, even if **AA\_MODE** is set to **REPLICATE**. H\_END specifies the first black pixel on the right end of each scanline. Setting H\_START = H\_END = 0 blanks the display output (full black, no picture displayed) but keeps the VI fully active (line counter will increment, interrupts will trigger, etc.). This is different from setting VI\_CTRL.TYPE=0, which instead will totally stop VI activity (no output signal). Setting VI\_H\_VIDEO while display is in progress can sometimes cause the VI to hang (normally, a striped picture will be displayed). The registers should only be changed during vertical blank. It is instead possible to change VI\_H\_VIDEO mid-frame as long as it strictly happens within horizontal blank. #### 0x0440 0028 - VI\_V\_VIDEO * * * | VI\_V\_VIDEO `0x0440 0028` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | V\_START\[9:8\] | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | V\_START\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | V\_END\[9:8\] | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | V\_END\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-26 | **Undefined:** Initialized to `0` | | bit 25-16 | **V\_START\[9:0\]:** Start of the active video image, in screen half-lines. Typical values: NTSC (`0x025`) or PAL (`0x05F`) | | bit 15-10 | **Undefined:** Initialized to `0` | | bit 9-0 | **V\_END\[9:0\]:** End of the active video image, in screen half-lines from vsync. Typical values: NTSC (`0x1FF`) or PAL (`0x239`) | **Extra Details:** Mid-frame changes to VI\_V\_VIDEO seem to be totally ignored by VI, including changes to V\_END, as if the register is latched internally at the beginning of the frame. #### 0x0440 002C - VI\_V\_BURST * * * | VI\_V\_BURST `0x0440 002C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | V\_BURST\_START\[9:8\] | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | V\_BURST\_START\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | V\_BURST\_END\[9:8\] | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | V\_BURST\_END\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-26 | **Undefined:** Initialized to `0` | | bit 25-16 | **V\_BURST\_START\[9:0\]:** Start of the color burst enable, in half-lines. Typical values: NTSC (`0x00E`) or PAL (`0x009`) | | bit 15-10 | **Undefined:** Initialized to `0` | | bit 9-0 | **V\_BURST\_END\[9:0\]:** End of the color burst enable, in half-lines. Typical values: NTSC (`0x204`) or PAL (`0x26B`) | #### 0x0440 0030 - VI\_X\_SCALE * * * | VI\_X\_SCALE `0x0440 0030` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | X\_OFFSET\[11:8\] | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | X\_OFFSET\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | X\_SCALE\[11:8\] | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | X\_SCALE\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-28 | **Undefined:** Initialized to `0` | | bit 27-16 | **X\_OFFSET\[11:0\]:** Horizontal subpixel offset ([2.10 format](https://n64brew.dev/wiki/Video_Interface#Fixed-Point_Format)
) | | bit 15-12 | **Undefined:** Initialized to `0` | | bit 11-0 | **X\_SCALE\[11:0\]:** 1/horizontal scale up factor (2.10 format) | **Extra Details:** It is possible to change VI\_X\_SCALE mid-frame during horizontal blank. ##### Errata * If [AA\_MODE](https://n64brew.dev/wiki/Video_Interface#0x0440_0000_-_VI_CTRL) = REPLICATE (resampling disabled), [TYPE](https://n64brew.dev/wiki/Video_Interface#0x0440_0000_-_VI_CTRL) = 10 (16-bit), X\_SCALE is 0x200 or lower, and H\_START is less than 128, the VI generates invalid output, consisting of the first 64 pixels from the framebuffer from the current line, then 64 pixels of garbage, and these two repeat for the rest of each scanline. A common case where this can happen is NTSC units (where H\_START is usually 96) with a standard framebuffer of 320x240 (X\_SCALE=0x200). For this very common situation, the simplest workaround is to activate resampling or, if the wanted scale was exactly 0x200 (for a 320 pixel wide framebuffer), use 0x201 instead which is guaranteed to not introduce any visual difference, but does not trigger the bug. * The previous bug can also be triggered with resampling is set to [AA\_MODE](https://n64brew.dev/wiki/Video_Interface#0x0440_0000_-_VI_CTRL) = RESAMPLE (resampling enabled, AA disabled) with the same constraints X\_SCALE < 0x200 and H\_START < 128, but the additional constraint of WIDTH < 8. Compared to the previous incantation which always reproduces, this crash also requires specific timing to trigger. See [this libdragon issue](https://github.com/DragonMinded/libdragon/issues/759) for more information. * If X\_SCALE is higher than 0x800 (32bpp) or 0xE00 (16bpp), the scaler renders incorrect pixels, with specifics depending on depth. This appears to be due to exceeding the number of VI fetches allocated per scanline. #### 0x0440 0034 - VI\_Y\_SCALE * * * | VI\_Y\_SCALE `0x0440 0034` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | unused | | Y\_OFFSET\[9:8\] | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | Y\_OFFSET\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | Y\_SCALE\[11:8\] | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | Y\_SCALE\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-28 | **Undefined:** Initialized to `0` | | bit 27-26 | **unused:** Holds state, but ignored during display. Erroneously thought to be two msbits of the offset. | | bit 25-16 | **Y\_OFFSET\[9:0\]:** Vertical subpixel offset ([0.10 format](https://n64brew.dev/wiki/Video_Interface#Fixed-Point_Format)
) | | bit 15-12 | **Undefined:** Initialized to `0` | | bit 11-0 | **Y\_SCALE\[11:0\]:** 1/vertical scale up factor (2.10 format) | **Extra Details:** The VI keeps an internal "accumulated vertical offset" register. This register is initialized with Y\_OFFSET sometimes at the beginning of the frame, and then Y\_SCALE is added to it every scanline. The integer part of this internal register is then used as Y position to fetch the framebuffer. It is possible to change VI\_Y\_SCALE mid-frame, during horizontal blank. Notice though that the internal register is not reset in any way, and will keep accumulating with the new Y\_SCALE value. This also means that mid-frame changes to Y\_OFFSET will be ignored. ##### Erratum * If Y\_SCALE exceeds 0xC00, it instead behaves like a glitchy variation of 3\*(0x1000-Y\_SCALE) #### 0x0440 0038 - VI\_TEST\_ADDR * * * | VI\_TEST\_ADDR `0x0440 0038` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | TEST\_ADDR\[6:0\] | | | | | | | | | | | --- | --- | | bit 31-7 | **Undefined:** Initialized to `0` | | bit 6-0 | **TEST\_ADDR\[6:0\]:** Sets the line buffer word address at which VI\_STAGED\_DATA will read/write data. | #### 0x0440 003C - VI\_STAGED\_DATA * * * | VI\_STAGED\_DATA `0x0440 003C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | STAGED\_DATA\[31:24\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | STAGED\_DATA\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | STAGED\_DATA\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | STAGED\_DATA\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-0 | **STAGED\_DATA\[31:0\]:** Reads from this register returns 32 bits of line buffer data at the address specified in VI\_TEST\_ADDR. Writes to this register emplace 32 bits of data into the line buffer at the address specified in VI\_TEST\_ADDR. Usage requires TEST\_MODE to be set in VI\_CTRL. | Fixed-Point Format ================== [Fixed-point](https://en.wikipedia.org/wiki/Fixed-point_arithmetic "wikipedia:Fixed-point arithmetic") is a method of representing decimal numbers. Unlike floating-point numbers, fixed-point numbers allocate a specific number of bits for the integer (X) and fractional (Y) parts of the number, denoted as "X.Y format" (similar to [Q-notation](https://en.wikipedia.org/wiki/Q_(number_format) "wikipedia:Q (number format)") ). In this format, a certain number of bits are dedicated to the integer part, while the remaining bits represent the fractional part. For instance, some VI registers employ the 2.10 format, where two bits are used for the integer part and ten bits are allocated for the fractional part, resulting in a total of twelve bits. Here are examples of decimals represented in 2.10 format:  [](https://n64brew.dev/wiki/File:2.10_fixed_point_examples.svg) Note that not all decimals can be represented and must be approximated. For example, in 2.10 format the decimal 3.14 is approximated as 3.1416015625, or \`11 0010010001\`. Here's an example of how to convert from the binary to decimal: The integer part is given by adding powers of two, starting at zero and going right to left:  [](https://n64brew.dev/wiki/File:2.10_fixed_point_example_1.svg) The fractional part is given by adding the inverse of powers of two, staring at one and going left to right:  [](https://n64brew.dev/wiki/File:2.10_fixed_point_example_2.svg) How to use this information =========================== ### Interlace Mode The NTSC (and PAL) standard support interlace mode which is commonly associated with high resolution, but it can be used for more than that. * High Resolution Mode * Improve the visible detail of the image * 60 frames per second in low resolution #### High Resolution Mode High resolution mode supports up to 480 lines (NTSC) while low resolution is 240 lines (NTSC). The Image doesn't magically grow or shrink because first the even lines are drawn on the screen, then it goes back to the top and draws the odd lines. If your game only draws the even lines then on a larger display you may have the image scanlines with smaller black lines visible between them. In order to implement this feature it requires the VI\_V\_START\_REG to be modified on every VI Interrupt, so that it outputs even lines then odd lines as needed. NTSC Alternates between: 0x002301fd and 0x002501ff PAL Alternates between: 0x005f0239 and 0x005d0237 Once this is explained I believe it will be fixed soon, so this is explained as an example of what the difference can be. The libdragon homebrew library doesn't actually support High Resolution Mode, because it doesn't implement this register value change. To be fair this is very easy to overlook, it works fine in every emulator and would at least look OK on a console. The difference is that emulators present the framebuffer memory as a single block of data. While the VI Interface and Video DAC see the 1 framebuffer as even lines top to bottom, then odd lines top to bottom. Learnt from Factor 5 games(Mazamars312): The VI\_DRAM\_ADDR\_REG address is set to the odd or even line of the framebuffer and the VI\_H\_WIDTH\_REG value is doubled to help skip to the next Odd or Even field line for the VI core to process. Once the odd or even field has been displayed the VI\_DRAM\_ADDR\_REG is updated to the other field's address. Also the VI\_Y\_SCALE\_REG.Subpixel is changed between fields with the values 12'h0100 and 12'h0200 to help the scaling and AA calculations (Need to find out which one is Odd and Even based as this could be game based) The real width and height values are calculated by the following calculations **Width**: C programming(float) ((VI\_H\_START\_REG.END - VI\_H\_START\_REG.START) \* (VI\_X\_SCALE\_REG.ScaleUp / 1024)) **Height**: C programming(float) (((VI\_H\_START\_REG.END - VI\_H\_START\_REG.START) >> 1) \* (VI\_X\_SCALE\_REG.ScaleUp / 1024)) #### Improve visible detail in low resolution ### 60 Frames per second in Low Resolution mode This is the easiest mode to use if your frame processing time is very low, because you simply swap the frame buffer 60 times per second inside the VI Interrupt, no other register changes are needed. #### Letter Boxing This is a fairly common effect that is nice for cut scenes or to indicate overworld vs a level. VI\_V\_START\_REG ### Pillar Boxing This feature is almost the default now since the N64 is intended for a 4:3 screen but is commonly played on 16:9 ratio screens. VI\_H\_START\_REG ### Reduce both Height and Width Reducing the display size by just a few pixels also reduces the size of the world view that the player has, while usually improving performance. Especially if the purpose of this is to improve performance I recommend doing it in increments of 8 pixels, for example either 4 or 8 pixels off each side and my increasing the size of the player status bars at either the top or bottom of the screen can also reduce the number of objects to draw on the screen. Use the same techniques mentioned above for Letter Boxing and Pillar Boxing. Advanced version of this is to reduce either the height or width and to increase the scaling so it still fits the screen but stretches the image out to fill the screen. 1. [↑](https://n64brew.dev/wiki/Video_Interface#cite_ref-1) This register used to be called VI\_V\_SYNC, but was changed to better reflect its actual meaning. 2. [↑](https://n64brew.dev/wiki/Video_Interface#cite_ref-2) This register used to be called VI\_H\_SYNC, but was changed to better reflect its actual meaning. 3. [↑](https://n64brew.dev/wiki/Video_Interface#cite_ref-3) This register used to be called VI\_H\_SYNC\_LEAP, but was changed to better reflect its actual meaning. Retrieved from "[https://n64brew.dev/wiki/Video\_Interface?oldid=5791](https://n64brew.dev/wiki/Video_Interface?oldid=5791) " --- # Jumper Pak - N64brew Wiki [](https://n64brew.dev/wiki/Jumper_Pak#) Jumper Pak ========== The **Jumper Pak** is a filler that plugs into the console's memory expansion port. It serves no functional purpose other than to terminate the RAMBUS bus in the absence of the [Expansion Pak](https://n64brew.dev/wiki/Expansion_Pak "Expansion Pak") . If neither the Expansion Pak nor Jumper Pak are in the expansion port, the N64 will not boot. Retrieved from "[https://n64brew.dev/wiki/Jumper\_Pak?oldid=5142](https://n64brew.dev/wiki/Jumper_Pak?oldid=5142) " --- # Pseultra - N64brew Wiki [](https://n64brew.dev/wiki/Pseultra#) Pseultra ======== pseultra is feature-rich, open-source alternative to the official N64 SDK. The biggest benefit of developing with an open-source SDK is that open-source code is legal to redistribute in your homebrew game (as long as you ensure you're not using _any_ proprietary Nintendo code _at all_ or infringing on other copyrights), and better support for modern operating systems (such as Windows 10 and Linux). The official pseultra repository is [available on GitHub](https://github.com/pseudophpt/pseultra) , and the "Getting Started" section of the readme explains how to set it up on macOS or Linux. Please note that there is a slight error in the current "Getting Started" instructions, as some users have reported that you must run `scons && sudo scons install` in the tools directory before running it in the root directory. There is also [a fork](https://github.com/iffyloop/pseultra) of pseultra with pre-built binaries for MSYS2 (Windows) x64, along with several merged features, updates, and patches. The following section describes how to set up an MSYS2 environment with pre-built pseultra binaries. Contents -------- * [1 Windows](https://n64brew.dev/wiki/Pseultra#Windows) * [2 macOS & Linux](https://n64brew.dev/wiki/Pseultra#macOS_&_Linux) * [3 Initializing a Project](https://n64brew.dev/wiki/Pseultra#Initializing_a_Project) * [4 A Note About Bootloaders](https://n64brew.dev/wiki/Pseultra#A_Note_About_Bootloaders) Windows ------- The following instructions will help you set up a pseultra development environment on Windows with MSYS2 and n64chain. 1. Download and install the latest 64-bit **MSYS2** from: [msys2.org](https://www.msys2.org/) . 2. Download the latest **n64chain** binaries from: [cen64.com](https://cen64.com/) . Make sure you download the binaries under the section titled "n64chain Downloads" rather than the CEN64 emulator. 3. Extract the n64chain package, move the `tools` directory into `C:\msys64\opt`, and rename `tools` to `n64chain`. 4. Download and extract [pseultra-chksum64-binaries.zip](https://github.com/iffyloop/pseultra/releases/) , open the extracted `pseultra-chksum64-binaries` folder, and move the `pseultra` and `chksum64` directories into `C:\msys64\opt`. macOS & Linux ------------- Please follow the "Getting Started" instructions in either of the linked repositories above. While the Windows one is an unofficial fork, it does contain some updates and patches that are likely to be beneficial to UNIX users also. Initializing a Project ---------------------- pseultra's build system integration is still a work-in-progress. Thus, currently a simple shell script is used to automate the building of a ROM from a single C source file. This section explains how to download and use the template project and build script, which you can then customize as much as you want. (Here, we assume the Windows setup described above; if you're using a different platform or toolchain, the most important thing to adjust will be the assignment of the `PATH` environment variable in `build.sh`. Additionally, make sure you have a copy of the [chksum64](https://github.com/DragonMinded/libdragon/blob/trunk/tools/chksum64.c) application which is bundled with pseultra in the binaries linked above). 1. Download and extract [pseultra-project.zip](https://github.com/iffyloop/pseultra/releases/) , optionally renaming the extracted `pseultra-project` directory to something more appropriate for your project. 2. Take the first 4096 bytes of a big-endian N64 ROM and copy them to a separate file called `nintendo-boot-template.n64`, placing that file in the root directory of your project (next to `build.sh`). If you want, you can keep a copy of this file in another location, separate from your project, and re-use it again later. **This is the non-free part discussed below in the section "A Note About Bootloaders."** 3. Run `build.sh` inside an **MSYS2** shell. The final output should be located inside `build\main-linked-nintendo-boot.n64`. Run this ROM with CEN64, and you should see a spinning cube! 4. Use this project as a starting point for your own N64 software. A Note About Bootloaders ------------------------ The official Nintendo bootloader (known more specifically as the [IPL3](https://n64brew.dev/wiki/IPL3 "IPL3") ) is the only non-open-source component required to build N64 games with pseultra, thus inhibiting free and unencumbered distribution of your homebrew games. Thankfully, though, work is ongoing in the N64brew community to write a completely open-source replacement for the [IPL3](https://n64brew.dev/wiki/IPL3 "IPL3") , and once that is accomplished, the template project and above instructions will be adjusted accordingly. Retrieved from "[https://n64brew.dev/wiki/Pseultra?oldid=90](https://n64brew.dev/wiki/Pseultra?oldid=90) " --- # MIPS Assembly - N64brew Wiki [](https://n64brew.dev/wiki/MIPS_Assembly#) MIPS Assembly ============= Writing MIPS assembly is not necessary to make a Nintendo 64 game. You can make a game entirely using C, C++, or any other programming language you can get working on the Nintendo 64. However, you may want to be able to read MIPS assembly from time to time, and you may want to write small snippets of MIPS assembly. This page will not teach you MIPS assembly, but just give you the highlights. See also [MIPS III instructions](https://n64brew.dev/wiki/MIPS_III_instructions "MIPS III instructions") , for a list of instructions. Contents -------- * [1 Learning MIPS Assembly](https://n64brew.dev/wiki/MIPS_Assembly#Learning_MIPS_Assembly) * [2 MIPS Versions](https://n64brew.dev/wiki/MIPS_Assembly#MIPS_Versions) * [3 MIPS Quirks](https://n64brew.dev/wiki/MIPS_Assembly#MIPS_Quirks) * [3.1 Branch Delay Slot](https://n64brew.dev/wiki/MIPS_Assembly#Branch_Delay_Slot) * [3.2 Integer Multiplication and Division](https://n64brew.dev/wiki/MIPS_Assembly#Integer_Multiplication_and_Division) * [3.3 VR4300 Multiplication Bug](https://n64brew.dev/wiki/MIPS_Assembly#VR4300_Multiplication_Bug) Learning MIPS Assembly ====================== MIPS is, still, commonly chosen as an architecture for teaching assembly language to computer science students. It has been used in countless college courses on assembly language. There several reasons why MIPS is a good language for teaching computer science classes, but that’s not really important for game developers—what is important is that you can easily find high-quality guides for programming in MIPS that teach the associated concepts and theory, and that don’t assume that you have any prior experience with MIPS. MIPS Versions ============= The VR4300 in the Nintendo 64 console uses the MIPS III architecture. This is somewhat older than architectures like MIPS32 and MIPS64. Even though MIPS III is 64-bit, it is a different (older) architecture from MIPS64. MIPS Quirks =========== The one big quirk of MIPS is that it contains (depending on the specific version) various non-interlocked hazards. This is what the MIPS acronym originally stood for, supposedly: “Microprocessor without Interlocked Pipeline Stages”. A [hazard](https://en.wikipedia.org/wiki/Hazard_(computer_architecture)) happens when the results of one instruction are not complete by the time the next instruction executes. An [interlock](https://en.wikipedia.org/wiki/Interlock_(engineering)) prevents an instruction from executing until the inputs are ready, creating a [pipeline stall](https://en.wikipedia.org/wiki/Pipeline_stall) —the pipeline stall delays the instruction so it can execute correctly. In most other processors, you don’t have to know about these things, because the hazards are protected by interlocks. MIPS has some common hazards which are not protected by interlocks, so you must structure your assembly code to avoid the hazards. In some cases, the assembler can help you by **automatically reordering the instructions** to avoid hazards. **Warning:** MIPS assemblers (except Bass) will reorder your instructions by default. This means that the assembly you write will not exactly match what you get when you disassemble the code afterwards. You can disable this with `.set noreorder`, or you can simply let the MIPS assembler make your job easier. Three hazards you might commonly encounter on the VR4300 are the branch delay slot, the integer multiply and division instructions, and the floating-point multiply instruction (VR4300 multiply bug). There are other hazards that you would only typically see in kernel code, like changes to the TLB—refer to the CPU manual if you are doing anything special. Branch Delay Slot ----------------- The instruction after most branch instructions is executed, regardless of whether the branch is taken. The exceptions are the “branch likely” instructions, such as `bnel`, which behave differently—refer to the CPU manual. For example, consider the C function: int add(int x, int y) { return x + y; } The actual MIPS generated by the compiler may be: .set noreorder add: jr $31 addu $2,$4,$5 Note that the `addu` instruction appears _after_ the function returns—it is in the branch delay slot, and will be executed anyway. However, if you are writing assembly, you can write the function like this instead: add: addu $2,$4,$5 jr $31 When you assemble it, you will get the same result as above… because by default, the assembler will reorder instructions so you don’t have to think about the delay slot. Disassembling the code from the second snippet will show you the first snippet. Integer Multiplication and Division ----------------------------------- After a `mfhi`, the next two instructions may not modify the `HI` register. After `mflo`, the next two instructions may not modify the `LO` register. Operations which modify those registers are: * `ddiv` * `ddivu` * `div` * `divu` * `dmult` * `dmultu` * `mthi` * `mtlo` Like the branch delay slot, the assembler will fix this for you. For example, if you try to assemble the following code: my\_function: mfhi $2 mthi $2 The assembler will insert two `nop` instructions between `mfhi` and `mthi` to avoid the hazard. This can be disabled with `.set noreorder`, just like the branch delay slot. VR4300 Multiplication Bug ------------------------- TODO: What is the exact nature of this bug? The assembler will not fix this one for you. After `mul.s` or `mul.d`, insert two `nop` instructions to avoid triggering the bug. Retrieved from "[https://n64brew.dev/wiki/MIPS\_Assembly?oldid=4065](https://n64brew.dev/wiki/MIPS_Assembly?oldid=4065) " --- # Serial Interface - N64brew Wiki [](https://n64brew.dev/wiki/Serial_Interface#) Serial Interface ================ The Serial Interface (or **SI**) is one of multiple I/O interfaces in the RCP, which is used to communicate with the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") and in turn, [Joybus](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") devices. Memory mapped registers are used to configure the Serial Interface and initiate DMA reads and writes. The base address for these registers is `0x0480 0000`, also known as SI\_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 `0xA000 0000` to the address. As an example, to directly write to the SI\_DRAM\_ADDR register, use address `0xA480 0000`. In addition to these registers, SI is also in charge of handling the memory mapping of PIF-ROM and PIF-RAM to VR4300. These memories are mapped at physical address `0x1FC0 0000` (and normally accessed via the uncached segment at `0xBFC0 0000`). Contents -------- * [1 Mapped PIF-ROM and PIF-RAM](https://n64brew.dev/wiki/Serial_Interface#Mapped_PIF-ROM_and_PIF-RAM) * [2 DMA transfers](https://n64brew.dev/wiki/Serial_Interface#DMA_transfers) * [3 Communication protocol with PIF-NUS](https://n64brew.dev/wiki/Serial_Interface#Communication_protocol_with_PIF-NUS) * [4 Registers](https://n64brew.dev/wiki/Serial_Interface#Registers) * [4.1 0x0480 0000 - SI\_DRAM\_ADDR](https://n64brew.dev/wiki/Serial_Interface#0x0480_0000_-_SI_DRAM_ADDR) * [4.2 0x0480 0004 - SI\_PIF\_AD\_RD64B](https://n64brew.dev/wiki/Serial_Interface#0x0480_0004_-_SI_PIF_AD_RD64B) * [4.3 0x0480 0008 - SI\_PIF\_AD\_WR4B](https://n64brew.dev/wiki/Serial_Interface#0x0480_0008_-_SI_PIF_AD_WR4B) * [4.4 0x0480 0010 - SI\_PIF\_AD\_WR64B](https://n64brew.dev/wiki/Serial_Interface#0x0480_0010_-_SI_PIF_AD_WR64B) * [4.5 0x0480 0014 - SI\_PIF\_AD\_RD4B](https://n64brew.dev/wiki/Serial_Interface#0x0480_0014_-_SI_PIF_AD_RD4B) * [4.6 0x0480 0018 - SI\_STATUS](https://n64brew.dev/wiki/Serial_Interface#0x0480_0018_-_SI_STATUS) * [5 iQue Player](https://n64brew.dev/wiki/Serial_Interface#iQue_Player) Mapped PIF-ROM and PIF-RAM -------------------------- When the VR4300 access the physical area at `0x1FC0 0000` - `0x1FCF FFFF`, RCP handles the request via SI; the memory access performed via standard MIPS opcode like `LW` or `SW` is converted into a I/O communication with PIF, using the serial bus. See [PIF-NUS#Internal ROMs and RAM](https://n64brew.dev/wiki/PIF-NUS#Internal_ROMs_and_RAM "PIF-NUS") for a description of the memories inside the PIF. The addresses are mapped as follows (and they mirror across the whole area): | | | | --- | --- | | 0x000 - 0x7BF | PIF-ROM. This area contains the IPL1/IPL2 boot code. VR4300 starts running from these addresses after a NMI. During the boot process, PIF-ROM is locked out for security reasons, and during normal runtime all reads from these addresses return 0. | | 0x7C0-0x7FF | PIF-RAM (64 bytes). This area is used to communicate with PIF, mostly to run the Joyous protocol to communicate with external controllers. | Notice that in general the SI is not aware of this memory map. For each access to the area, it will issue a read or write request (using the protocol detailed below) which includes the 11-bit address. It does not behave differently depending on the address (eg: writes to the ROM area are still issued). The SI serial protocol with PIF-NUS only allows to transfer 32-bit words (or 64-byte sequences, when a DMA transfer is requested), so it is advised for the VR4300 to access this memory mapped area only via 32-bit operations. The result obtained when using accesses of different size is detailed in [Memory map#Range 0x1FC0'0000 - 0x1FCF'FFFF (SI external bus)](https://n64brew.dev/wiki/Memory_map#Range_0x1FC0'0000_-_0x1FCF'FFFF_(SI_external_bus) "Memory map") . In general, read accesses are blocking, while write accesses are asynchronous. Read accesses while a write is in progress are correctly delayed and run at the end of the write. This is described in detail in [Memory map#Range 0x1FC0'0000 - 0x1FCF'FFFF (SI external bus)](https://n64brew.dev/wiki/Memory_map#Range_0x1FC0'0000_-_0x1FCF'FFFF_(SI_external_bus) "Memory map") . Direct writes to the memory mapped areas cause interrupts on the VR4300, exactly like DMA transfers. In fact, the two are mostly identical at the hardware level, including the fact that the flag DMA\_BUSY is also set. DMA transfers ------------- The SI allows to transfer the contains of the whole PIF-RAM (64 bytes) with a DMA transfer (both reads and writes). VR4300 can trigger these DMAs by writing to the registers `SI_PIF_AD_WR64B` and `SI_PIF_AD_RD64B` (see below). Notice that the 64-byte read transfer has a special: when the transfer is requested by the SI, the PIF firmware first runs the whole joyous handshake described in PIF-RAM, communicating with the attached device; then t updates the contents of PIF-RAM with the results, and only a this point the ACK is sent to the SI and the actual transfer is done with the updated values. This means that the 64-byte DMA read is usually much slower than expected because it does not just transfer the bytes, but must first wait for the PIF to communicates with all controllers as requested. Communication protocol with PIF-NUS -----------------------------------  [](https://n64brew.dev/wiki/File:SI_-_PIF_communication_protocol.gif) Visual representation of the protocol described in this paragraph The communication protocol with PIF-NUS is the low-level data encapsulation performed by the SI to communicate with PIF-NUS. There are 4 supported packets: * **RD4B** (Read 4 bytes): This packet is generated any time the VR4300 reads from the PIF mapped area. The SI sends on the bus the bits `11` to identify the packet, followed by bits 10..2 of the address to read (bits 1..0 are assumed to be always 0, that is the address is always 32-bit aligned). The PIF replies with an ACK followed by the 32-bit word that was contained in the memory (ROM or RAM) at the specified address. * **WR4B** (Write 4 bytes): This packet is generated any time the VR4300 writes to the PIF mapped area The SI sends on the bus the bits `10` to identify the packet, followed by bits 10..2 of the address to write (bits 1..0 are assumed to be always 0, that is the address is always 32-bit aligned). The PIF replies with an ACK, and at that point the SI sends the 32-bit word to be written to memory (RAM; writes to ROM are obviously ignored) at the specified address. * **RD64B** (Read 64 bytes): This packet is generated any time the VR4300 issues a DMA read transfer The SI sends on the bus the bits `01`to identify the packet, followed by bits 10..2 of the address to write (which would normally be `111110000`, which are bits 10..2 of `0x7C0`). When the PIF receives this packet, it does not immediately replies with the ACK: first, it runs the joybus handshake described in PIF-RAM, communicating with the various attached devices, and updates the PIF-RAM contents with the result. Only after this is done, the ACK is sent to the SI, followed by the 512 bits of PIF-RAM contents. * **WR64B** (Write 64 bytes). This packet is generated any time the VR4300 issues a DMA write transfer. The SI sends on the bus the bits `01`to identify the packet, followed by bits 10..2 of the address to write (which would normally be `111110000`, which are bits 10..2 of `0x7C0`). The PIF replies with an ACK, and at that point the SI sends the 512-bit sequence to be written to PIF-RAM. 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 #### 0x0480 0000 - SI\_DRAM\_ADDR * * * | SI\_DRAM\_ADDR `0x0480 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **DRAM\_ADDR\[23:0\]:** RDRAM address used in SI DMAs | #### 0x0480 0004 - SI\_PIF\_AD\_RD64B * * * | SI\_PIF\_AD\_RD64B `0x0480 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | — | PIF\_ADDR\[10:8\] | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | R-0 | | PIF\_ADDR\[7:2\] | | | | | | 0 | 0 | | | | | --- | --- | | bit 31-11 | **Undefined:** Initialized to `0` | | bit 10-0 | **PIF\_ADDR:** Offset in PIF\_RAM/PIF\_ROM where to fetch data | Writing to this register triggers a SI DMA transfer from PIF to RDRAM. The RDRAM address is the one stored in SI\_DRAM\_ADDR, while the address within PIF\_ROM/PIF\_RAM must be written to this register. Notice that the lowest two bits of the PIF address are fixed to zero, so only aligned transfers can be run. This transfer is done by sending a RD64B serial packet to PIF, waiting for acknowledge and then writing to RDRAM the data sent back via serial. Notice that this command has a special meaning for PIF: if the PIF\_RAM command byte has either bit 0 or bit 1 set, the respective commands will first be executed (both of them will write to PIF\_RAM), and then the requested data is transferred. So the transfer could take a while to run, because the SI might be waiting for the acknowledge for a long time. See PIF-NUS for more information about RD64B. In the normal case, VR4300 would have prepared a Joybus packet in PIF\_RAM to poll controllers. When the SI DMA read is run, the PIF will receive the RD64B packet and will actually perform the full joybus exchange with controllers, writing the state in PIF\_RAM. It will then send back the results to SI that will in turn write them to RDRAM. This means that the actual Joybus protocol is only executed when VR4300 asks to read the results via SI DMA, and the actual DMA will be delayed until the data is ready. During the DMA transfer, SI\_DRAM\_ADDR is updated. At the end of the transfer, it points to the last word in RDRAM that was written to. #### 0x0480 0008 - SI\_PIF\_AD\_WR4B * * * | SI\_PIF\_AD\_WR4B `0x0480 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[31:24\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-0 | **DATA:** 32-bit data to be transferred to PIF-RAM | This register is basically broken: it was probably meant to allow for a DMA transfer of 4 bytes to PIF\_RAM, but in reality it is just directly connected to an internal register of SI holding the "current data" word for PIF transfers. Curiously enough, writing to it does seem to trigger a WR4B serial packet to PIF, so it can actually be used to transfer a word, with the following sequence: * First, we need to populate the internal SI register that holds the "current address" to PIF. To do so, we can simply trigger a read from PIF\_RAM at the desired location. The read will be executed, and the internal SI register will hold that address. * Now, write a 32-bit word of data to \`SI\_PIF\_AD\_WR4B\`. This goes into the internal "current data" register of SI, and triggers a WR4B transfer using the "current address" (loaded with the previous trick) and the "current data", effectively writing the word to PIF RAM. The sequence triggers a non-blocking write, but also direct writes to the memory mapped area of PIF\_RAM are non-blocking, so there does not seem to be any reason of using this register. After writing to this register, the DMA\_BUSY bit in SI\_STATUS goes to 1 for a small amount of time, even though no actual DMA transfer is executed. #### 0x0480 0010 - SI\_PIF\_AD\_WR64B * * * SI\_PIF\_AD\_WR64B `0x0480 0010` **TODO** #### 0x0480 0014 - SI\_PIF\_AD\_RD4B * * * | SI\_PIF\_AD\_RD4B `0x0480 0014` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[31:24\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-0 | **DATA:** 32-bit data to be transferred to PIF-RAM | This register is very similar to SI\_PIF\_AD\_WR4B: it also is directly mapped to the internal "current data" word for PIF transfers, but writing to it triggers a RD4B serial packet to PIF. So a read is actually executed and the data is fetched into "current data" and is thus available for reading later. No DMA is performed though. Using a trick similar to that described in SI\_PIF\_AD\_WR4B, it is possible to actually triggers a memory read from a selected location, but it is superfluous since it is sufficient to access the memory mapped PIF-RAM to do the same. After writing to this register, the DMA\_BUSY bit in SI\_STATUS goes to 1 for a small amount of time, even though no actual DMA transfer is executed. #### 0x0480 0018 - SI\_STATUS * * * | SI\_STATUS `0x0480 0018` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | R-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | INTERRUPT | DMA\_STATE\[3:0\] | | | | | 7:0 | U-0 | U-0 | U-0 | U-0 | R-0 | R-0 | R-0 | R-0 | | PCH\_STATE\[3:0\] | | | | DMA\_ERROR | READ\_PENDING | IO\_BUSY | DMA\_BUSY | | | | | --- | --- | | bit 31-13 | **Undefined:** Initialized to `0` | | bit 12 | **INTERRUPT:** Copy of SI interrupt flag from [MIPS Interface](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0008_-_MI_INTERRUPT "MIPS Interface")
,
    Writing any value to SI\_STATUS acknowledges the interrupt.
    SI interrupts occur when a DMA or a direct write finishes. | | bit 11-8 | **DMA\_STATE\[3:0\]:** Internal DMA state. Non-zero values indicate activity. | | bit 7-4 | **PCH\_STATE\[3:0\]:** Internal PIF channel state. Non-zero values indicate activity. | | bit 3 | **DMA\_ERROR:** Set when overlapping DMA requests occur, or when writing to a misaligned address. Can only be cleared with a power reset. | | bit 2 | **READ\_PENDING:** Unknown? | | bit 1 | **IO\_BUSY:** Set when a direct memory write to PIF\_RAM is in progress. | | bit 0 | **DMA\_BUSY:** Set when a read or write DMA, or an IO write, is in progress. | iQue Player ----------- On the iQue Player, the Serial Interface was substantially reworked as the PIF-NUS was removed from the design; the DMA engine no longer transfers data to/from the PIF over a serial line. DMA writes from SDRAM to the SI simply acquires 32 bytes of data from SDRAM, processing of this data is deferred until DMA reads from the SI back to SDRAM similarly to N64. When a DMA read back to SDRAM is requested, the data supplied by the DMA write is processed and a response is fetched from joybus devices. The format of the data is stricter than on N64 and only properly supports small commands. Each joybus channel is allotted 8 bytes (effectively 7) in the data: \- The first byte of the first channel must be sent as 0xFF, otherwise the result will be meaningless (a buffer full of 0xFF). The first byte of the other channels is ignored and skipped over. - The second byte is the TX length as on N64, the lower 6 bits determine how many bytes the joybus device receives while bit 7 may behave the same as on n64, skipping the device. If the number of bytes to send is 0 the device receives nothing. - The third byte is the RX length as on N64, the lower 3 (TOVERIFY) bits determine how many bytes it should expect to receive from the joybus device. - The remaining 5 bytes contain data that may be sent to the joybus device. If the TX length is greater than 5, the last byte is repeated out to the device to fill the rest of the packet. When the data is DMA'd back to RAM, each channel contains: \- The first byte is always 0xFF, irrespective of what was there when the data was uploaded. - The second byte is the lower 6 bits of the input TX length. - The third byte is the lower 3 bits of the input RX length and bits 6 & 7 are error bits like on N64. - The fourth byte is always the fourth byte from the input data. - The remaining 4 bytes contain RX data received from the joybus device. If the amount of data received was less than 4 bytes, the remaining bytes are the same as the input data. Unlike on N64, it is not possible to upload a block of data to the SI and use it multiple times. It is also not necessary to set any bits in byte 63, since the DMA is just 32 bytes. Retrieved from "[https://n64brew.dev/wiki/Serial\_Interface?oldid=5663](https://n64brew.dev/wiki/Serial_Interface?oldid=5663) " --- # RDRAM Interface - N64brew Wiki [](https://n64brew.dev/wiki/RDRAM_Interface#) RDRAM Interface =============== The RDRAM Interface (or **RI**) is one of multiple I/O interfaces in the RCP. It acts as a controller for the RDRAM channel to which one or more RDRAM modules are connected. It converts memory accesses from the system into RDRAM protocol commands for the RDRAM bus. The RI integrates a RDRAM ASIC Cell (or **RAC**) in order to take care of the low level details of the RDRAM bus. Further details about such a RAC can be found in [datasheets for similar RACs](https://www.datasheetarchive.com/pdf/download.php?id=04f8a219efa5e2304d0e403c3f77e759adedf8&type=O&term=rac%2520rdram) (however not necessarily the same version used by the N64). There are two sets of memory mapped registers for RDRAM configuration. One set is specifically for writing to or reading from the configuration registers in one or all individual [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM") module(s). The other set, defined in the next section, configure this RDRAM interface. Refer to [Memory map](https://n64brew.dev/wiki/Memory_map "Memory map") for the full map, including all RDRAM-related segments. The base address for these registers is `0x0470 0000`, also known as RI\_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 `0xA000 0000` to the address. As an example, to directly write to the RI\_MODE register, use address `0xA470 0000`. Some information is available in [US6593929.pdf](https://patentimages.storage.googleapis.com/cc/33/96/6e54e1628ec0f9/US6593929.pdf) in paragraph "Example Memory Controller/Interface Registers" and associated figures (37A-H). Contents -------- * [1 Registers](https://n64brew.dev/wiki/RDRAM_Interface#Registers) * [1.1 0x0470 0000 - RI\_MODE](https://n64brew.dev/wiki/RDRAM_Interface#0x0470_0000_-_RI_MODE) * [1.2 0x0470 0004 - RI\_CONFIG](https://n64brew.dev/wiki/RDRAM_Interface#0x0470_0004_-_RI_CONFIG) * [1.3 0x0470 0008 - RI\_CURRENT\_LOAD](https://n64brew.dev/wiki/RDRAM_Interface#0x0470_0008_-_RI_CURRENT_LOAD) * [1.4 0x0470 000C - RI\_SELECT](https://n64brew.dev/wiki/RDRAM_Interface#0x0470_000C_-_RI_SELECT) * [1.5 0x0470 0010 - RI\_REFRESH](https://n64brew.dev/wiki/RDRAM_Interface#0x0470_0010_-_RI_REFRESH) * [1.6 0x0470 0014 - RI\_LATENCY](https://n64brew.dev/wiki/RDRAM_Interface#0x0470_0014_-_RI_LATENCY) * [1.7 0x0470 0018 - RI\_ERROR](https://n64brew.dev/wiki/RDRAM_Interface#0x0470_0018_-_RI_ERROR) * [1.8 0x0470 001c - RI\_BANK\_STATUS](https://n64brew.dev/wiki/RDRAM_Interface#0x0470_001c_-_RI_BANK_STATUS) * [2 Bank Status Tracking](https://n64brew.dev/wiki/RDRAM_Interface#Bank_Status_Tracking) * [3 Memory addressing](https://n64brew.dev/wiki/RDRAM_Interface#Memory_addressing) * [3.1 Accesses outside of mapped RDRAM chips](https://n64brew.dev/wiki/RDRAM_Interface#Accesses_outside_of_mapped_RDRAM_chips) * [4 Count](https://n64brew.dev/wiki/RDRAM_Interface#Count) * [5 RI\_SELECT configurations](https://n64brew.dev/wiki/RDRAM_Interface#RI_SELECT_configurations) Registers ========= **Table Notation:** R = Readable bit W = Writable bit U = Undefined/Unused bit -n = Default value n at boot -? = Unknown default value \[x:y\] = Specifies bits x to y, inclusively #### 0x0470 0000 - RI\_MODE * * * | RI\_MODE `0x0470 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | RW-1 | RW-1 | RW-? | RW-? | | — | — | — | — | STOP\_R | STOP\_T | OP\_MODE\[1:0\] | | | | | | --- | --- | | bit 31-4 | **Undefined:** Initialized to `0` | | bit 3 | **STOP\_R:** Automatic halting of RAC clock used for receive logic when not in use, normally enabled.
1 = Enabled
0 = Disabled | | bit 2 | **STOP\_T:** Automatic halting of RAC clock used for transmit logic when not in use, normally enabled.
1 = Enabled
0 = Disabled | | bit 1-0 | **OP\_MODE\[1:0\]:** Controls how Serial Mode (SMode) packets are sent to RDRAM modules. [\[1\]](http://www.bitsavers.org/components/nec/_dataBooks/1995_NEC_Application_Specific_Memory.pdf)
Usually set to `10`.
11 = Unknown
10 = Sends a packet before each RDRAM transaction. Tells the modules to enter standby mode after receiving each transaction.
01 = Sends a packet every 4 BusClk cycles. Tells the modules to always be active (consumes more power and usually not used).
00 = Sends continuous packets. After 272 BusClk cycles, all RDRAM modules will enter a reset mode.
    _Due to the timing differences, some changes will require a delay to become active._ | #### 0x0470 0004 - RI\_CONFIG * * * | RI\_CONFIG `0x0470 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | U-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | — | AutoCC | CC \[5:0\] | | | | | | READ?/WRITE: \[6\] Enable/Disable automatic current calibration from controller. Corresponds to the RAC CCtlEn input signal. It selects whether the value CC\[5:0\] will be written to current control register (AutoCC=0), or if an internally generated value should be used (AutoCC=1). \[5:0\] Current Control Input. The value to be loaded into current control register when AutoCC is disabled. Corresponds to the RAC CCtlI input signal. #### 0x0470 0008 - RI\_CURRENT\_LOAD * * * | RI\_CURRENT\_LOAD `0x0470 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | WRITE: Any write to this register causes a new value to be loaded into the RAC current control register. Corresponds to the RAC CCtlLd input signal. The value loaded depends on the contents of the RI\_CONFIG register, see there for details. TOVERIFY: When AutoCC=1 in RI\_CONFIG and this register is written, a sufficient delay should be observed to let CC autocalibration stabilize. READ: This register is intended to be write-only, the read behavior is unintended and returns a collection of bits from other registers: \[0\] : RI\_ERROR Ack \[1\] : 1 TOVERIFY always 1? \[2\] : 1 TOVERIFY always 1? \[3\] : RI\_MODE STOP\_R \[4\] : RI\_SELECT TSEL\[0\] \[32:5\] : 0 TOVERIFY always 0? #### 0x0470 000C - RI\_SELECT * * * | RI\_SELECT `0x0470 000C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | TSEL \[3:0\] | | | | RSEL \[3:0\] | | | | | | | | --- | --- | | bit 31-8 | **Undefined:** Undefined | | bit 7-4 | **TSEL\[3:0\]:** Configure transmit signals timings. Corresponds to RAC signals B{C,D,E}Sel. | | bit 3-0 | **RSEL\[3:0\]:** Configure receive signals timings. Corresponds to RAC signals R{C,D}Sel. | **Extra Details:** IPL3 configures TSEL to `0b0001` and RSEL to `0b0100`. It is currently unclear if this is the only valid configuration. #### 0x0470 0010 - RI\_REFRESH * * * | RI\_REFRESH `0x0470 0010` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | RW-? | RW-? | RW-? | RW-? | RW-? | | — | MultiBank\[3:0\] | | | | Opt | En | Bank | | 15:8 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | DirtyRefreshDelay \[7:0\] | | | | | | | | | 7:0 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | CleanRefreshDelay \[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-?? | **Undefined:** Undefined | | bit ??-19 | **MultiBank\[3:0\]:** Bitfield indicating multibank RDRAM modules. Up to four multibank modules are tracked, enough to fill 8MiB with 4x2MiB modules.
Probably why RDRAM modules are re-ordered with multibanks modules first during initialization in IPL3. | | bit 18 | **Opt:** Optimize. Usually set to `0x1`. | | bit 17 | **En:** Automatic Refresh Enable. Usually set to `0x1`. | | bit 16 | **Bank:** Oscillates between 0 and 1 during operation. | | bit 15-8 | **DirtyRefreshDelay\[7:0\]:** Cycles to delay after refresh when the bank was previously dirty. Usually set to `54`, which is `tRETRYREFRESHDIRTY / 4`. | | bit 7-0 | **CleanRefreshDelay\[7:0\]:** Cycles to delay after refresh when the bank was previously clean. Usually set to `52`, which is `` tRETRYREFRESHCLEAN / 4`.` `` | **Extra Details:** The automatic refresh operation, when enabled, is triggered by VI HSYNC timing. This forces the refresh operation to happen during HBLANK so it can't block VI scanout. As a single RDRAM refresh command refreshes 2 rows on all banks, the standard NTSC/PAL video timings result in refreshing all 512 rows in 15.6ms or 16.4ms respectively, meeting the RDRAM spec of 17ms. VI HSYNC defaults to 41us on power-cycle. This results in a 10.5ms refresh cycle, causing a noticeable memory bandwidth reduction until the VI is configured. #### 0x0470 0014 - RI\_LATENCY * * * | RI\_LATENCY `0x0470 0014` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | U-? | U-? | U-? | U-? | RW-? | RW-? | RW-? | RW-? | | — | — | — | — | DmaLatencyOverlap\[4:0\] | | | | | | | | --- | --- | | bit 31-4 | **Undefined:** Undefined | | bit 3-0 | **DmaLatencyOverlap\[4:0\]:** ? Defaults to `0xf` | **Speculation:** This might control the maximum size of DMA transfers. RCP supports DMA bursts of upto 16 Octbytes (128 bytes), which matches the default value. Perhaps this register allows forces a smaller transfer size and allows better interleaving of multiple DMA requests, or for a lower guaranteed latency when a high-priority device (like VI) requests a DMA transfer. This register isn't used by any known N64 software, maybe it's broken. Maybe it didn't improve performance. #### 0x0470 0018 - RI\_ERROR * * * | RI\_ERROR `0x0470 0018` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | U-? | U-? | U-? | U-? | U-? | R-? | R-? | R-? | | — | — | — | — | — | Over | Nack | Ack | | | | | --- | --- | | bit 31-3 | **Undefined:** Undefined | | bit 2 | **Over:** OverRangeError. Set when reading/writing any addresses in the range `0x0080 0000` to `0x03EF FFFF`, even if an RDRAM bank has been mapped there. However note that request packets are still sent out over the RDRAM bus even if this error was flagged. | | bit 1 | **NAck:** UnexpectedNAck. Set when RI sees an unexpected NAak (probably because bank status bits were wrong). | | bit 0 | **Ack:** MissingAck. Set when RI doesn't see an Ack (like when no RDRAM device was mapped to that address).

This bit is set sometime during IPL3 init, presumably due to probing memory size. | Writing any value this register will clear any errors. #### 0x0470 001c - RI\_BANK\_STATUS * * * | RI\_BANK\_STATUS `0x0470 001c` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | BankDirtyBits\[7:0\] | | | | | | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | BankValidBits\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-16 | **Undefined:** Undefined | | bit 15-8 | **BankDirtyBits\[7:0\]:** One per bank. Set when the currently open row has been written. Cleared when a new row is opened but not yet written to. | | bit 7-0 | **BankValidBits\[7:0\]:** One per bank. Set when a row is opened. Presumably only cleared by a refresh cycle. | Writing any value to this register will set all valid bits to 0 and all dirty bits to 1. This causes the RI to become out-of-sync with RDRAM and will result in errors. Memory read/write requests to banks mapped above 8MiB do not update any of these bits. This may also cause out-of-sync errors as the RI appears to be unable to track the current open row state for banks above 8MiB. **Note:** Some sources such as libultra's `rcp.h` header call this register `RI_WERROR`, however this register is unrelated to errors. The name `RI_BANK_STATUS` comes from a patent and is much more descriptive of the function of this register. Bank Status Tracking ==================== Each 1 MiB bank can only have one row (2 KiB) open. The only way to open a row on with version 1 of the Rambus spec is to just attempt a read or write operation. If the row is already open, the operation succeeds (hits) and the Rambus device responds with an Ack packet. If the row wasn't open, the operation fails and the Rambus device responds with a NAck packet, while simultaneously closing the currently open row and loading the next. This takes even longer if the current row is dirty and needs to be written back to the dram array first. The Controller must send a new request packet once the device has finished opening the row. One possible implementation for a Rambus controller is to just retry any operations that miss, they will eventually succeed. But RI doesn't have any retry logic. It does detect unexpected NAcks and set the **NAck** bit in the **RI\_ERROR** register. Instead, RI tracks the current status of the state machine for each bank. Some of this shadow state machine is exposed via the **RI\_BANK\_STATUS** register where you can find the row valid and row dirty bits. The **MultiBank** field of the **RI\_REFRESH** register also has some effect, as the two banks of a 2MiB chip share some resources. _(Research Needed, exactly which timings are affected by Multibank?)_ Other parts of the shadow state machine are not exposed via registers, such as if the chips are currently executing a refresh operation, or which row is currently open. With this state tracking, RI always knows which requests will cause a miss and how long it needs to wait before resending the request packet. RI only has resources for tracking 8 banks (of 1 MiB each, for a total of 8 MiB) and these banks are hardwired into the bottom 8 MiB of the memory-space, as 8 continuous banks. While you could initialise more Rambus devices in the space above 8 MiB, or move one of the existing devices, without Bank Status tracking, the timings will be wrong _(Research Needed, wrong in what way, presumably RI always assumes operations will always hit?)._ Bank Status Tracking also interferes with any attempt to use the Rambus' Address Swapping feature, as there is no way to configure Bank Status tracking's address to match the new layout. Memory addressing ================= RI translate memory accesses in the range `0x0000 0000` - `0x03FF FFFF` into suitable RDRAM protocol packets with proper command type and 36 bit address. See [RDRAM addressing](https://n64brew.dev/wiki/RDRAM "RDRAM") paragraph for details about how 36bit addresses are interpreted. Address conversion done by RI (TOVERIFY): | Address Range | | Adr\[35:29\] | Adr\[28:20\] | Adr\[19:11\] | Adr\[10:0\] | BCastRWrite | Description | | --- | --- | --- | --- | --- | --- | --- | --- | | `0x0000 0000` | `0x007F FFFF` | 0 | (address >> 20) & 0x3F | (address >> 11) & 0x1FF | address & 0x7FF | 0 | Memory-space access | | `0x0080 0000` | `0x03EF FFFF` | 0 | (address >> 20) & 0x3F | (address >> 11) & 0x1FF | address & 0x7FF | 0 | Broken Memory-space access

Not covered by bank status tracking | | `0x03F0 0000` | `0x03F7 FFFF` | 0 | (address >> 10) & 0x1FF | (address >> 10) & 0x1FF | address & 0x3FF | (address >> 19) & 0x1 == 0 | Register-space access | | `0x03F8 0000` | `0x03FF FFFF` | 0 | (address >> 10) & 0x1FF | (address >> 10) & 0x1FF | address & 0x3FF | (address >> 19) & 0x1 == 1 | Broadcast register write | Examples : Assuming a standard RDRAM configuration of 4x2x9Mbit RDRAM each with IdField = 2\*k for module k = 0..3 and SwapField = 0 for all modules (eg. no address swapping, Adr = AdrS). * Reading at address 0x003A BCDE, gives the following Adr\[35:20\] = 3, Adr\[19:0\] = 0xABCDE, BCastRWrite = 0. Since we have 2x9Mbit modules, Adr\[20\] is ignored for Id matching and therefore RDRAM with IdField == 2 gives a match. This means RDRAM module 1 will be read at address 0x1ABCDE. * Writing at address 0x03F0 0808, gives Adr\[35:20\] = 2, Adr\[19:0\] = 8, BCastRWrite = 0. Which means writing to RDRAM module 1 delay register. * Writing at address 0x03F8 0008, gives BCastRWrite = 1, Adr\[19:0\] = 8. Which means broadcast writing to all RDRAM modules delay registers. Remarks : * Early version of RCP reserved fewer bits for RDRAM register address (eg. Adr\[35:20\] = (address >> 9) & 0x3FF; Adr\[19:0\] = address & 0x1FF) which didn't allow to access RDRAM register 128 (Row register) which is at offset 0x200. * The presented address map has space for upto 32x 2x9Mbit RDRAM modules. However, a RI only has bank tracking resources for 8MiB. * Standard DRAM initialization only supports up to 8 modules, but can mix 2x9Mbit and 1x9Mbit modules. In that case, 2x9Mbit modules are placed before 1x9Mbit modules. * Standard DRAM initialization procedure, doesn't make use of address swapping feature, because bank tracking doesn't support it. * Register-space addresses duplicates the content between Adr\[28:20\] and Adr\[19:11\] to not be affected by RDRAM address swapping features. Indeed, whereas address swapping is desirable for RDRAM memory to benefit from row internal row caching, registers won't benefit from the swapping and would complicate usage of registers in such a case. #### Accesses outside of mapped RDRAM chips Memory-space accesses (0x00000000 - 0x03EFFFFF) that hit addresses where there is no RDRAM chip mapped will result in a sort of "no-operation" behavior: reads will return zero, and writes will be ignored. For instance, in a N64 with 4 MiB (no expansion pak), reads at the 5 MiB are not a mirror of the reads at the first MiB: they just return zero because no chips in the RAMBUS will reply to those requests. The same goes for accessing addresses above 8 MiB, no Rambus device will respond to requests. This is true in theory for RDRAM buses, but there seems to be a weird behavior, at least during reads, causing some areas of the address space to return non-zero values when read. These 32-bit non-zero values can be seen every 0x80 bytes, in an area of 8 KiB, repeating every 512 KiB. The dump below has been taken from a N64; an identical pattern can be observed on different consoles, though an extensive comparison has not been run. You can see the non-zero values present in 32-bit slots every 0x80 bytes (though not all slots contain a value), in range 0 - 8KiB (0x2000), and then repeating again after 512 KiB (0x80000 - 0x82000), and so on every 512 KiB. What seems to happen is that somehow a RDRAM register value is shown as part of a memory read; this is probably a RI bug, but it has not been fully investigated yet. For instance, the value `0xb4190010` shown at several addresses (eg: 0x1400) is a very common value for the [RDRAM register DeviceType](https://n64brew.dev/wiki/RDRAM#0x00_-_DeviceType "RDRAM") . 00000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 00380 78 01 fe 02 00 00 00 00 00 00 00 00 00 00 00 00 |x...............| \* 00d80 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00d90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 00e40 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00e50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 00f80 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00f90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 01380 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 01390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 01400 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 01410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 01480 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 01490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 01540 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 01550 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 01600 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 01610 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 016c0 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 016d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 01740 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 01750 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 804c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 806c0 94 01 fe 02 00 00 00 00 00 00 00 00 00 00 00 00 |................| 806d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80700 9c 01 fe 02 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80710 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80780 a6 01 fe 02 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80790 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 807c0 ae 01 fe 02 00 00 00 00 00 00 00 00 00 00 00 00 |................| 807d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80840 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80850 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 808c0 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 808d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80940 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80950 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 809c0 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 809d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80a40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80b80 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80b90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80bc0 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80bd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80c40 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80c50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80cc0 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80cd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80d40 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80d50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80dc0 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80dd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80ec0 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80ed0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 80fc0 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 80fd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 81380 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 81390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 81480 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 81490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 815c0 b4 19 00 10 00 00 00 00 00 00 00 00 00 00 00 00 |................| 815d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 816c0 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 816d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 81700 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 81710 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 81780 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 81790 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 817c0 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 817d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 81ac0 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 81ad0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 81b00 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 81b10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 81b80 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 81b90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* 81bc0 fe 03 fe 03 00 00 00 00 00 00 00 00 00 00 00 00 |................| 81bd0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| \* Count ===== RCP supports DMA bursts upto a maximum of 128 bytes (16 Octwords) The recommended mapping the Rambus request Count field from the Rambus datasheet is `Count = NumBytes + Address[2:0]`, as this produces the correct byte masking for writes that aren't 64bit aligned. But RI actually implements this mapping from the RCP as: Count\[6:3\] = NumBytes\[6:3\] Count\[2:0\] = NumBytes\[2:0\] + Address\[2:0\] Which drops any carries from bit 2 to bit 3. This works fine for unaligned writes that fit within a single 64bit transfer (and all unaligned writes from the CPU fit this rule). But you can use PI to create misaligned DMA bursts of any length from 1 to 128 bytes, and it's possible to cause a dropped carry. Testing shows this results in the DMA transfers of `NumBytes - Address[2:0]` bytes. It's possible to compensate for this "bug" by increasing the transfer length (at least for short transfers under 128 bytes). SI also allows for misaligned DMA transfers, but exact results haven't been documented. All other devices don't allow the lower bits of address to be set. RI\_SELECT configurations ========================= **Warning: This section contains speculative information that is in need of further research.** It is currently unclear what the full set of working configurations for the TSEL and RSEL fields of RI\_SELECT are. A datasheet for a Rambus Memory Controller (RMC), a component similar in function to the RI that interfaces with a Rambus ASIC Cell (RAC), refers to the IPL3 configuration (`TSEL=0b0001, RSEL=0b0100`) as "Option A". The same datasheet mentions an alternative configuration, "Option Z", configured with (`TSEL=0b0010, RSEL=0b1000`) and considers this configuration preferable over Option A: > Option Z is the recommended timing option for the RMC. This minimizes the setup times of all inputs. > > — RMC datasheet Option Z has been tested on hardware and does not appear to cause noticeable instability in RDRAM operation, although it is still unclear whether the claim about Option Z being preferable is applicable to the RI. Other "random" configurations for TSEL and RSEL were also attempted but these quickly crashed, however it is still unclear whether the two options mentioned by the RMC datasheet are the extent of possible configurations, and which configuration should be preferred on N64. Retrieved from "[https://n64brew.dev/wiki/RDRAM\_Interface?oldid=5302](https://n64brew.dev/wiki/RDRAM_Interface?oldid=5302) " --- # Expansion Pak - N64brew Wiki [](https://n64brew.dev/wiki/Expansion_Pak#) Expansion Pak ============= The **Expansion Pak** consists of 4 MB (4,194,304 bytes) of random access memory (RAM)—which is [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM") , the same type of memory used inside the console itself—increasing the Nintendo 64 console's RAM from 4 MB to 8 MB of contiguous main memory. It is installed in a port on top of the console and replaces the pre-installed [Jumper Pak](https://n64brew.dev/wiki/Jumper_Pak "Jumper Pak") , which is simply a RAMBUS terminator. The Expansion Pak is required for two retail games: _Donkey Kong 64_ and _The Legend of Zelda: Majora’s Mask._ Some other games, like _Rayman 2: The Great Escape_ are capable of using the Expansion Pak if available, but do not require it. Details of how the Expansion Pak is detected are in the [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM") article. Contents -------- * [1 Hardware](https://n64brew.dev/wiki/Expansion_Pak#Hardware) * [1.1 Variations](https://n64brew.dev/wiki/Expansion_Pak#Variations) * [1.1.1 Version 1 - (No extra resistor, no bodge)](https://n64brew.dev/wiki/Expansion_Pak#Version_1_-_(No_extra_resistor,_no_bodge)) * [1.1.2 Version 2 - (Bodge from Nintendo)](https://n64brew.dev/wiki/Expansion_Pak#Version_2_-_(Bodge_from_Nintendo)) * [1.1.3 Version 3 - (Resistor added to PCB design)](https://n64brew.dev/wiki/Expansion_Pak#Version_3_-_(Resistor_added_to_PCB_design)) * [1.2 Open Source Recreation](https://n64brew.dev/wiki/Expansion_Pak#Open_Source_Recreation) Hardware -------- ### Variations The Nintendo Expansion Pak went through several PCB revisions. The most notable change was to add in an extra termination resistor to Pin 20 (SIn - Initialization Daisy Chain Input). ##### Version 1 - (No extra resistor, no bodge) *  [](https://n64brew.dev/wiki/File:V1-Front.jpg "Front side of a Version 1 Expansion Pak (No resistor, no bodge)[1]") Front side of a Version 1 Expansion Pak (No resistor, no bodge)[\[1\]](https://n64brew.dev/wiki/Expansion_Pak#cite_note-1) *  [](https://n64brew.dev/wiki/File:V1-Back.jpg "Back side of a Version 1 Expansion Pak (No resistor, no bodge)[2]") Back side of a Version 1 Expansion Pak (No resistor, no bodge)[\[2\]](https://n64brew.dev/wiki/Expansion_Pak#cite_note-2) ##### Version 2 - (Bodge from Nintendo) *  [](https://n64brew.dev/wiki/File:Bodge-Front.jpg "Front side of a Bodged Expansion Pak from Nintendo") Front side of a Bodged Expansion Pak from Nintendo *  [](https://n64brew.dev/wiki/File:Bodge-Back.jpg "Back side of a Bodged Expansion Pak from Nintendo") Back side of a Bodged Expansion Pak from Nintendo ##### Version 3 - (Resistor added to PCB design) *  [](https://n64brew.dev/wiki/File:Final-Front.jpg "Front side of the modified PCB design that includes the resistor") Front side of the modified PCB design that includes the resistor *  [](https://n64brew.dev/wiki/File:Final-Back.jpg "Back side of the modified PCB design that includes the resistor") Back side of the modified PCB design that includes the resistor ### Open Source Recreation An open source, 1:1 recreation of the OEM Expansion Pak can be found [here](https://github.com/MasonStooksbury/OEM-N64-Expansion-Pak) complete with pictures and the full KiCad project. *  [](https://n64brew.dev/wiki/File:Recreated_OEM_Expansion_Pak.png "Recreated OEM Expansion Pak") Recreated OEM Expansion Pak *  [](https://n64brew.dev/wiki/File:Schematic.png "Electrical schematic for the OEM Expansion Pak") Electrical schematic for the OEM Expansion Pak *  [](https://n64brew.dev/wiki/File:Front_side_of_the_schematic.png "Front side of the PCB in KiCad") Front side of the PCB in KiCad *  [](https://n64brew.dev/wiki/File:Back_side_of_the_schematic.png "Back side of the PCB in KiCad") Back side of the PCB in KiCad 1. [↑](https://n64brew.dev/wiki/Expansion_Pak#cite_ref-1) Picture provided by: [https://www.reddit.com/user/URA\_CJ/](https://www.reddit.com/user/URA_CJ/) 2. [↑](https://n64brew.dev/wiki/Expansion_Pak#cite_ref-2) Picture provided by: [https://www.reddit.com/user/URA\_CJ/](https://www.reddit.com/user/URA_CJ/) Retrieved from "[https://n64brew.dev/wiki/Expansion\_Pak?oldid=5328](https://n64brew.dev/wiki/Expansion_Pak?oldid=5328) " --- # MIPS III instructions - N64brew Wiki [](https://n64brew.dev/wiki/MIPS_III_instructions#) MIPS III instructions ===================== The NEC VR4300 uses an instruction set that is nearly identical to that of the MIPS III Instruction Set Architecture (ISA). While the instruction set is explained in detail in the [VR4300 datasheet](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf "File:VR4300-Users-Manual.pdf") , this article serves as a reformatted reference for those same instructions, including any quirks or additional reminders that are useful when programming something for the N64. See also [MIPS Assembly](https://n64brew.dev/wiki/MIPS_Assembly "MIPS Assembly") . If you are looking for instructions to perform certain actions, use your browser's search feature with `CTRL+F`. Contents -------- * [1 Instruction Notation Key](https://n64brew.dev/wiki/MIPS_III_instructions#Instruction_Notation_Key) * [2 CPU Instruction Set](https://n64brew.dev/wiki/MIPS_III_instructions#CPU_Instruction_Set) * [2.1 Hazards](https://n64brew.dev/wiki/MIPS_III_instructions#Hazards) * [2.2 ADD](https://n64brew.dev/wiki/MIPS_III_instructions#ADD) * [2.3 ADDI](https://n64brew.dev/wiki/MIPS_III_instructions#ADDI) * [3 FPU Instruction Set](https://n64brew.dev/wiki/MIPS_III_instructions#FPU_Instruction_Set) * [4 Pseudo-Instructions](https://n64brew.dev/wiki/MIPS_III_instructions#Pseudo-Instructions) Instruction Notation Key ======================== | Symbol | Description | | --- | --- | | base | An immediate value which represents a base address | | cofun | Coprocessor function | | r | 64-bit register | | rd | Destination register | | rs | Source register | | rt | Temporary register | | GPR | General Purpose Register | | GPR\[r\] | The general purpose register 'r' | | label | Label name | | immediate | A hardcoded value via an assembler variable or literal | | offset | An immediate value added to base address to form a virtual address | | sa | Shift this number of bits left or right | | [sign-extended](https://en.wikipedia.org/wiki/Sign_extension "w:Sign extension") | Extends the upper bits with 1's or 0's to preserve the sign and value of the number | | [zero-extended](https://en.wikipedia.org/wiki/Sign_extension#Zero_extension "w:Sign extension") | Extends the upper bits with 0's. | CPU Instruction Set =================== Every instruction has a 32-bit instruction word that the CPU uses to identify the instruction and any arguments used in it. The upper 6 bits is the instruction's opcode, however some instructions use additional constant bits to further define the operation. For example, the SPECIAL opcode is used for numerous instructions including ADD, SUB, AND, BREAK, and more. | Symbol | Description | | --- | --- | | s | source register number | | d | destination register number | | t | temporary register number | | k | literal/immediate value | | b | base address | | f | offset address | | x | coprocessor number | Typically an assembler will be used when programming in order to format the proper instruction word, when generating a ROM file. **Most developers can safely ignore the instruction word column entirely.** _However, most assemblers implement **branch instructions** differently! Instead of an offset value, they usually require the use of a label or absolute address._ | Mnemonic | Description | 32-bit Instruction Word | | --- | --- | --- | | [ADD](https://n64brew.dev/wiki/MIPS_III_instructions#ADD)
       rd, rs, rt | Add rs and rt, store result in rd | 0000 00ss ssst tttt dddd d000 0010 0000 | | [ADDI](https://n64brew.dev/wiki/MIPS_III_instructions#ADDI)
      rt, rs, immediate | Add sign-extended 16bit _immediate_ and rs, store result in rt | 0010 00ss ssst tttt kkkk kkkk kkkk kkkk | | [ADDIU](https://n64brew.dev/wiki/MIPS_III_instructions#ADDIU)
     rt, rs, immediate | Add sign-extended 16bit _immediate_ and rs, store result in rt | 0010 01ss ssst tttt kkkk kkkk kkkk kkkk | | [ADDU](https://n64brew.dev/wiki/MIPS_III_instructions#ADDU)
      rd, rs, rt | Add rs and rt, store result in rd | 0000 00ss ssst tttt dddd d000 0010 0001 | | [AND](https://n64brew.dev/wiki/MIPS_III_instructions#AND)
       rd, rs, rt | AND rs with rt, store result in rd | 0000 00ss ssst tttt dddd d000 0010 0100 | | [ANDI](https://n64brew.dev/wiki/MIPS_III_instructions#ANDI)
      rt, rs, immediate | AND rs with zero-extended _immediate_, store result in rt | 0011 00ss ssst tttt kkkk kkkk kkkk kkkk | | [BCzF](https://n64brew.dev/wiki/MIPS_III_instructions#BCzF)
      offset | If CPz's CpCond is false, branch to address (delay slot + _offset_) | 0100 xx01 0000 0000 ffff ffff ffff ffff | | [BCzFL](https://n64brew.dev/wiki/MIPS_III_instructions#BCzFL)
     offset | If CPz's CpCond is false, branch to address (delay slot + _offset_), otherwise discard delay slot instruction | 0100 xx01 0000 0010 ffff ffff ffff ffff | | [BCzT](https://n64brew.dev/wiki/MIPS_III_instructions#BCzT)
      offset | If CPz's CpCond is true, branch to address (delay slot + _offset_) | 0100 xx01 0000 0001 ffff ffff ffff ffff | | [BCzTL](https://n64brew.dev/wiki/MIPS_III_instructions#BCzTL)
     offset | If CPz's CpCond is true, branch to address (delay slot + _offset_), otherwise discard delay slot instruction | 0100 xx01 0000 0011 ffff ffff ffff ffff | | [BEQ](https://n64brew.dev/wiki/MIPS_III_instructions#BEQ)
       rs, rt, offset | If rs equals rt, branch to address (delay slot + _offset_) | 0001 00ss ssst tttt ffff ffff ffff ffff | | [BEQL](https://n64brew.dev/wiki/MIPS_III_instructions#BEQL)
      rs, rt, offset | If rs equals rt, branch to address (delay slot + _offset_), otherwise discard delay slot instruction | 0101 00ss ssst tttt ffff ffff ffff ffff | | [BGEZ](https://n64brew.dev/wiki/MIPS_III_instructions#BGEZ)
      rs, offset | If rs is greater than or equal to zero, branch to address (delay slot + _offset_) | 0000 01ss sss0 0001 ffff ffff ffff ffff | | [BGEZAL](https://n64brew.dev/wiki/MIPS_III_instructions#BGEZAL)
    rs, offset | If rs is greater than or equal to zero, branch to address (delay slot + _offset_) and store next address to r31 | 0000 01ss sss1 0001 ffff ffff ffff ffff | | [BGEZALL](https://n64brew.dev/wiki/MIPS_III_instructions#BGEZALL)
   rs, offset | If rs is greater than or equal to zero, branch to address (delay slot + _offset_) and store next address to r31, otherwise discard delay slot instruction | 0000 01ss sss1 0011 ffff ffff ffff ffff | | [BGEZL](https://n64brew.dev/wiki/MIPS_III_instructions#BGEZL)
     rs, offset | If rs is greater than or equal to zero, branch to address (delay slot + _offset_), otherwise discard delay slot instruction | 0000 01ss sss0 0011 ffff ffff ffff ffff | | [BGTZ](https://n64brew.dev/wiki/MIPS_III_instructions#BGTZ)
      rs, offset | If rs is greater than zero, branch to address (delay slot + _offset_) | 0001 11ss sss0 0000 ffff ffff ffff ffff | | [BGTZL](https://n64brew.dev/wiki/MIPS_III_instructions#BGTZL)
     rs, offset | If rs is greater than zero, branch to address (delay slot + _offset_), otherwise discard delay slot instruction | 0101 11ss sss0 0000 ffff ffff ffff ffff | | [BLEZ](https://n64brew.dev/wiki/MIPS_III_instructions#BLEZ)
      rs, offset | If rs is less than or equal to zero, branch to address (delay slot + _offset_) | 0001 10ss sss0 0000 ffff ffff ffff ffff | | [BLEZL](https://n64brew.dev/wiki/MIPS_III_instructions#BLEZL)
     rs, offset | If rs is less than or equal to zero, branch to address (delay slot + _offset_), otherwise discard delay slot instruction | 0101 10ss sss0 0000 ffff ffff ffff ffff | | [BLTZ](https://n64brew.dev/wiki/MIPS_III_instructions#BLTZ)
      rs, offset | If rs is less than zero, branch to address (delay slot + _offset_) | 0000 01ss sss0 0000 ffff ffff ffff ffff | | [BLTZAL](https://n64brew.dev/wiki/MIPS_III_instructions#BLTZAL)
    rs, offset | If rs is less than zero, branch to address (delay slot + _offset_) and store next address to r31 | 0000 01ss sss1 0000 ffff ffff ffff ffff | | [BLTZALL](https://n64brew.dev/wiki/MIPS_III_instructions#BLTZALL)
   rs, offset | If rs is less than zero, branch to address (delay slot + _offset_) and store next address to r31, otherwise discard delay slot instruction | 0000 01ss sss1 0010 ffff ffff ffff ffff | | [BLTZL](https://n64brew.dev/wiki/MIPS_III_instructions#BLTZL)
     rs, offset | If rs is less than zero, branch to address (delay slot + _offset_), otherwise discard delay slot instruction | 0000 01ss sss0 0010 ffff ffff ffff ffff | | [BNE](https://n64brew.dev/wiki/MIPS_III_instructions#BNE)
       rs, rt, offset | If rs is not equal to rt, branch to address (delay slot + _offset_) | 0001 01ss ssst tttt ffff ffff ffff ffff | | [BNEL](https://n64brew.dev/wiki/MIPS_III_instructions#BNEL)
      rs, rt, offset | If rs is not equal to rt, branch to address (delay slot + _offset_), otherwise discard delay slot instruction | 0101 01ss ssst tttt ffff ffff ffff ffff | | [BREAK](https://n64brew.dev/wiki/MIPS_III_instructions#BREAK) | Causes breakpoint exception | 0000 00kk kkkk kkkk kkkk kkkk kk00 1101 | | [CACHE](https://n64brew.dev/wiki/MIPS_III_instructions#CACHE)
     op, offset(base) | Flush instruction or data cache at address (base + _offset_) to RAM | 1011 11bb bbbk kkkk ffff ffff ffff ffff | | [CFCz](https://n64brew.dev/wiki/MIPS_III_instructions#CFCz)
      rt, rd | Copy contents of CPz control register rd, to GPR rt | 0100 xx00 010t tttt dddd d000 0000 0000 | | [COPz](https://n64brew.dev/wiki/MIPS_III_instructions#COPz)
      cofun | Perform coprocessor operation | 0100 xx1k kkkk kkkk kkkk kkkk kkkk kkkk | | [CTCz](https://n64brew.dev/wiki/MIPS_III_instructions#CTCz)
      rt, rd | Copy contents of GPR rt, to CPz control register rd | 0100 xx00 110t tttt dddd d000 0000 0000 | | [DADD](https://n64brew.dev/wiki/MIPS_III_instructions#DADD)
      rd, rs, rt | Add rs and rt, store result in rd (mode restrictions) | 0000 00ss ssst tttt dddd d000 0010 1100 | | [DADDI](https://n64brew.dev/wiki/MIPS_III_instructions#DADDI)
     rt, rs, immediate | Add sign-extended 16bit _immediate_ and rs, store result in rt (mode restrictions) | 0110 00ss ssst tttt kkkk kkkk kkkk kkkk | | [DADDIU](https://n64brew.dev/wiki/MIPS_III_instructions#DADDIU)
    rt, rs, immediate | Add sign-extended 16bit _immediate_ and rs, store result in rt (mode restrictions) | 0110 01ss ssst tttt kkkk kkkk kkkk kkkk | | [DADDU](https://n64brew.dev/wiki/MIPS_III_instructions#DADDU)
     rd, rs, rt | Add rs and rt, store result in rd (mode restrictions) | 0000 00ss ssst tttt dddd d000 0010 1101 | | [DDIV](https://n64brew.dev/wiki/MIPS_III_instructions#DDIV)
      rs, rt | Divide signed rs by signed rt, store quotient in register _LO_ and remainder in _HI_ (mode restrictions) | 0000 00ss ssst tttt 0000 0000 0001 1110 | | [DDIVU](https://n64brew.dev/wiki/MIPS_III_instructions#DDIVU)
     rs, rt | Divide unsigned rs by unsigned rt, store quotient in register _LO_ and remainder in _HI_ (mode restrictions) | 0000 00ss ssst tttt 0000 0000 0001 1111 | | [DIV](https://n64brew.dev/wiki/MIPS_III_instructions#DIV)
       rs, rt | Divide signed rs by signed rt, store quotient in register _LO_ and remainder in _HI_ | 0000 00ss ssst tttt 0000 0000 0001 1010 | | [DIVU](https://n64brew.dev/wiki/MIPS_III_instructions#DIVU)
      rs, rt | Divide unsigned rs by unsigned rt, store quotient in register _LO_ and remainder in _HI_ | 0000 00ss ssst tttt 0000 0000 0001 1011 | | [DMFC0](https://n64brew.dev/wiki/MIPS_III_instructions#DMFC0)
     rt, rd | Copy doubleword contents of CPz coprocessor register rd, to GPR rt | 0100 0000 001t tttt dddd d000 0000 0000 | | [DMTC0](https://n64brew.dev/wiki/MIPS_III_instructions#DMTC0)
     rt, rd | Copy doubleword contents of GPR rt, to CPz coprocessor register rd | 0100 0000 101t tttt dddd d000 0000 0000 | | [DMULT](https://n64brew.dev/wiki/MIPS_III_instructions#DMULT)
     rs, rt | Multiply signed rs with signed rt, store low-order doubleword in _LO_, and high-order doubleword to _HI_ (mode restrictions) | 0000 00ss ssst tttt 0000 0000 0001 1100 | | [DMULTU](https://n64brew.dev/wiki/MIPS_III_instructions#DMULTU)
    rs, rt | Multiply unsigned rs with unsigned rt, store low-order doubleword in _LO_, and high-order doubleword to _HI_ (mode restrictions) | 0000 00ss ssst tttt 0000 0000 0001 1101 | | [DSLL](https://n64brew.dev/wiki/MIPS_III_instructions#DSLL)
      rd, rt, sa | Shift rt left by _sa_ bits, store result in rd (mode restrictions) | 0000 0000 000t tttt dddd dkkk kk11 1000 | | [DSLLV](https://n64brew.dev/wiki/MIPS_III_instructions#DSLLV)
     rd, rt, rs | Shift rt left by rs (limited to 63), store result in rd (mode restrictions) | 0000 00ss ssst tttt dddd d000 0001 0100 | | [DSLL32](https://n64brew.dev/wiki/MIPS_III_instructions#DSLL32)
    rd, rt, rs | Shift rt left by (32 + _sa_) bits, store result in rd (mode restrictions) | 0000 0000 000t tttt dddd dkkk kk11 1100 | | [DSRA](https://n64brew.dev/wiki/MIPS_III_instructions#DSRA)
      rd, rt, sa | Shift rt right by _sa_ bits, store sign-extended result in rd (mode restrictions) | 0000 0000 000t tttt dddd dkkk kk11 1011 | | [DSRAV](https://n64brew.dev/wiki/MIPS_III_instructions#DSRAV)
     rd, rt, rs | Shift rt right by rs (limited to 63), store sign-extended result in rd (mode restrictions) | 0000 00ss ssst tttt dddd d000 0001 0111 | | [DSRA32](https://n64brew.dev/wiki/MIPS_III_instructions#DSRA32)
    rd, rt, sa | Shift rt right by (32 + _sa_) bits, store sign-extended result in rd (mode restrictions) | 0000 0000 000t tttt dddd dkkk kk11 1111 | | [DSRL](https://n64brew.dev/wiki/MIPS_III_instructions#DSRL)
      rd, rt, sa | Shift rt right by _sa_ bits, store sign-extended result in rd (mode restrictions) | 0000 0000 000t tttt dddd dkkk kk11 1010 | | [DSRLV](https://n64brew.dev/wiki/MIPS_III_instructions#DSRLV)
     rd, rt, rs | Shift rt right by rs (limited to 63), store sign-extended result in rd (mode restrictions) | 0000 00ss ssst tttt dddd d000 0001 0110 | | [DSRL32](https://n64brew.dev/wiki/MIPS_III_instructions#DSRL32)
    rd, rt, sa | Shift rt right by (32 + _sa_) bits, store sign-extended result in rd (mode restrictions) | 0000 0000 000t tttt dddd dkkk kk11 1110 | | [DSUB](https://n64brew.dev/wiki/MIPS_III_instructions#DSUB)
      rd, rs, rt | Subtract rt from rs, store result in rd | 0000 00ss ssst tttt dddd d000 0010 1110 | | [DSUBU](https://n64brew.dev/wiki/MIPS_III_instructions#DSUBU)
     rd, rs, rt | Subtract rt from rs, store result in rd | 0000 00ss ssst tttt dddd d000 0010 1111 | | [ERET](https://n64brew.dev/wiki/MIPS_III_instructions#ERET) | Return from interrupt, exception, or error exception | 0100 0010 0000 0000 0000 0000 0001 1000 | | [J](https://n64brew.dev/wiki/MIPS_III_instructions#J)
         target | Jump to _target_ address | 0000 10kk kkkk kkkk kkkk kkkk kkkk kkkk | | [JAL](https://n64brew.dev/wiki/MIPS_III_instructions#JAL)
       target | Jump to _target_ address, stores return address in r31 | 0000 11kk kkkk kkkk kkkk kkkk kkkk kkkk | | [JALR](https://n64brew.dev/wiki/MIPS_III_instructions#JALR)
      rd, rs | Jump to address stored in rs, stores return address in rd | 0000 00ss sss0 0000 dddd d000 0000 1001 | | [JALR](https://n64brew.dev/wiki/MIPS_III_instructions#JALR)
      rs | Jump to address stored in rs, stores return address in r31 | 0000 00ss sss0 0000 1111 1000 0000 1001 | | [JR](https://n64brew.dev/wiki/MIPS_III_instructions#JR)
        rs | Jump to address stored in rs | 0000 00ss sss0 0000 0000 0000 0000 1000 | | [LB](https://n64brew.dev/wiki/MIPS_III_instructions#LB)
        rt, offset(base) | Loads byte stored at memory address (base + _offset_), stores sign-extended byte in rt | 1000 00bb bbbt tttt ffff ffff ffff ffff | | [LBU](https://n64brew.dev/wiki/MIPS_III_instructions#LBU)
       rt, offset(base) | Loads byte stored at memory address (base + _offset_), stores zero-extended byte in rt | 1001 00bb bbbt tttt ffff ffff ffff ffff | | [LD](https://n64brew.dev/wiki/MIPS_III_instructions#LD)
        rt, offset(base) | Loads doubleword stored at memory address (base + _offset_), stores doubleword in rt | 1101 11bb bbbt tttt ffff ffff ffff ffff | | [LDCz](https://n64brew.dev/wiki/MIPS_III_instructions#LDCz)
      rt, offset(base) | Copies doubleword stored at memory address (base + _offset_), to CPz | 1101 xxbb bbbt tttt ffff ffff ffff ffff | | [LDL](https://n64brew.dev/wiki/MIPS_III_instructions#LDL)
       rt, offset(base) | Loads a portion of a doubleword beginning at memory address (base + _offset_), stores 1-8 bytes in high-order portion of rt | 0110 10bb bbbt tttt ffff ffff ffff ffff | | [LDR](https://n64brew.dev/wiki/MIPS_III_instructions#LDR)
       rt, offset(base) | Loads a portion of a doubleword beginning at memory address (base + _offset_), stores 1-8 bytes in low-order portion of rt | 0110 11bb bbbt tttt ffff ffff ffff ffff | | [LH](https://n64brew.dev/wiki/MIPS_III_instructions#LH)
        rt, offset(base) | Loads halfword stored at memory address (base + _offset_), stores sign-extended halfword in rt | 1000 01bb bbbt tttt ffff ffff ffff ffff | | [LHU](https://n64brew.dev/wiki/MIPS_III_instructions#LHU)
       rt, offset(base) | Loads halfword stored at memory address (base + _offset_), stores zero-extended halfword in rt | 1001 01bb bbbt tttt ffff ffff ffff ffff | | [LL](https://n64brew.dev/wiki/MIPS_III_instructions#LL)
        rt, offset(base) | Load Linked (Refer to section below) | 1100 00bb bbbt tttt ffff ffff ffff ffff | | [LLD](https://n64brew.dev/wiki/MIPS_III_instructions#LLD)
       rt, offset(base) | Load Linked Doubleword (Refer to section below) | 1101 00bb bbbt tttt ffff ffff ffff ffff | | [LUI](https://n64brew.dev/wiki/MIPS_III_instructions#LUI)
       rt, immediate | 16-bit _immediate_ is shifted left 16 bits using trailing zeros, result placed in rt | 0011 1100 000t tttt kkkk kkkk kkkk kkkk | | [LW](https://n64brew.dev/wiki/MIPS_III_instructions#LW)
        rt, offset(base) | Loads word stored at memory address (base + _offset_), stores sign-extended word in rt | 1000 11bb bbbt tttt ffff ffff ffff ffff | | [LWCz](https://n64brew.dev/wiki/MIPS_III_instructions#LWCz)
      rt, offset(base) | Copies word stored at memory address (base + _offset_), to CPz | 1100 xxbb bbbt tttt ffff ffff ffff ffff | | [LWL](https://n64brew.dev/wiki/MIPS_III_instructions#LWL)
       rt, offset(base) | Loads a portion of a word beginning at memory address (base + _offset_), stores 1-4 bytes in high-order portion of rt | 1000 10bb bbbt tttt ffff ffff ffff ffff | | [LWR](https://n64brew.dev/wiki/MIPS_III_instructions#LWR)
       rt, offset(base) | Loads a portion of a word beginning at memory address (base + _offset_), stores 1-4 bytes in low-order portion of rt | 1001 10bb bbbt tttt ffff ffff ffff ffff | | [LWU](https://n64brew.dev/wiki/MIPS_III_instructions#LWU)
       rt, offset(base) | Loads word stored at memory address (base + _offset_), stores zero-extended word in rt | 1001 11bb bbbt tttt ffff ffff ffff ffff | | [MFC0](https://n64brew.dev/wiki/MIPS_III_instructions#MFC0)
      rt, rd | Copy contents of CP0's coprocessor register rd, to GPR rt | 0100 0000 000t tttt dddd d000 0000 0000 | | [MFCz](https://n64brew.dev/wiki/MIPS_III_instructions#MFCz)
      rt, rd | Copy contents of CPz's coprocessor register rd, to GPR rt | 0100 xx00 000t tttt dddd d000 0000 0000 | | [MFHI](https://n64brew.dev/wiki/MIPS_III_instructions#MFHI)
      rd | Copy contents of register _HI_ to rd (warning: hazard) | 0000 0000 0000 0000 dddd d000 0001 0000 | | [MFLO](https://n64brew.dev/wiki/MIPS_III_instructions#MFLO)
      rd | Copy contents of register _LO_ to rd (warning: hazard) | 0000 0000 0000 0000 dddd d000 0001 0010 | | [MTC0](https://n64brew.dev/wiki/MIPS_III_instructions#MTC0)
      rt, rd | Copy contents of GPR rt, to CP0's coprocessor register rd | 0100 0000 100t tttt dddd d000 0000 0000 | | [MTCz](https://n64brew.dev/wiki/MIPS_III_instructions#MTCz)
      rd, rd | Copy contents of GPR rt, to CPz's coprocessor register rd | 0100 xx00 100t tttt dddd d000 0000 0000 | | [MTHI](https://n64brew.dev/wiki/MIPS_III_instructions#MTHI)
      rs | Copy contents of rs to register _HI_ | 0000 00ss sss0 0000 0000 0000 0001 0001 | | [MTLO](https://n64brew.dev/wiki/MIPS_III_instructions#MTLO)
      rs | Copy contents of rs to register _LO_ | 0000 00ss sss0 0000 0000 0000 0001 0011 | | [MULT](https://n64brew.dev/wiki/MIPS_III_instructions#MULT)
      rs, rt | Multiply signed rs by signed rt, store low-order word of result in register _LO_ and high-order word in _HI_ | 0000 00ss ssst tttt 0000 0000 0001 1000 | | [MULTU](https://n64brew.dev/wiki/MIPS_III_instructions#MULTU)
     rs, rt | Multiply unsigned rs by unsigned rt, store low-order word of result in register _LO_ and high-order word in _HI_ | 0000 00ss ssst tttt 0000 0000 0001 1001 | | [NOR](https://n64brew.dev/wiki/MIPS_III_instructions#NOR)
       rd, rs, rt | NOR rs and rt, store result in rd | 0000 00ss ssst tttt dddd d000 0010 0111 | | [OR](https://n64brew.dev/wiki/MIPS_III_instructions#OR)
        rd, rs, rt | OR rs and rt, store result in rd | 0000 00ss ssst tttt dddd d000 0010 0101 | | [ORI](https://n64brew.dev/wiki/MIPS_III_instructions#ORI)
       rt, rs, immediate | OR rs and zero-extended _immediate_, store result in rt | 0011 01ss ssst tttt kkkk kkkk kkkk kkkk | | [SB](https://n64brew.dev/wiki/MIPS_III_instructions#SB)
        rt, offset(base) | Stores least-significant byte from rt, to memory address (base + _offset_) | 1010 00bb bbbt tttt ffff ffff ffff ffff | | [SC](https://n64brew.dev/wiki/MIPS_III_instructions#SC)
        rt, offset(base) | If LL bit is set, stores contents of rt, to memory address (base + _offset_) | 1110 00bb bbbt tttt ffff ffff ffff ffff | | [SCD](https://n64brew.dev/wiki/MIPS_III_instructions#SCD)
       rt, offset(base) | If LLD bit is set, stores contents of rt, to memory address (base + _offset_) (mode restrictions) | 1111 00bb bbbt tttt ffff ffff ffff ffff | | [SD](https://n64brew.dev/wiki/MIPS_III_instructions#SD)
        rt, offset(base) | Stores doubleword from rt, to memory address (base + _offset_) | 1111 11bb bbbt tttt ffff ffff ffff ffff | | [SDCz](https://n64brew.dev/wiki/MIPS_III_instructions#SDCz)
      rt, offset(base) | Copies doubleword from CPz, to memory address (base + _offset_) | 1111 xxbb bbbt tttt ffff ffff ffff ffff | | [SDL](https://n64brew.dev/wiki/MIPS_III_instructions#SDL)
       rt, offset(base) | Loads a portion of rt, stores 1-8 bytes in high-order portion of memory address (base + _offset_) | 1011 00bb bbbt tttt ffff ffff ffff ffff | | [SDR](https://n64brew.dev/wiki/MIPS_III_instructions#SDR)
       rt, offset(base) | Loads a portion of rt, stores 1-8 bytes in low-order portion of memory address (base + _offset_) | 1011 01bb bbbt tttt ffff ffff ffff ffff | | [SH](https://n64brew.dev/wiki/MIPS_III_instructions#SH)
        rt, offset(base) | Stores halfword from rt, to memory address (base + _offset_) | 1010 01bb bbbt tttt ffff ffff ffff ffff | | [SLL](https://n64brew.dev/wiki/MIPS_III_instructions#SLL)
       rd, rt, sa | Shift rt left by _sa_ bits, store result in rd | 0000 0000 000t tttt dddd dkkk kk00 0000 | | [SLLV](https://n64brew.dev/wiki/MIPS_III_instructions#SLLV)
      rd, rt, rs | Shift rt left by rs (limited to 31), store result in rd | 0000 00ss ssst tttt dddd d000 0000 0100 | | [SLT](https://n64brew.dev/wiki/MIPS_III_instructions#SLT)
       rd, rs, rt | If signed rs is less than signed rt, store one in rd, otherwise store zero | 0000 00ss ssst tttt dddd d000 0010 1010 | | [SLTI](https://n64brew.dev/wiki/MIPS_III_instructions#SLTI)
      rt, rs, immediate | If signed rs is less than sign-extended _immediate_, store one in rd, otherwise store zero | 0010 10ss ssst tttt kkkk kkkk kkkk kkkk | | [SLTIU](https://n64brew.dev/wiki/MIPS_III_instructions#SLTIU)
     rt, rs, immediate | If unsigned rs is less than sign-extended _immediate_, store one in rt, otherwise store zero | 0010 11ss ssst tttt kkkk kkkk kkkk kkkk | | [SLTU](https://n64brew.dev/wiki/MIPS_III_instructions#SLTU)
      rd, rs, rt | If unsigned rs is less than unsigned rt, store one in rd, otherwise store zero | 0000 00ss ssst tttt dddd d000 0010 1011 | | [SRA](https://n64brew.dev/wiki/MIPS_III_instructions#SRA)
       rd, rt, sa | Shift rt right by _sa_ bits, store sign-extended result in rd | 0000 0000 000t tttt dddd dkkk kk00 0011 | | [SRAV](https://n64brew.dev/wiki/MIPS_III_instructions#SRAV)
      rd, rt, rs | Shift rt right by rs (limited to 31), store sign-extended result in rd | 0000 00ss ssst tttt dddd d000 0000 0111 | | [SRL](https://n64brew.dev/wiki/MIPS_III_instructions#SRL)
       rd, rt, sa | Shift rt right by _sa_ bits, store sign-extended result in rd | 0000 0000 000t tttt dddd dkkk kk00 0010 | | [SRLV](https://n64brew.dev/wiki/MIPS_III_instructions#SRLV)
      rd, rt, rs | Shift rt right by rs (limited to 31), store sign-extended result in rd | 0000 00ss ssst tttt dddd d000 0000 0110 | | [SUB](https://n64brew.dev/wiki/MIPS_III_instructions#SUB)
       rd, rs, rt | Subtract rt from rs, store result in rd | 0000 00ss ssst tttt dddd d000 0010 0010 | | [SUBU](https://n64brew.dev/wiki/MIPS_III_instructions#SUBU)
      rd, rs, rt | Subtract rt from rs, store result in rd | 0000 00ss ssst tttt dddd d000 0010 0011 | | [SW](https://n64brew.dev/wiki/MIPS_III_instructions#SW)
        rt, offset(base) | Stores word from rt, to memory address (base + _offset_) | 1010 11bb bbbt tttt ffff ffff ffff ffff | | [SWCz](https://n64brew.dev/wiki/MIPS_III_instructions#SWCz)
      rt, offset(base) | Copies word from CPz, to memory address (base + _offset_) | 1110 xxbb bbbt tttt ffff ffff ffff ffff | | [SWL](https://n64brew.dev/wiki/MIPS_III_instructions#SWL)
       rt, offset(base) | Loads a portion of rt, stores 1-4 bytes in high-order portion of memory address (base + _offset_) | 1010 10bb bbbt tttt ffff ffff ffff ffff | | [SWR](https://n64brew.dev/wiki/MIPS_III_instructions#SWR)
       rt, offset(base) | Loads a portion of rt, stores 1-4 bytes in low-order portion of memory address (base + _offset_) | 1011 10bb bbbt tttt ffff ffff ffff ffff | | [SYNC](https://n64brew.dev/wiki/MIPS_III_instructions#SYNC) | Executed as NOP on the VR4300 | 0000 0000 0000 0000 0000 0000 0000 1111 | | [SYSCALL](https://n64brew.dev/wiki/MIPS_III_instructions#SYSCALL) | Causes system call exception | 0000 00kk kkkk kkkk kkkk kkkk kk00 1100 | | [TEQ](https://n64brew.dev/wiki/MIPS_III_instructions#TEQ)
       rs, rt | If rs equals rt, cause a trap exception | 0000 00ss ssst tttt kkkk kkkk kk11 0100 | | [TEQI](https://n64brew.dev/wiki/MIPS_III_instructions#TEQI)
      rs, immediate | If rs equals sign-extended _immediate_, cause a trap exception | 0000 01ss sss0 1100 kkkk kkkk kkkk kkkk | | [TGE](https://n64brew.dev/wiki/MIPS_III_instructions#TGE)
       rs, rt | If signed rs is greater than or equal to signed rt, cause a trap exception | 0000 00ss ssst tttt kkkk kkkk kk11 0000 | | [TGEI](https://n64brew.dev/wiki/MIPS_III_instructions#TGEI)
      rs, immediate | If signed rs is greater than or equal to sign-extended _immediate_, cause a trap exception | 0000 01ss sss0 1000 kkkk kkkk kkkk kkkk | | [TGEIU](https://n64brew.dev/wiki/MIPS_III_instructions#TGEIU)
     rs, immediate | If unsigned rs is greater than or equal to sign-extended _immediate_, cause a trap exception | 0000 01ss sss0 1001 kkkk kkkk kkkk kkkk | | [TGEU](https://n64brew.dev/wiki/MIPS_III_instructions#TGEU)
      rs, rt | If unsigned rs is greater than or equals to unsigned rt, cause a trap exception | 0000 00ss ssst tttt kkkk kkkk kk11 0001 | | [TLBP](https://n64brew.dev/wiki/MIPS_III_instructions#TLBP) | Searches for a TLB entry that matches the EntryHi register | 0100 0010 0000 0000 0000 0000 0000 1000 | | [TLBR](https://n64brew.dev/wiki/MIPS_III_instructions#TLBR) | Loads EntryHi and EntryLo registers with the TLB entry pointed at by the Index register | 0100 0010 0000 0000 0000 0000 0000 0001 | | [TLBWI](https://n64brew.dev/wiki/MIPS_III_instructions#TLBWI) | Stores the contents of EntryHi and EntryLo registers into the TLB entry pointed at by the Index register | 0100 0010 0000 0000 0000 0000 0000 0010 | | [TLBWR](https://n64brew.dev/wiki/MIPS_III_instructions#TLBWR) | Stores the contents of EntryHi and EntryLo registers into the TLB entry pointed at by the Random register | 0100 0010 0000 0000 0000 0000 0000 0110 | | [TLT](https://n64brew.dev/wiki/MIPS_III_instructions#TLT)
       rs, rt | If signed rs is less than signed rt, cause a trap exception | 0000 00ss ssst tttt kkkk kkkk kk11 0010 | | [TLTI](https://n64brew.dev/wiki/MIPS_III_instructions#TLTI)
      rs, immediate | If signed rs is less than sign-extended _immediate_, cause a trap exception | 0000 01ss sss0 1010 kkkk kkkk kkkk kkkk | | [TLTIU](https://n64brew.dev/wiki/MIPS_III_instructions#TLTIU)
     rs, immediate | If unsigned rs is less than sign-extended _immediate_, cause a trap exception | 0000 01ss sss0 1011 kkkk kkkk kkkk kkkk | | [TLTU](https://n64brew.dev/wiki/MIPS_III_instructions#TLTU)
      rs, rt | If unsigned rs is less than unsigned rt, cause a trap exception | 0000 00ss ssst tttt kkkk kkkk kk11 0011 | | [TNE](https://n64brew.dev/wiki/MIPS_III_instructions#TNE)
       rs, rt | If rs does not equal rt, cause a trap exception | 0000 00ss ssst tttt kkkk kkkk kk11 0110 | | [TNEI](https://n64brew.dev/wiki/MIPS_III_instructions#TNEI)
      rs, immediate | If rs does not equal sign-extended _immediate_, cause a trap exception | 0000 01ss sss0 1110 kkkk kkkk kkkk kkkk | | [XOR](https://n64brew.dev/wiki/MIPS_III_instructions#XOR)
       rd, rs, rt | XOR rs with rt, store result in rd | 0000 00ss ssst tttt dddd d000 0010 0110 | | [XORI](https://n64brew.dev/wiki/MIPS_III_instructions#XORI)
      rt, rs, immediate | XOR rs with zero-extended _immediate_, store result in rd | 0011 10ss ssst tttt kkkk kkkk kkkk kkkk | Hazards ------- The `mfhi` and `mflo` instructions will produce incorrect results if any of the two following instructions modify the `HI` and `LO` registers, respectively. Note that by default, assemblers other than Bass will prevent this by reordering instructions or inserting `nop`. If you are using `.set noreorder` or Bass, you must ensure this yourself. ADD --- **Format:** ADD rd, rs, rt Description: The contents of general purpose register rs and the contents of general purpose register rt are added to store the result in general purpose register rd. In 64-bit mode, the operands must be sign-extended, 32-bit values. An integer overflow exception occurs if the carries out of bits 30 and 31 differ (2’s complement overflow). The contents of destination register rd is not modified when an integer overflow exception occurs. Exceptions: Integer overflow exception ADDI ---- **Format:** ADDI rt, rs, immediate Description: The 16-bit immediate is sign-extended and added to the contents of general purpose register rs to store the result in general purpose register rt. In 64-bit mode, the operand must be sign-extended, 32-bit values. An integer overflow exception occurs if carries out of bits 30 and 31 differ (2’s complement overflow). The contents of destination register rt is not modified when an integer overflow exception occurs. Exceptions: Integer overflow exception _TODO_ FPU Instruction Set =================== The floating point unit (FPU) is contained within the VR3400 microprocessor and handles floating point operations. The FPU uses a different set of registers for its operations (f0 - f31). While these registers are separate from general purpose registers, they function the same way. | Symbol | Description | | --- | --- | | fd | Destination register | | fs | Source register | | ft | Temporary register | | fmt | Operand format | | FPR | Floating-point Register | Many FPU instructions require an operand format `fmt`. This format dictates the size of the operators and whether they are floating-point or fixed-point, as noted in this table: | Symbol | Description | | --- | --- | | S | 32 bits, floating-point | | D | 64 bits, floating-point | | W | 32 bits, fixed-point | | L | 64 bits, fixed-point | Some instructions will also require a conditional `cond`. This determines what kind of comparison is performed. | Symbol | Description | | --- | --- | | \- | **table incomplete** | | F | T | | UN | OR | | EQ | NEQ | | UEQ | OGL | | OLT | UGE | | ULT | OGE | | OLE | UGT | | ULE | OGT | | SF | ST | | NGLE | GLE | | SEQ | SNE | | NGL | GL | | LT | NLT | | NGE | GE | | LE | NLE | | NGT | GT | **Instruction Word Key** | Symbol | Description | | --- | --- | | s | source register number | | d | destination register number | | t | temporary register number | | k | literal/immediate value | | b | base address | | f | offset address | | x | coprocessor number | | a | fmt - operand format | | c | cond - conditional | | Mnemonic | Description | 32-bit Instruction Word | | --- | --- | --- | | [ABS.fmt](https://n64brew.dev/wiki/MIPS_III_instructions#ABS.fmt)
   fd, fs | Absolute value of fs, store result in fd | 0100 01aa aaa0 0000 ssss sddd dd00 0101 | | [ADD.fmt](https://n64brew.dev/wiki/MIPS_III_instructions#ADD.fmt)
   fd, fs | Add fs and ft, store result in fd | 0100 01aa aaat tttt ssss sddd dd00 0000 | | [C.cond.fmtfs](https://n64brew.dev/wiki/MIPS_III_instructions#C.cond.fmt)
, ft | Compares fs and ft using cond | 0100 01aa aaat tttt ssss s000 0011 cccc | | [BC1F](https://n64brew.dev/wiki/MIPS_III_instructions#BC1F)
      offset | | 0100 0101 0000 0000 ffff ffff ffff ffff | | [BC1FL](https://n64brew.dev/wiki/MIPS_III_instructions#BC1FL)
     offset | | 0100 0101 0000 0010 ffff ffff ffff ffff | | [BC1T](https://n64brew.dev/wiki/MIPS_III_instructions#BC1T)
      offset | | 0100 0101 0000 0001 ffff ffff ffff ffff | | [BC1TL](https://n64brew.dev/wiki/MIPS_III_instructions#BC1TL)
     offset | | 0100 0101 0000 0011 ffff ffff ffff ffff | _TODO_ Pseudo-Instructions =================== Many assemblers, regardless of architecture, will add additional instructions to assist developers. This table shows the instructions added by the [bass assembler](https://github.com/ARM9/bass) , which is commonly used to develop N64 ROMs. Retrieved from "[https://n64brew.dev/wiki/MIPS\_III\_instructions?oldid=4574](https://n64brew.dev/wiki/MIPS_III_instructions?oldid=4574) " --- # MIPS Interface - N64brew Wiki [](https://n64brew.dev/wiki/MIPS_Interface#) MIPS Interface ============== The MIPS Interface (or **MI**) is one of multiple I/O interfaces in the RCP. It is the interface between the RCP and the VR4300 CPU, primarily used for enabling/disabling interrupts and checking their status. Memory mapped registers are used to configure the MIPS Interface. The base address for these registers is `0x0430 0000`, also known as MI\_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 `0xA000 0000` to the address. As an example, to directly write to the MI\_MODE register, use address `0xA430 0000`. Note that some of these registers have different behavior when writing to them, than when reading from them. When writing to a register that has Set and Clear bits, write a 1 on the desired bit. Writing 0's have no effect. When writing 1's to both Set and Clear bits in a pair at the same time results in no effect, the previous state is preserved. Accesses beyond `0x0430 0010` are mirrored, so only the least significant four bits are taken into account for address decoding. (Note that this isn't the case on the iQue Player.) Contents -------- * [1 Registers](https://n64brew.dev/wiki/MIPS_Interface#Registers) * [1.1 0x0430 0000 - MI\_MODE](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0000_-_MI_MODE) * [1.2 0x0430 0004 - MI\_VERSION](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0004_-_MI_VERSION) * [1.3 0x0430 0008 - MI\_INTERRUPT](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0008_-_MI_INTERRUPT) * [1.4 0x0430 000C - MI\_MASK](https://n64brew.dev/wiki/MIPS_Interface#0x0430_000C_-_MI_MASK) * [2 iQue Player-specific registers](https://n64brew.dev/wiki/MIPS_Interface#iQue_Player-specific_registers) * [2.1 0x0430 0014 - MI\_BB\_SECURE\_EXCEPTION](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0014_-_MI_BB_SECURE_EXCEPTION) * [2.2 0x0430 002C - MI\_BB\_RANDOM](https://n64brew.dev/wiki/MIPS_Interface#0x0430_002C_-_MI_BB_RANDOM) * [2.3 0x0430 0038 - MI\_BB\_INTERRUPT](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0038_-_MI_BB_INTERRUPT) * [2.4 0x0430 003C - MI\_BB\_MASK](https://n64brew.dev/wiki/MIPS_Interface#0x0430_003C_-_MI_BB_MASK) 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 #### 0x0430 0000 - MI\_MODE * * * **When Reading:** | MI\_MODE `0x0430 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | W-0 | W-0 | W-0 | W-0 | R-0 | R-0 | | — | — | — | — | — | — | Upper | Ebus | | 7:0 | R-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | Repeat | RepeatCount\[6:0\] | | | | | | | | | | | --- | --- | | bit 9 | **Upper:** Upper mode enabled. | | bit 8 | **EBus:** EBus mode enabled. | | bit 7 | **Repeat:** Repeat mode enabled, Automatically clears after a single Rambus write. | | bit 6-0 | **RepeatCount\[6:0\]:** Number of bytes (minus 1) to write in repeat mode | **When Writing:** | MI\_MODE `0x0430 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | W-0 | W-0 | W-0 | W-0 | RW-0 | RW-0 | | — | — | Set Upper | Clear Upper | ClearDP | Set Ebus | Clear Ebus | Set Repeat | | 7:0 | W-0 | RW-0 | RW-0 | RW-0 | RW-0 | U-0 | RW-0 | RW-0 | | Clear Repeat | RepeatCount\[6:0\] | | | | | | | | | | | --- | --- | | bit 13 | **SetUpper:** Set Upper mode. | | bit 12 | **ClearUpper:** Clear Upper mode. | | bit 11 | **ClearDP:** Clear the DP Interrupt. | | bit 10 | **SetEBus:** Set Ebus mode. | | bit 9 | **ClearEBus:** Clear Ebus mode. | | bit 8 | **SetRepeat:** Set repeat mode. Automatically clears after a single Rambus write. | | bit 7 | **ClearRepeat:** Clear repeat mode. | | bit 6-0 | **RepeatCount\[6:0\]:** Number of bytes (minus 1) to write in repeat mode | **MI's Mode:** The mode controls how reads and writes coming from the CPU are forwarded into RDRAM (technically, how 32bit SysAD bus transfers are mapped onto DBus and EBus, which are the internal RCP buses that transfer data from and to RDRAM). RI only uses 64bit aligned reads/writes when accessing RDRAM. For operations smaller than 64bits, the data needs to be shifted into the correct bytes of DBUS. When writing, the Rambus device will use the lower 3 bits of the address and byte count as a byte mask. When reading, all 64bits are returned and the receiving device will need to implement its own byte masking. The VR4300's pipeline already shifts smaller loads/stores into the correct part of the 64bit double word and implements byte masking for loads, but because 8bit/16bit/32bit operations only result in 32bits of data being transferred across SysAD bus, MI still needs to shift each 32bit word into the correct half of DBus. These modes impact all access to the RDRAM address range, not just Rambus registers, but actual memory too. Reads and Writes to other RCP registers and MMIO (like SI, PI or RSP DMEM/IMEM) are unaffected, as that data goes over CBus. **Normal mode:** When all special mode bits are disabled, MI maps words onto the DBus as expected. 32bit words are shifted into the upper or low half of the 64bits depending on Addr\[2\] and any "critical word first" rules. EBus appears to default to the sign extension of each byte (further testing needed) **Upper mode:** 32bit transfers are always shifted into the upper half of the 64bit bus. This mode is labeled as **"RDRAM register mode"** in some documentation and is useful for accessing registers on Rambus devices. The Rambus Rreg, Wreg, and WregB commands are hardcoded to ignore the count field of request packets and always do a 32bit transfers. When misinterpreting the RI's 8 byte transfer, the Rambus device always takes the first 4 bytes (which are the upper 32bits of DBus, because RCP is big endian) and ignores the next 4 bytes. Normal mode should produce correct results for registers at even offsets, but you need switch MI into Upper mode to correctly access odd registers. **EBus mode:** The lower 4 bits of the 32bit word are mapped onto 4 bits of EBus. In typical operation, EBus is used by RDP and VI to access the extra 9th bit (aka parity/error bit) that RDRAM provides for each byte. This mode allows the CPU to read this extra information back. Unfortunately this mode doesn't appear to be useful for writing to Antialiased framebuffers, as you can't combine a normal mode write and a EBus mode write without overwriting each other (Future testing required, maybe 64bit transfers work?) **Repeat Mode:** Writes cause a repeating pattern of **RepeatCount+1** bytes (upto 128 bytes) to be written. Reading can cause a hang (further testing needed). First a 64bit value is loaded into the DBus FIFO to be the pattern: | | | | --- | --- | | Uncached 64bit write | The 64bit value is used directly | | `reg64=0x01234567_89abcdef -> pattern=0x012345678_9abcdef` | | Uncached 32bit write | Only the lower 32bits are send over SYSAD, and that is duplicated into both upper and lower halves | | `reg64=0x01234567_89abcdef -> pattern=0x89abcdef_89abcdef` | | Uncached 16bit write | When writing less than 32bits, the cpu pre-shifts the value and MI sees it as a 32bit write. Normal byte masking is suppressed as it was replaced by RepeatCount. | | `reg64=0x01234567_89abcdef, addr[1:0]=0b10 -> pattern=0xcdef0000_cdef0000` | | Uncached 8bit write | _As Above._ Notice that some bytes are now unmasked. | | `reg64=0x01234567_89abcdef, addr[2:0]=0b001 -> pattern=0xabcdef00_abcdef00` | | Cache writeback | The last 8 bytes of the cacheline are used, everything else is discarded. | | `cacheline="an ascii example" -> pattern=" example example"` | Then MI lies to RI about the transfer count, with **RepeatCount** overriding the correct value. causing RI to do `RepeatCount[6:3]` transfers, with MI repeating the same 64bit value for all transfers. The Rambus device uses **Addr\[2:0\]** and **RepeatCount\[2:0\]** to implement byte masking for the first and last transfers. RI does not correctly implement support for unaligned writes that cross a 64bit boundary, so you end up with `RepeatCount[6:3] | RepeatCount[2:0] + Addr[2:0]` written, which discards the potential carry out from bit 2 to bit 3. This mode is labeled as **"Init Mode"** in some documentation. It's used once during Nintendo's IPL3's RDRAM initialization as a workaround for the default timings after RDRAM reset not being compatible with RI's hardcoded timings. See [RDRAM Delay register](https://n64brew.dev/wiki/RDRAM#0x02_-_Delay "RDRAM") for a detailed explanation. Repeat Mode is ideal for fast Memset operations, as you can quickly write a repeating pattern to upto 128 bytes of RAM with just two 32bit uncached writes. For cache-coherency, you do need to invalidate those cache lines, but you can execute the CACHE instructions while MI is finishing the Repeat write with almost no impact to performance. MI repeat is almost as fast as using RSP's DMA, but it will often be faster and more convenient to use MI repeat as the RSP is busy doing other things. | | | | --- | --- |Memset benchmarks for 1MiB of data. | 64bit uncached writes | 25.7 ms | | 64bit cached writes | 49.8 ms | | RSP DMA | 2.58 ms | | MI repeat | 3.80 ms (plus 0.20ms with inline CACHE instructions) | If you are careful, it should be possible to do unaligned memsets too. An uncached byte write can be used to set **Addr\[2:0\]** to any value, and you can adjust **RepeatCount\[6:3\]** to compensate for the missing carry out, so the Rambus device writes the correct number of bytes. #### 0x0430 0004 - MI\_VERSION * * * | MI\_VERSION `0x0430 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-? | R-? | | RSP\_VERSION\[7:0\] | | | | | | | | | 23:16 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-? | R-? | | RDP\_VERSION\[7:0\] | | | | | | | | | 15:8 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-? | R-? | | RAC\_VERSION\[7:0\] | | | | | | | | | 7:0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-? | R-? | | IO\_VERSION\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **RSP\_VERSION\[7:0\]:** RSP hardware version | | bit 23-16 | **RDP\_VERSION\[7:0\]:** RDP hardware version | | bit 15-8 | **RAC\_VERSION\[7:0\]:** RAC hardware version | | bit 7-0 | **IO\_VERSION\[7:0\]:** IO hardware version | **Extra Details:** It is not known for certain the full extent of values that can exist here. Most consoles report `0x0202_0102`, though emulators and other docs seem to mention other similar values such as `0x0101_0101` or `0x0201_0202`. iQue retail consoles and early prototypes report `0x0202_b0b0`. Analogue3D reports `0x0303_0303`. Testing should be performed on all revisions of the N64 motherboard and development systems. Results will be listed here. #### 0x0430 0008 - MI\_INTERRUPT * * * | MI\_INTERRUPT `0x0430 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | — | — | DP | PI | VI | AI | SI | SP | | | | | --- | --- | | bit 31-6 | **Undefined:** Initialized to `0` | | bit 5 | **DP:** Interrupt flag - Set when the RDP finishes a full sync (requested explicitly via a SYNC\_FULL command) | | bit 4 | **PI:** Interrupt flag - Set when a PI DMA transfer finishes | | bit 3 | **VI:** Interrupt flag - Set when the VI starts processing a specific half-line of the screen (`VI_V_CURRENT == VI_V_INTR`). Usually, this is configured with `VI_V_CURRENT = 2` so that it behaves as a VBlank interrupt. | | bit 2 | **AI:** Interrupt flag - Set when the AI begins playing back a new audio buffer (to notify that the next one should be enqueued as soon as possible, to avoid crackings) | | bit 1 | **SI:** Interrupt flag - Set when a SI DMA to/from PIF RAM finishes | | bit 0 | **SP:** Interrupt flag - Set when the RSP executes a `BREAK` opcode while `SP_STATUS` has been configured with the `INTERRUPT_ON_BREAK` bit; alternatively, it can also be set by explicitly writing the `INTERRUPT` flag in the `SP_STATUS` register (by either the CPU or the RSP itself). | #### 0x0430 000C - MI\_MASK * * * | MI\_MASK `0x0430 000C` (Read) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | — | — | DP | PI | VI | AI | SI | SP | | | | | --- | --- | | bit 5 | **DP:** DP Interrupt Mask | | bit 4 | **PI:** PI Interrupt Mask | | bit 3 | **VI:** VI Interrupt Mask | | bit 2 | **AI:** AI Interrupt Mask | | bit 1 | **SI:** SI Interrupt Mask | | bit 0 | **SP:** SP Interrupt Mask | | MI\_MASK `0x0430 000C` (Write) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | W-0 | W-0 | W-0 | W-0 | | — | — | — | — | Set DP | Clear DP | Set PI | Clear PI | | 7:0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | Set VI | Clear VI | Set AI | Clear AI | Set SI | Clear SI | Set SP | Clear SP | | | | | --- | --- | | bit 11 | **Set DP:** Set DP Interrupt Mask | | bit 10 | **Clear DP:** Clear DP Interrupt Mask | | bit 9 | **Set PI:** Set PI Interrupt Mask | | bit 8 | **Clear PI:** Clear PI Interrupt Mask | | bit 7 | **Set VI:** Set VI Interrupt Mask | | bit 6 | **Clear VI:** Clear VI Interrupt Mask | | bit 5 | **Set AI:** Set AI Interrupt Mask | | bit 4 | **Clear AI:** Clear AI Interrupt Mask | | bit 3 | **Set SI:** Set SI Interrupt Mask | | bit 2 | **Clear SI:** Clear SI Interrupt Mask | | bit 1 | **Set SP:** Set SP Interrupt Mask | | bit 0 | **Clear SP:** Clear SP Interrupt Mask | **Extra Details:** Notice that disabling an interrupt does not prevent the interrupt to be raised in MI\_INTERRUPT. It just prevents the interrupt to be signaled to the CPU. If a mask is enabled while the respective interrupt is already set in MI\_INTERRUPT, the interrupt is signaled to the CPU right away. iQue Player-specific 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 #### 0x0430 0014 - MI\_BB\_SECURE\_EXCEPTION * * * | MI\_BB\_SECURE\_EXCEPTION `0x0430 0014` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | W-? | W-? | | — | — | — | — | — | — | — | SK RAM Access | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-1 | RW-1 | | Module | Button | MI error | PI error | Timer | Application | Boot memory swap | Secure mode | | | | | --- | --- | | bit 25 | **Unknown:** System software writes to this bit when launching an app or game. (TODO determine the purpose of this bit) | | bit 24 | **SK RAM Access:** Setting this bit to 1 enables access to an 0x8000-byte SRAM mapped at 0x1FC40000 outside of secure mode. | | bit 7 | **Module:** Secure trap caused by the memory card being removed. (TOVERIFY) | | bit 6 | **Button:** Secure trap caused by the power button being pressed. (TOVERIFY: theory is that this cause bit is set when a secure trap is signaled 0.5s after the power button was pressed) | | bit 5 | **MI error:** Secure trap caused by an MI error. | | bit 4 | **PI error:** Secure trap caused by a PI error. | | bit 3 | **Timer:** Secure trap caused by the secure timer. | | bit 2 | **Application:** Secure trap caused by an application reading this register outside of secure mode. | | bit 1 | **Boot memory swap:** If this is set to 1 the boot ROM is mapped to 0x1FC00000 and Secure Kernel RAM is mapped at 0x1FC20000; this is the state at cold boot. If this is not set the Secure Kernel RAM is mapped at 0x1FC00000 and the boot ROM is mapped at 0x1FC20000. | | bit 0 | **Secure mode:** Secure Kernel writes 0 here to exit secure mode. This is effective immediately, all future memory accesses that miss the CPU cache will be unable to access secure mode resources. Whenever this register is written to without the intention to exit secure mode, a 1 must be re-written here. | **Extra Details:** Secure traps are implemented as an NMI to the CPU and are vectored to 0xBFC00000, this register is then used by the Secure Kernel as a Cause register to determine the reason for the secure trap and the relevant handler is entered. Reading (and possibly writing, TOVERIFY) this register from non-secure mode (i.e. from a game or application) causes a secure trap with the Application bit set in this register. This mechanism is how SKCs ([Secure Kernel Calls](https://n64brew.dev/wiki/Secure_Kernel_Calls "Secure Kernel Calls") ) are implemented; the application sets up the required CPU registers for the call before reading this register to trigger the Application trap. #### 0x0430 002C - MI\_BB\_RANDOM * * * | MI\_BB\_RANDOM `0x0430 002C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | R-? | | — | — | — | — | — | — | — | Random | | | | | --- | --- | | bit 0 | **Random:** Single bit hardware-generated random value. Accumulating bits from this register can produce larger random numbers that are (theoretically) suitable for cryptographic purposes. The Secure Kernel uses this for gathering entropy as part of generating new cryptographic keys. | #### 0x0430 0038 - MI\_BB\_INTERRUPT * * * | MI\_BB\_INTERRUPT `0x0430 0038` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | R-0 | R-0 | | — | — | — | — | — | — | MD\_STATE | BTN\_STATE | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | RW-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | — | — | MD | BTN | USB1 | USB0 | PI\_ERR | IDE | | 7:0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | AES | FLASH | DP | PI | VI | AI | SI | SP | | | | | --- | --- | | bit 25 | **MD\_STATE:** If 0, indicates that the memory card is present. If 1, indicates that the memory card is currently removed. | | bit 24 | **BTN\_STATE:** If 0, indicates that the power button is not currently held. If 1, indicates that the power button is currently held. | | bit 13 | **MD:** Interrupt flag - Set when the memory card is removed from the console. Writing to this bit clears the interrupt. | | bit 12 | **BTN:** Interrupt flag - Set when the power button is pressed. This flag is not typically useful as this triggers an Int2 interrupt rather than Int1 like the other Que-specific interrupts. | | bit 11 | **USB1:** Interrupt flag - Set by USB events on connector 1 (TOVERIFY what kind of events?) | | bit 10 | **USB0:** Interrupt flag - Set by USB events on connector 0 (TOVERIFY what kind of events?) | | bit 9 | **PI\_ERR:** Interrupt flag - Set on "PI Errors"? (TOVERIFY what sort of errors?) | | bit 8 | **IDE:** Interrupt flag - IDE (TODO What is IDE? Only the name is known from some library debug info) | | bit 7 | **AES:** Interrupt flag - Set when an AES decryption operation completes | | bit 6 | **FLASH:** Interrupt flag - Set when a NAND command completes | | bit 5 | **DP:** Interrupt flag - Same as N64 (see MI\_INTERRUPT) | | bit 4 | **PI:** Interrupt flag - Same as N64 (see MI\_INTERRUPT) | | bit 3 | **VI:** Interrupt flag - Same as N64 (see MI\_INTERRUPT) | | bit 2 | **AI:** Interrupt flag - Same as N64 (see MI\_INTERRUPT) | | bit 1 | **SI:** Interrupt flag - Same as N64 (see MI\_INTERRUPT) | | bit 0 | **SP:** Interrupt flag - Same as N64 (see MI\_INTERRUPT) | **Extra Details:** The additional iQue-specific interrupts in bits 6-11 and bit 13 raise Int1 in the COP0 Cause register rather than Int0 like the N64's RCP interrupts. On N64 Int1 was attached to the Cartridge and Disk Drive ports. Bit 12 (power button) raises Int2/PreNMI, followed by a secure trap 0.5s later. #### 0x0430 003C - MI\_BB\_MASK * * * | MI\_BB\_MASK `0x0430 003C` (Read) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | — | — | MD | BTN | USB1 | USB0 | PI\_ERR | IDE | | 7:0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | AES | FLASH | DP | PI | VI | AI | SI | SP | | | | | --- | --- | | bit 13 | **MD:** MD Interrupt Mask | | bit 12 | **BTN:** BTN Interrupt Mask | | bit 11 | **USB1:** USB1 Interrupt Mask | | bit 10 | **USB0:** USB0 Interrupt Mask | | bit 9 | **PI\_ERR:** PI\_ERR Interrupt Mask | | bit 8 | **IDE:** IDE Interrupt Mask | | bit 7 | **AES:** AES Interrupt Mask | | bit 6 | **FLASH:** FLASH Interrupt Mask | | bit 5 | **DP:** DP Interrupt Mask | | bit 4 | **PI:** PI Interrupt Mask | | bit 3 | **VI:** VI Interrupt Mask | | bit 2 | **AI:** AI Interrupt Mask | | bit 1 | **SI:** SI Interrupt Mask | | bit 0 | **SP:** SP Interrupt Mask | | MI\_BB\_MASK `0x0430 003C` (Write) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | W-0 | W-0 | W-0 | W-0 | | — | — | — | — | Set MD | Clear MD | Set BTN | Clear BTN | | 23:16 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | Set USB1 | Clear USB1 | Set USB0 | Clear USB0 | Set PI\_ERR | Clear PI\_ERR | Set IDE | Clear IDE | | 15:8 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | Set AES | Clear AES | Set FLASH | Clear FLASH | Set DP | Clear DP | Set PI | Clear PI | | 7:0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | Set VI | Clear VI | Set AI | Clear AI | Set SI | Clear SI | Set SP | Clear SP | | | | | --- | --- | | bit 27 | **Set MD:** Set MD Interrupt Mask | | bit 26 | **Clear MD:** Clear MD Interrupt Mask | | bit 25 | **Set BTN:** Set BTN Interrupt Mask | | bit 24 | **Clear BTN:** Clear BTN Interrupt Mask | | bit 23 | **Set USB1:** Set USB1 Interrupt Mask | | bit 22 | **Clear USB1:** Clear USB1 Interrupt Mask | | bit 21 | **Set USB0:** Set USB0 Interrupt Mask | | bit 20 | **Clear USB0:** Clear USB0 Interrupt Mask | | bit 19 | **Set PI\_ERR:** Set PI\_ERR Interrupt Mask | | bit 18 | **Clear PI\_ERR:** Clear PI\_ERR Interrupt Mask | | bit 17 | **Set IDE:** Set IDE Interrupt Mask | | bit 16 | **Clear IDE:** Clear IDE Interrupt Mask | | bit 15 | **Set AES:** Set AES Interrupt Mask | | bit 14 | **Clear AES:** Clear AES Interrupt Mask | | bit 13 | **Set FLASH:** Set FLASH Interrupt Mask | | bit 12 | **Clear FLASH:** Clear FLASH Interrupt Mask | | bit 11 | **Set DP:** Set DP Interrupt Mask | | bit 10 | **Clear DP:** Clear DP Interrupt Mask | | bit 9 | **Set PI:** Set PI Interrupt Mask | | bit 8 | **Clear PI:** Clear PI Interrupt Mask | | bit 7 | **Set VI:** Set VI Interrupt Mask | | bit 6 | **Clear VI:** Clear VI Interrupt Mask | | bit 5 | **Set AI:** Set AI Interrupt Mask | | bit 4 | **Clear AI:** Clear AI Interrupt Mask | | bit 3 | **Set SI:** Set SI Interrupt Mask | | bit 2 | **Clear SI:** Clear SI Interrupt Mask | | bit 1 | **Set SP:** Set SP Interrupt Mask | | bit 0 | **Clear SP:** Clear SP Interrupt Mask | **Extra Details:** Notice that disabling an interrupt does not prevent the interrupt to be raised in MI\_BB\_INTERRUPT. It just prevents the interrupt to be signaled to the CPU. If a mask is enabled while the respective interrupt is already set in MI\_BB\_INTERRUPT, the interrupt is signaled to the CPU right away. The interrupts shared with N64 are mirrors of the MI\_MASK state. Updating one register updates the other automatically. Note that if the BTN interrupt is disabled the NMI triggered by the button is also disabled, rather than just the Int2/PreNMI interrupt. Retrieved from "[https://n64brew.dev/wiki/MIPS\_Interface?oldid=5753](https://n64brew.dev/wiki/MIPS_Interface?oldid=5753) " --- # Parallel Interface - N64brew Wiki [](https://n64brew.dev/wiki/Parallel_Interface#) Parallel Interface ================== The Parallel Interface (commonly referred to as the **PI**, or Peripheral Interface) is one of multiple I/O interfaces in the RCP, which is used to communicate with [game cartridges](https://n64brew.dev/wiki/Game_Pak "Game Pak") or other devices connected to either the cartridge port or expansion port on the bottom of the console. (e.g. 64DD) The PI is not to be confused with the [PIF](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") (or PIF-NUS) which is an entirely separate IC. Memory mapped registers are used to configure the Parallel Interface and initiate DMA reads and writes. The base address for these registers is `0x0460 0000`, 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 `0xA000 0000` to the address. As an example, to directly write to the PI\_DRAM\_ADDR register, use address `0xA460 0000`. Contents -------- * [1 The PI Bus](https://n64brew.dev/wiki/Parallel_Interface#The_PI_Bus) * [1.1 Domains](https://n64brew.dev/wiki/Parallel_Interface#Domains) * [1.2 Open bus behavior](https://n64brew.dev/wiki/Parallel_Interface#Open_bus_behavior) * [2 Registers](https://n64brew.dev/wiki/Parallel_Interface#Registers) * [2.1 0x0460 0000 - PI\_DRAM\_ADDR](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0000_-_PI_DRAM_ADDR) * [2.2 0x0460 0004 - PI\_CART\_ADDR](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0004_-_PI_CART_ADDR) * [2.3 0x0460 0008 - PI\_RD\_LEN](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0008_-_PI_RD_LEN) * [2.4 0x0460 000C - PI\_WR\_LEN](https://n64brew.dev/wiki/Parallel_Interface#0x0460_000C_-_PI_WR_LEN) * [2.5 0x0460 0010 - PI\_STATUS](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0010_-_PI_STATUS) * [2.6 0x0460 00n4 - PI\_BSD\_DOMn\_LAT](https://n64brew.dev/wiki/Parallel_Interface#0x0460_00n4_-_PI_BSD_DOMn_LAT) * [2.7 0x0460 00n8 - PI\_BSD\_DOMn\_PWD](https://n64brew.dev/wiki/Parallel_Interface#0x0460_00n8_-_PI_BSD_DOMn_PWD) * [2.8 0x0460 00nC - PI\_BSD\_DOMn\_PGS](https://n64brew.dev/wiki/Parallel_Interface#0x0460_00nC_-_PI_BSD_DOMn_PGS) * [2.9 0x0460 00n0 - PI\_BSD\_DOMn\_RLS](https://n64brew.dev/wiki/Parallel_Interface#0x0460_00n0_-_PI_BSD_DOMn_RLS) * [3 iQue Player-specific registers](https://n64brew.dev/wiki/Parallel_Interface#iQue_Player-specific_registers) * [3.1 0x0460 0040 - PI\_BB\_ATB\_UPPER](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0040_-_PI_BB_ATB_UPPER) * [3.2 0x0460 0048 - PI\_BB\_NAND\_CTRL](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0048_-_PI_BB_NAND_CTRL) * [3.3 0x0460 004C - PI\_BB\_NAND\_CFG](https://n64brew.dev/wiki/Parallel_Interface#0x0460_004C_-_PI_BB_NAND_CFG) * [3.4 0x0460 0058 - PI\_BB\_RD\_LEN](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0058_-_PI_BB_RD_LEN) * [3.5 0x0460 005C - PI\_BB\_WR\_LEN](https://n64brew.dev/wiki/Parallel_Interface#0x0460_005C_-_PI_BB_WR_LEN) * [3.6 0x0460 0060 - PI\_BB\_GPIO](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0060_-_PI_BB_GPIO) * [3.7 0x0460 0070 - PI\_BB\_NAND\_ADDR](https://n64brew.dev/wiki/Parallel_Interface#0x0460_0070_-_PI_BB_NAND_ADDR) * [3.8 0x0461 0500 to 0x0461 0800 - PI\_BB\_ATB\_LOWER](https://n64brew.dev/wiki/Parallel_Interface#0x0461_0500_to_0x0461_0800_-_PI_BB_ATB_LOWER) * [4 iQue Player-specific memory](https://n64brew.dev/wiki/Parallel_Interface#iQue_Player-specific_memory) * [5 Physical Bus Pinout](https://n64brew.dev/wiki/Parallel_Interface#Physical_Bus_Pinout) * [5.1 PI Interface Process](https://n64brew.dev/wiki/Parallel_Interface#PI_Interface_Process) * [5.1.1 Address output](https://n64brew.dev/wiki/Parallel_Interface#Address_output) * [5.1.2 Data Read](https://n64brew.dev/wiki/Parallel_Interface#Data_Read) * [5.1.3 Constant Read](https://n64brew.dev/wiki/Parallel_Interface#Constant_Read) * [6 DMA Transfers](https://n64brew.dev/wiki/Parallel_Interface#DMA_Transfers) * [6.1 Internal process](https://n64brew.dev/wiki/Parallel_Interface#Internal_process) * [6.2 Internal process: first block](https://n64brew.dev/wiki/Parallel_Interface#Internal_process:_first_block) * [6.3 Followup transfers](https://n64brew.dev/wiki/Parallel_Interface#Followup_transfers) * [6.4 PI\_WR\_LEN readbacks after a transfer](https://n64brew.dev/wiki/Parallel_Interface#PI_WR_LEN_readbacks_after_a_transfer) * [6.5 DMA data dumps](https://n64brew.dev/wiki/Parallel_Interface#DMA_data_dumps) 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](https://n64brew.dev/wiki/Memory_map "Memory map") 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 `0x0500_0000 - 0x1FBF_FFFF` and `0x1FD0_0000 - 0x7FFF_FFFF`. 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#Ranges 0x0500'0000 - 0x1FBF'FFFF and 0x1FD0'0000 - 0x7FFF'FFFF (PI external bus)](https://n64brew.dev/wiki/Memory_map#Ranges_0x0500'0000_-_0x1FBF'FFFF_and_0x1FD0'0000_-_0x7FFF'FFFF_(PI_external_bus) "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: | | | | | --- | --- | --- | | PI address range | Domain | Device | | 0x0000\_0000 - 0x04FF\_FFFF | Domain 1 | No known device exists that operates in this range | | 0x0500\_0000 - 0x05FF\_FFFF | Domain 2 | 64DD registers | | 0x0600\_0000 - 0x07FF\_FFFF | Domain 1 | 64DD ROM | | 0x0800\_0000 - 0x0FFF\_FFFF | Domain 2 | SRAM | | 0x1000\_0000 - 0xFFFF\_FFFF | Domain 1 | ROM (though this address range is huge, and ROM only typically occupied a small portion of it) | 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 `0x6666_DCBA` will return the value `0xDCBA_DCBA`. 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 * * * | PI\_DRAM\_ADDR `0x0460 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | | DRAM\_ADDR\[7:1\] | | | | | | | 0 | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-1 | **DRAM\_ADDR\[23:1\]:** Base address of RDRAM for PI DMAs; notice that bit 0 cannot be written and is fixed to zero. | **Extra Details:** Note that DMA transfers are buggy if DRAM\_ADDR\[2:0\] are not all zero, see [below](https://n64brew.dev/wiki/Parallel_Interface#Unaligned_DMA_transfer) . #### 0x0460 0004 - PI\_CART\_ADDR * * * | PI\_CART\_ADDR `0x0460 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[31:24\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | | CART\_ADDR\[7:1\] | | | | | | | 0 | | | | | --- | --- | | bit 31-1 | **CART\_ADDR\[31:1\]:** Base address of the PI bus (e.g. cartridge) for PI DMAs; notice that bit 0 cannot be written and is fixed to 0. | **Extra Details:** This register is automatically updated by PI after any PI transfer (both DMA and direct I/O). In both cases, it will contain the first address _after_ the last transferred word. DMA transfers are a bit complex in this regard, so see below in this page where the mechanics of the transfers are detailed. #### 0x0460 0008 - PI\_RD\_LEN * * * | PI\_RD\_LEN `0x0460 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **RD\_LEN\[23:0\]:** Number of bytes, minus one, to be transferred from RDRAM, to the PI bus | **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 * * * | PI\_WR\_LEN `0x0460 000C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **WR\_LEN\[23:0\]:** Number of bytes, minus one, to be transferred from the PI bus, into RDRAM | **Extra Details:** Writing to this register will start the DMA transfer. Reading appears to almost always return \`0x7F\` (see [below](https://n64brew.dev/wiki/Peripheral_Interface#PI_WR_LEN_readbacks_after_a_transfer "Peripheral Interface") for exceptions). #### 0x0460 0010 - PI\_STATUS * * * | PI\_STATUS `0x0460 0010` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | R-0 | R-0 | RW-0 | RW-0 | | — | — | — | — | 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 * * * | PI\_BSD\_DOM1\_LAT `0x0460 0014`

PI\_BSD\_DOM2\_LAT `0x0460 0024` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | LAT\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-8 | **Undefined:** Initialized to `0` | | bit 7-0 | **LAT\[7:0\]:** The "LATch" value is the number of RCP cycles, minus one, after the address has been sent (falling edge of ALE\_L) and before the first read or write may start (falling edge of /RD or /WR) | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's LAT using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set LAT = 64 (meaning (64+1)\*16 = 1040ns). #### 0x0460 00n8 - PI\_BSD\_DOMn\_PWD * * * | PI\_BSD\_DOM1\_PWD `0x0460 0018`

PI\_BSD\_DOM2\_PWD `0x0460 0028` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | PWD\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-8 | **Undefined:** Initialized to `0` | | bit 7-0 | **PWD\[7:0\]:** The "Pulse WiDth" value is the number of RCP cycles, minus one, the /RD or /WR signals are held low | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's PWD using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set PWD = 18 (meaning (18+1)\*16 = 304ns). #### 0x0460 00nC - PI\_BSD\_DOMn\_PGS * * * | PI\_BSD\_DOM1\_PGS `0x0460 001C`

PI\_BSD\_DOM2\_PGS `0x0460 002C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | PGS\[3:0\] | | | | | | | | --- | --- | | bit 31-4 | **Undefined:** Initialized to `0` | | bit 3-0 | **PGS\[3:0\]:** The "PaGe Size" value configures how many bytes can be sequentially read/written on the bus before sending the next base address (Size = 2^(PGS+2) bytes) | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's PGS using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set PGS = 7 (meaning 2^(7+2) = 512 bytes). The smallest possible value, 0, means 2^(0+2) = 4 bytes; the largest means 2^(15+2) = 128KiB. Page Size only matters for DMA transfers; all direct accesses via the PI are only ever 32 bits wide. Notice that Page Size refers to aligned pages. For instance, with the default setting of 512 bytes, the PI will never allow a single burst to cross a 512 byte boundary. For instance, requesting 16 bytes from address 508 will actually generate two different burst transfers on the PI bus: the first of 4 bytes from offset 508, and the second of 12 bytes from offset 512. #### 0x0460 00n0 - PI\_BSD\_DOMn\_RLS * * * | PI\_BSD\_DOM1\_RLS `0x0460 0020`

PI\_BSD\_DOM2\_RLS `0x0460 0030` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | RLS\[1:0\] | | | | | | --- | --- | | bit 31-2 | **Undefined:** Initialized to `0` | | bit 1-0 | **RLS\[1:0\]:** The "ReLeaSe" value is the number of RCP cycles, minus one, that the /RD or /WR signals are held high between each 16-bits of data | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's RLS using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set RLS = 3 (meaning (3+1)\*16 = 64ns). iQue Player-specific 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 0040 - PI\_BB\_ATB\_UPPER * * * | PI\_BB\_ATB\_UPPER `0x0460 0040` (Read) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | IV Source | | 7:0 | U-? | U-? | U-? | U-? | U-? | | | | | — | — | CpuEn | DmaEn | log2(Num Blocks) | | | | | | | | --- | --- | | bit 8 | **IV Source:** Where to source the Initialization Vector from for AES decryption. See below. | | bit 5 | **CpuEn:** If set to 1, the mapping will be enabled for CPU reads | | bit 4 | **DmaEn:** If set to 1, the mapping will be enabled for DMA reads | | bit 3-0 | **log2(Num Blocks):** log2 of the number of contiguous NAND blocks to map. This is applied to an ATB entry when **ATB\_LOWER** registers are written. | **Extra Details** This register supplies only half of the configuration for an ATB entry, also see the **PI\_BB\_ATB\_LOWER** array of registers where PI addresses and the starting NAND block number are specified. Mappings work with sequences of blocks, whose length is a power of two. The register here contains the logarithm of the length so for instance writing "0" causes 1 block to be mapped; writing 4 causes 16 consecutive blocks to be mapped. ATB is the N64 PI address space emulator that translates PI DMAs into NAND flash accesses. Data stored on the NAND is encrypted with AES, ATB must transparently decrypt the data when a PI DMA requests it. To decrypt AES at an 0x10-aligned position **P** the data at **P-0x10** is also required, or if **P=0** then the Initialization Vector (IV) is required. At the start of a DMA, ATB will try to find the entry that maps the PI address for **P-0x10** into the NAND to fetch the needed prior data; for all cases but **P=0** this should resolve correctly with a contiguous PI address space mapping. To handle the **P=0** case an additional dummy mapping must precede the base address of the desired mapping, with the IV Source bit set to 1. When the IV Source bit is 1 the IV will be pulled from the memory at **0x046104D0** rather than reading any data off the NAND. For example if the mapping begins at PI address 0x10000000 as for Cartridge ROM, a dummy mapping for PI address 0x0FFFC000 with IV Source set to 1 should be programmed. #### 0x0460 0048 - PI\_BB\_NAND\_CTRL * * * | PI\_BB\_NAND\_CTRL `0x0460 0048` (Read) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | Busy | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | R-0 | R-0 | U-? | U-? | | — | — | — | — | Single-bit Error | Double-bit Error | — | — | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31 | **Busy:** Indicates that a command is currently executing. | | bit 11 | **Single-bit Error:** Indicates that a single-bit error was detected by ECC. These are automatically corrected so generally no action is required. | | bit 10 | **Double-bit Error:** Indicates that a double-bit error was detected by ECC. Unlike single-bit errors, these are not automatically recoverable. | | PI\_BB\_NAND\_CTRL `0x0460 0048` (Write) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | Execute | Interrupt | — | — | — | — | — | — | | 23:16 | W-0 | | | | | | | | | NAND Command | | | | | | | | | 15:8 | W-0 | W-0 | W-0 | | W-0 | W-0 | W-0 | | | — | Buffer Select | Device Select | | Do ECC | Multicycle | Data Length \[9:8\] | | | 7:0 | W-0 | | | | | | | | | Data Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit 31 | **Execute:** Setting this bit when writing will cause the last written command to begin execution. | | bit 30 | **Interrupt:** Whether the FLASH interrupt should be raised when the command finishes execution. | | bit 29 | **?:** Unknown. Set when issuing Page Program (first cycle) | | bit 28 | **?:** Unknown. Set when issuing Read 1, Read Status and Read ID | | bit 27 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 26 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 25 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 24 | **?:** Unknown. Set when issuing Read 1, Read ID and Page Program (first cycle) | | bit 23-16 | **NAND Command:** NAND Command to execute. Corresponds directly to commands for the K9F1208U0M flash. | | bit 15 | **?:** Unknown. Set when issuing Read 1, Block Erase (second cycle) and Page Program (second cycle) | | bit 14 | **Buffer Select:** Selects which half of the 0x400-byte PI Buffer mapped at 0x04610000 should be used for DMA operations. See **iQue Player-specific Memory** for details on this buffer | | bit 13-12 | **Device Select:** Corresponds to Chip Enable signals on the card connector. Typically 0. | | bit 11 | **Do ECC:** Whether to do ECC | | bit 10 | **Multicycle:** Set to 1 if the command issued was not the last command in a multi-cycle sequence. | | bit 9-0 | **Data Length:** Data transfer length in bytes. Unlike most other lengths this is not length minus one, a length of 0 can be specified. | **Extra Details:** Writing 0 to this register will clear any pending FLASH interrupt. #### 0x0460 004C - PI\_BB\_NAND\_CFG * * * | PI\_BB\_NAND\_CFG `0x0460 004C` | | | | | | | | | | --- | --- | | 31:24 | U-? | | | | | | | | | Configuration | | | | | | | | | 23:16 | U-? | | | | | | | | | Configuration | | | | | | | | | 15:8 | U-? | | | | | | | | | Configuration | | | | | | | | | 7:0 | U-? | | | | | | | | | Configuration | | | | | | | | | | | | --- | --- | | bit 31-0 | **Configuration:** Likely specifies timing configurations for different NAND flash chips. It is currently unknown how to relate values programmed into this register and timing information found in datasheets. | **Extra Details** System software programs `0x753E3EFF` into this register to execute a Read ID command, then selects an appropriate configuration based on the ID: | ID \[31:16\] | NAND Size in Blocks | NAND Size in MiB | Configuration Value | Part Number | | --- | --- | --- | --- | --- | | 0xEC76 | 0x1000 | 64 | 0x441F1F3F | K9F1208U0M | | 0xEC79 | 0x2000 | 128 | 0x441F1F3F | K9K1G08U0A or K9K1G08U0B | | 0x9876 | 0x1000 | 64 | 0x753E1F3F | TC58512FT | | 0x2076 | 0x1000 | 64 | 0x441F1F3F | NAND512W3A | #### 0x0460 0058 - PI\_BB\_RD\_LEN * * * | PI\_BB\_RD\_LEN `0x0460 0058` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | | | | | — | | | | | | | | | 23:16 | U-? | | | | | | | | | — | | | | | | | | | 15:8 | U-? | | | | | | | W-? | | — | | | | | | | Length \[8\] | | 7:0 | W-? | | | | | | | | | Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Length:** DMA Transfer Length (-1). Writes initiate a DMA from SDRAM starting at **PI\_DRAM\_ADDR** to the PI Buffer at **0x04610000 + PI\_CART\_ADDR**. Exact bit width unknown, it is at least long enough to transfer 0x200 bytes. | **Extra Details** It is currently unknown what the behavior is if a DMA extends out of the bounds of the target PI Buffer, and whether both buffers can be accessed in one transfer. Spare buffers can't be accessed via DMA though. The busy bits in **PI\_STATUS** also applies to these transfers. These transfers also trigger an interrupt upon completion. It is the same interrupt used for regular PI DMAs. #### 0x0460 005C - PI\_BB\_WR\_LEN * * * | PI\_BB\_WR\_LEN `0x0460 005C` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | | | | | — | | | | | | | | | 23:16 | U-? | | | | | | | | | — | | | | | | | | | 15:8 | U-? | | | | | | | W-? | | — | | | | | | | Length \[8\] | | 7:0 | W-? | | | | | | | | | Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Length:** DMA Transfer Length (-1). Writes initiate a DMA from the PI Buffer at **0x04610000 + PI\_CART\_ADDR** to SDRAM starting at **PI\_DRAM\_ADDR**. Exact bit width unknown, it is at least long enough to transfer 0x200 bytes. | **Extra Details** It is currently unknown what the behavior is if a DMA extends out of the bounds of the target PI Buffer, and whether both buffers can be accessed in one transfer. Spare buffers can't be accessed via DMA though. The busy bits in **PI\_STATUS** also applies to these transfers. These transfers also trigger an interrupt upon completion. It is the same interrupt used for regular PI DMAs. #### 0x0460 0060 - PI\_BB\_GPIO * * * | PI\_BB\_GPIO `0x0460 0060` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-? | | U-? | U-? | U-? | R-? | | R-? | | Box ID \[15:14\] | | — | — | — | Box ID \[10:9\] | | Box ID \[8:6\] \[2\] | | 23:16 | R-? | | U-? | U-? | U-? | U-? | U-? | U-? | | Box ID \[8:6\] \[1:0\] | | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | RW-? | | RW-? | RW-? | RW-? | | RW-? | RW-? | | RTC Output Enable | | LED Output Enable | Power Output Enable | RTC Control | | LED Control | Power Control | | | | | --- | --- | | bit 31-16 | **Box ID \[15:0\]:** System software calls this area the "Box ID". Various sub-fields are read out of this area separately for varying purposes. | | bit 31-30 | **Box ID \[15:14\]:** System software reads this as some sort of model identifier. Precise meaning unknown. Whether all players have the same value here is unknown. | | bit 26-25 | **Box ID \[10:9\]:** System clock speed identifier? System software reads this to determine delay intervals for some operations. | | bit 24-22 | **Box ID \[8:6\]:** The Boot ROM checks this against bits \[10:8\] in a register at MI+0x10, if they don't match this value is copied there and the system is rebooted? | | bit 7-6 | **RTC Output Enable:** Output enables for the RTC bit lines. If off, the bit lines will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit lines can be driven to logic low or logic high by writing 0 or 1 respectively to the RTC Control bits. | | bit 5 | **LED Output Enable:** Output enable for the LED bit line. If off, the bit line will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit line can be driven to logic low or logic high by writing 0 or 1 respectively to the LED Control bit. | | bit 4 | **Power Output Enable:** Output enable for the Power bit line. If off, the bit line will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit line can be driven to logic low or logic high by writing 0 or 1 respectively to the Power Control bit. | | bit 3-2 | **RTC Control:** RTC communication happens through these bits. The communication protocol is described in the [ST M41T0 Serial RTC datasheet](https://www.st.com/content/ccc/resource/technical/document/datasheet/19/24/95/e2/85/6a/47/30/CD00003139.pdf/files/CD00003139.pdf/jcr:content/translations/en.CD00003139.pdf)
; the lower bit is the clock line while the upper bit is the data line. When RTC Output Enable bits are 0 the RTC can drive the bus, in which case reading the RTC Control bits will read the values sent by the RTC. | | bit 1 | **LED Control:** If the LED Output Enable is 1, writing 0 or 1 to this bit will drive the LED bit line low or high. If 0, the LED on the front of the player will light up. If 1, the LED will switch off. | | bit 0 | **Power Control:** If the Power Output Enable is 1, writing 0 or 1 to this bit will drive the Power bit line low or high. If 1, the power will remain on. If 0, the device will power off. | **Extra Details:** Whenever a GPIO control bit (with its corresponding output enable bit set) is set to 1, the corresponding bit line will be set to logic high (3.3v). If set to 0 (with output enable set) the bit line is set to logic low (0v). The LED lights up when the LED GPIO is 0 as the LED requires a voltage difference across it to light up. One side of the LED is fixed to 3.3v while the other side is connected to the LED GPIO port; when LED Control is 1 there is no voltage difference across the LED (3.3 - 3.3 = 0v) so it does not light up, while an LED Control of 0 creates a voltage difference (3.3 - 0 = 3.3v) so the LED lights up. #### 0x0460 0070 - PI\_BB\_NAND\_ADDR * * * | PI\_BB\_NAND\_ADDR `0x0460 0070` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | W-? | | | | — | | | | | Address \[26:24\] | | | | 23:16 | W-? | | | | | | | | | Address \[23:16\] | | | | | | | | | 15:8 | W-? | | | | | | | | | Address \[15:8\] | | | | | | | | | 7:0 | W-? | | | | | | | | | Address \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Address:** Set the NAND flash address that commands issued by **PI\_BB\_NAND\_CTRL** will target. Exact bit width is unknown, however it is at least enough to address 128MiB (27 bits) | **Extra Details:** To convert a page number to an address, multiply it by 512. To convert a block number to an address, multiply it by 0x4000. #### 0x0461 0500 to 0x0461 0800 - PI\_BB\_ATB\_LOWER * * * | PI\_BB\_ATB\_LOWER `0x0461 0500 - 0x0461 0800` | | | | | | | | | | --- | --- | | 31:24 | U-? | | | | | | | | | NAND Block Number \[15:8\] | | | | | | | | | 23:16 | U-? | | | | | | | | | NAND Block Number \[7:0\] | | | | | | | | | 15:8 | U-? | | | | | | | | | PI Physical Address \[29:14\] \[15:8\] | | | | | | | | | 7:0 | U-? | | | | | | | | | PI Physical Address \[29:14\] \[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-16 | **NAND Block Number:** Starting block number to map to the provided PI address. | | bit 15-0 | **PI Physical Address \[29:14\]:** PI address to begin the mapping at, divided by 0x4000 the NAND block size. | **Extra Details** There are 192 **ATB\_LOWER** registers. Issuing a write to a particular register will program that ATB entry with a mapping, also using the current contents of **ATB\_UPPER** to complete the entry configuration. The number of blocks to map comes from **ATB\_UPPER**. Mappings involving non-contiguous or unsorted NAND blocks must occupy multiple ATB entries. These ATB entries should be sorted by PI address, from lowest to highest. It is not possible to write addresses that are not aligned to the NAND block size (0x4000) It is not possible to map more contiguous blocks than the PI address alignment allows in a single entry. For example it is not possible to map 2 contiguous blocks in the same ATB entry if the base address is 0x10004000. The maximum number of blocks you can map for given `(pi_addr, nblocks)` in a single ATB entry is `1 << min(ctz(pi_addr/0x4000), ceil(log2(nblocks)))` where `ctz(x)` counts the number of trailing zeros in the binary representation of `x`. iQue Player-specific memory =========================== In addition to extra registers, the iQue Player maps additional memory into the PI registers address space for use in various PI operations. | Address Range | | Name | Description | | --- | --- | --- | --- | | 0x04610000 | 0x046101FF | PI Buffer 0 | Holds intermediate data between SDRAM and the NAND. NAND commands transfer data between this buffer and the flash; transfers between this buffer and SDRAM is done via DMAs triggered by **PI\_BB\_RD\_LEN** and **PI\_BB\_WR\_LEN**. AES decryptions happen in this buffer. | | 0x04610200 | 0x046103FF | PI Buffer 1 | Same as Buffer 0 in operation. | | 0x04610400 | 0x0461040F | PI Spare Data 0 | Holds "spare data" for buffer 0 contents. | | 0x04610410 | 0x0461041F | PI Spare Data 1 | Holds "spare data" for buffer 1 contents. | | 0x04610420 | 0x046104CF | AES Expanded Key | Holds the AES expanded key for AES decryption operations. | | 0x046104D0 | 0x046104DF | AES Initialization Vector | Holds the AES IV for AES decryption operations. | Access by the CPU to these buffers must be performed as if the buffers were memory mapped from PI address space: that is, it is important that the PI status register reports that the PI unit is idle before attempting a read or a write. Physical Bus Pinout =================== The PI Bus is a Bi-directional and multiplexed interface with a 16bit data path to the ROM, 64DD, [Flash](https://n64brew.dev/wiki/Flash "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. | | | | | --- | --- | --- | | Pin Name | Cart pins | Description | | AD0 | 28 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[16\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[0\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[0\] | | AD1 | 29 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[17\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[1\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[1\] | | AD2 | 30 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[18\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[2\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[2\] | | AD3 | 32 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[19\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[3\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[3\] | | AD4 | 36 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[20\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[4\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[4\] | | AD5 | 37 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[21\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[5\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[5\] | | AD6 | 40 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[22\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[6\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[6\] | | AD7 | 41 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[23\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[7\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[7\] | | AD8 | 16 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[24\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[8\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[8\] | | AD9 | 15 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[25\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[9\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[9\] | | AD10 | 12 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[26\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[10\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[10\] | | AD11 | 11 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[27\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[11\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[11\] | | AD12 | 7 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[28\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[12\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[12\] | | AD13 | 5 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[29\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[13\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[13\] | | AD14 | 4 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[30\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[14\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[14\] | | AD15 | 3 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[31\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[15\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[15\] | | /ALEH | 35 | Parts on the PI bus are expected to latch the high address (Bits\[31:16\]) when this goes from HIGH to LOW.

When this signal goes from LOW to HIGH it resets the internal address system so it can await for a new address request.

This stays HIGH when in idle and LOW when processing data. Commercial ROMs also use this as /CE, entering a low-power state while this signal is high.

This signal will be high for at least 7 FSB cycles, each time a new address is loaded. | | /ALEL | 33 | Parts on the PI bus are expected to latch the low address (Bits\[15:0\]) when this goes from HIGH to LOW.

No action has been seen when this goes from LOW to HIGH.

This stays HIGH when in idle and LOW when processing data.

This signal will be high for at least 14 FSB cycles, each time a new address is loaded, ending 7 FSB cycles after ALEH falls. | | /WR | 8 | This is the signal that sends a write command to the FLASH ram, SRAM or 64DD

While this signal is low, the RCP drives the PI bus with the current word of data.

When this signal goes from LOW to HIGH external parts are expected to record the value at that moment, if they need it. The RCP and external parts are also expected to increase the internal address counter in preparation for the next word transferred.

The RCP will not change this signal from HIGH to LOW until either the Latency (PI\_BSD\_DOMn\_LAT) or Release (PI\_BSD\_DOMn\_RLS) registers have counted the required number of FSB clocks.

This stays HIGH when idle. | | /RD | 10 | This is the signal that sends a read command to the ROM, FLASH ram, SRAM or 64DD.

While this signal is low, the RCP expects that some device will drive the PI Bus.

When this signal goes from LOW to HIGH the RCP will record the value at that moment. The RCP and external parts are also expected to increase the internal address counter in preparation for the next word transferred.

The /RD signal has the same timing constraints as the /WR signal above.

This stays HIGH when idle. | PI Interface Process -------------------- ### Address output  [](https://n64brew.dev/wiki/File:Rom_address_output.png "Rom Address Output") ### Data Read  [](https://n64brew.dev/wiki/File:Rom_Read_Data.png "Rom Read Data") ### Constant Read  [](https://n64brew.dev/wiki/File:Constant_ROM_Access.png "Constant ROM Access") DMA Transfers ============= PI DMA is well defined for so-called "aligned transfers", which are defined by the following constraints: 1. RDRAM address must be 8 bytes aligned 2. PI address must be 2 bytes aligned 3. Length must be a multiple of 2 Notice that the second point might be considered redundant from a hardware point of view given that both registers holding addresses are fixed to be 2-byte aligned (LSB is fixed to 0), but from a software point of view, this has to be taken into account. The behavior of PI DMA when the first and third constraint are not respected is not well designed; it seems like the designers attempted to implement support for loosing these constraints but gave up in the middle, leaving the hardware in a state that can only be described as "buggy". This also leaks some internal details on how the transfers are performed. To implement PI DMA, the RCP uses an internal 128 byte buffer. The following section attempts to describe the exact process (though the \*actual\* process implemented in the hardware is unknown; the following does match in observable behavior). NOTE: only DMA write transfers (PI -> RDRAM) have been analyzed in detail, using default PI DOM1 settings. It is expected that read transfers (RDRAM -> PI) behave in a similar way, though it's not been fully tested yet. We also expect PI DOM1 page size setting to somehow affect the transfer, though this has also not been explored yet. #### Internal process The transfer is split in blocks of maximum 128 bytes each one. Within each block, the PI first fills the internal buffer fetching data from the PI bus, and then write backs the buffer contents to RDRAM. This can be observed by monitoring PI\_DRAM\_ADDR and PI\_CART\_ADDR: during the transfer, it can be first seen PI\_CART\_ADDR moving forward, and then PI\_DRAM\_ADDR catching up with a leap (writing to RDRAM is much faster than reading PI). In general for all blocks of the transfer (excluding the first one, see below), the logic appears to be as follows: * Compute the block size. This is the smallest between the remaining length, the end of the current RDRAM page, and 128 bytes (which is the maximum size of the internal buffer). RDRAM pages are 2 KiB (0x800) long, so for instance if the current RDRAM address (at the beginning of the block) is 0x147e0, the block size will be 0x20 because the RDRAM page ends at 0x147ff. * Fill the page using PI reads from the bus. All PI accesses are always 16-bit long, so if the block size was odd (which happens on the last block, if the remaining length is odd), one extra byte will be fetched from PI into the internal buffer. * Write back into RDRAM. The exact format of RDRAM writes is unknown at the moment; since PI DMA transfers are well-defined for 8-byte aligned RDRAM addresses, it is assumed that 64-bit writes are used (a burst like that used for D/I cache writebacks would require 16-byte alignment or more to be performed). If an extra byte was fetched in the previous step, that byte is also written to RDRAM. So in general odd-length PI DMA transfers will transfer one byte more than requested. The above logic applies for all blocks of the transfer, excluding the first one. The first block in fact is treated specially by PI. It appears that the goal of the designers was to use the first block to realign transfers to 8-byte in RDRAM, which possibly causes the first block to use smaller, masked writes to RDRAM. So, even if the RDRAM starting address is misaligned, all blocks besides the first one will begin from a 8-byte aligned RDRAM address, and behave with the logic described above. #### Internal process: first block These are the differences in logic while processing the first block, which mostly concerns how to handle the initial RDRAM misalignment. In this description, we refer to _RDRAM misalignment_ as the amount of bytes that the RDRAM address is distant from the previous 8-byte aligned word (that is, the misalignment is the value of the last 3 bits of the RDRAM address). Notice that the RDRAM address hardware register has the LSB fixed 0, so misalignment can be either 2, 4, or 6. * The internal 128 byte buffer is filled starting from the index matching the misalignment. This might affect the maximum size of the first block: for instance, if misalignment is 6, the maximum size is not 128 but 122, because the first 6 bytes are skipped. * Writes to RDRAM seems to use some kind of masking, so they are correctly done at the byte granularity. This means that odd length transfers in the first block appear to work correctly. Notice that this applies only to the first block whatever its size is; the size (as described above) might be limited by the end of the RDRAM page, in which case only odd transfers up to there are working correctly. * As an exception to the above exception, if the first block reaches the end of the 128 byte buffer, the last 2 bytes of the buffer are always written back in full to RDRAM, even though one less byte was requested. * Example: PI DMA transfer with misalignment 0 and RDRAM page end far away. Odd lengths up to 125 (included) work correctly; odd transfers of exactly 127 bytes are rounded up to 128 (since they reach the last 16-bit word of the buffer). Also odd transfers of 129 or more, since they need two blocks to be performed, fall back into the general rule where one more byte is transferred. * Example: PI DMA transfer with misalignment 6 and RDRAM page end far away. Odd lengths up to 119 (included) work correctly; odd transfers of exactly 121 bytes are rounded up to 122 (since they reach the last 16-bit word of the buffer). Also odd transfers of 123 or more, since they need two blocks to be performed, fall back into the general rule where one more byte is transferred. * There seems to be a hardware bug related how RDRAM writes are performed, in case of misaligned addresses. It seems like the hardware is counting the block length starting from index 0 of the buffer, even though the first byte was actually placed at the index matching the misalignment, and even though masking is performed correctly. This means that for instance, if misalignment is 6 and the length of 8, the following happens: * First, 8 bytes are fetched from the PI bus and put at index 6..13 in the internal buffer. * Then, RDRAM writes are performed but the hardware believes the block ends at index 8, so only bytes 6..8 are written back to RDRAM. * Symmetrically, if the buffer is full (128 bytes), the last 6 bytes will not be transferred because of the same bug (even if those bytes were fetched by the PI bus). So there will be a "hole" of 6 bytes in the RDRAM output buffer. For instance, if misalignment is 6 and the length is 1024, and the RDRAM page end is far away, the following happens on the first block: * Block size is computed as 122 bytes. * 122 bytes are fetched from the PI bus, and put at index 6..127 in the internal buffer. * RDRAM writes are performed but the hardware believes that the block ends at index 121, so only bytes 6..121 are written back to RDRAM. * Notice that, this notwithstanding, RDRAM address is correctly rounded up to 8 byte at the end of the block (see below), so the second block will behave correctly. There will be a hole in RDRAM as bytes 122.127 in the first block are never written back to RDRAM, so the content of RDRAM for those bytes is not affected by DMA. * RDRAM address register is always rounded up to the next 8 byte alignment at the end of the first block. In most normal cases, the logic above already ensures that the address ends up being aligned at the end of the block, but the rounding up happens even in cases like short transfers that ends with the first block at ends at an arbitrary byte. #### Followup transfers After a DMA transfer is finished, it is possible to trigger a "followup transfer", that is a transfer that sequentially continues the previous one, by simply writing a new length to the PI\_WR\_LEN register. In this case, the current values of PI\_DRAM\_ADDR and PI\_CART\_ADDR are used at the beginning of the transfers. Those values will match the last addresses as updated by the first transfer. The above section describes in details how PI reads and RDRAM writes are done, and registers are updated, so they also implicitly describe how a followup transfer behaves in various edge cases (short transfers, misaligned transfers, etc.) #### PI\_WR\_LEN readbacks after a transfer Reading back PI\_WR\_LEN after a transfer is done, appears to always be fixed at 0x7F. The only exception that has been noticed is when the transfer was smaller than 8 bytes: in that case, the value is 0x7F minus the initial RDRAM misalignment. For instance, if the RDRAM misalignment was 4, the value found in the register at the end of the transfer will be 0x7B. #### DMA data dumps To further investigate and understand how PI DMA is performed, the repo [n64\_pi\_dma\_test](https://github.com/rasky/n64_pi_dma_test) can be used. The repo contains data dumps acquires on real hardware of DMA transfers with all possible misalignments (0, 2, 4, 6), all lengths from 1 to 384 bytes, and all distances from RDRAM page end from 0 to 128 bytes. It also contains timing information on all those transfers. The repo can be used as a testsuite for emulators, but also to further investigate other side cases. Retrieved from "[https://n64brew.dev/wiki/Parallel\_Interface?oldid=5761](https://n64brew.dev/wiki/Parallel_Interface?oldid=5761) " --- # Konami Dance Pad - N64brew Wiki [](https://n64brew.dev/wiki/Dance_Pad#) Konami Dance Pad ================ (Redirected from [Dance Pad](https://n64brew.dev/wiki/Dance_Pad?redirect=no "Dance Pad") ) The **Konami Dance Pad** is used exclusively for _Dance Dance Revolution: Disney Dancing Museum_. It was released only in Japan along with the game in November 2000. The pad consists of a 3x3 grid with an image of Mickey Mouse in the center and giant arrows adjacent. When viewed from above, B is in the upper left and A is in the upper right. The start button is above the A. The bottom row has an image of Donald Duck on the left and Minnie Mouse on the right. Retrieved from "[https://n64brew.dev/wiki/Konami\_Dance\_Pad?oldid=5140](https://n64brew.dev/wiki/Konami_Dance_Pad?oldid=5140) " --- # Keyboard - N64brew Wiki [](https://n64brew.dev/wiki/Randnet_Keyboard#) Keyboard ======== (Redirected from [Randnet Keyboard](https://n64brew.dev/wiki/Randnet_Keyboard?redirect=no "Randnet Keyboard") ) The **Nintendo 64 Keyboard** (Nintendo 64 キーボード) is an accessory released for the Nintendo 64DD in Japan. Nintendo released the accessory in conjunction with Randnet DD. Stickers came with the set that you could place over the buttons. With the keyboard, players could chat with people over the internet and send them digital mail. The accessory was never released outside of Japan. It cost ¥4,600. While the keyboard itself was black, the keys were white and dark blue. It seems to be used exclusively by one game, the Randnet Disk, which is a web browser and email client. Key Matrix Map -------------- 1. Converting Joybus to Compressed: #define JBSC2CSC(x, y) ((x - 2) << 4) + (y - 1) 2. Converting Compressed back to Joybus: #define CSC2JBSC(x) ((x >> 4) + 2), (x & 0x0F) + 1 | Key Function | Compressed Scan Code (1 byte) | Joybus Scan Code (2 byte) | | --- | --- | --- | | 0 | 0x45 | \[0x06, 0x06\] | | 1 | 0xA4 | \[0x0C, 0x05\] | | 2 | 0x34 | \[0x05, 0x05\] | | 3 | 0x44 | \[0x06, 0x05\] | | 4 | 0x54 | \[0x07, 0x05\] | | 5 | 0x64 | \[0x08, 0x05\] | | 6 | 0x74 | \[0x09, 0x05\] | | 7 | 0x75 | \[0x09, 0x06\] | | 8 | 0x65 | \[0x08, 0x06\] | | 9 | 0x55 | \[0x07, 0x06\] | | A | 0xB6 | \[0x0D, 0x07\] | | Alt\_L | 0xE7 | \[0x10, 0x08\] | | Asterisk | 0x42 | \[0x06, 0x03\] | | B | 0x57 | \[0x07, 0x08\] | | BackQuote | 0xA5 | \[0x0C, 0x06\] | | BackSlash | 0xE3 | \[0x10, 0x04\] | | BackSpace | 0xB5 | \[0x0D, 0x06\] | | Bar | 0xF4 | \[0x11, 0x05\] | | BracketLeft | 0xA3 | \[0x0C, 0x04\] | | BracketRight | 0x25 | \[0x04, 0x06\] | | C | 0x37 | \[0x05, 0x08\] | | Caps\_Lock | 0xD4 | \[0x0F, 0x05\] | | Control\_L | 0xF6 | \[0x11, 0x07\] | | D | 0x36 | \[0x05, 0x07\] | | Down | 0x14 | \[0x03, 0x05\] | | E | 0x40 | \[0x06, 0x01\] | | End | 0x05 | \[0x02, 0x06\] | | Escape | 0x87 | \[0x0A, 0x08\] | | F | 0x46 | \[0x06, 0x07\] | | F1 | 0x90 | \[0x0B, 0x01\] | | F10 | 0x83 | \[0x0A, 0x04\] | | F11 | 0x02 | \[0x02, 0x03\] | | F12 | 0x95 | \[0x0B, 0x06\] | | F2 | 0x80 | \[0x0A, 0x01\] | | F3 | 0x97 | \[0x0B, 0x08\] | | F4 | 0x86 | \[0x0A, 0x07\] | | F5 | 0x96 | \[0x0B, 0x07\] | | F6 | 0x81 | \[0x0A, 0x02\] | | F7 | 0x91 | \[0x0B, 0x02\] | | F8 | 0x82 | \[0x0A, 0x03\] | | F9 | 0x92 | \[0x0B, 0x03\] | | G | 0x56 | \[0x07, 0x07\] | | Greater | 0x61 | \[0x08, 0x02\] | | H | 0x66 | \[0x08, 0x07\] | | Henkan | 0xC1 | \[0x0E, 0x02\] | | Home | 0xEF | \[0x10, 0x10\] | | I | 0x63 | \[0x08, 0x04\] | | J | 0x76 | \[0x09, 0x07\] | | K | 0x72 | \[0x09, 0x03\] | | Kana\_Lock | 0xE5 | \[0x10, 0x06\] | | L | 0x62 | \[0x08, 0x03\] | | Left | 0x04 | \[0x02, 0x05\] | | Less | 0x71 | \[0x09, 0x02\] | | M | 0x77 | \[0x09, 0x08\] | | Menu | 0x94 | \[0x0B, 0x05\] | | Meta\_L | 0xD6 | \[0x0F, 0x07\] | | Minus | 0x35 | \[0x05, 0x06\] | | Muhenkan | 0xE1 | \[0x10, 0x02\] | | N | 0x67 | \[0x08, 0x08\] | | Next | 0x06 | \[0x02, 0x07\] | | Num\_Lock | 0x84 | \[0x0A, 0x05\] | | O | 0x53 | \[0x07, 0x04\] | | P | 0x43 | \[0x06, 0x04\] | | Plus | 0x52 | \[0x07, 0x03\] | | Prior | 0x07 | \[0x02, 0x08\] | | Q | 0xA0 | \[0x0C, 0x01\] | | QuestionMark | 0x51 | \[0x07, 0x02\] | | QuoteLeft | 0x33 | \[0x05, 0x04\] | | R | 0x50 | \[0x07, 0x01\] | | Return | 0xB3 | \[0x0D, 0x04\] | | Right | 0x24 | \[0x04, 0x05\] | | S | 0xA6 | \[0x0C, 0x07\] | | Shift\_L | 0xC0 | \[0x0E, 0x01\] | | Shift\_R | 0xC5 | \[0x0E, 0x06\] | | Space | 0x41 | \[0x06, 0x02\] | | T | 0x60 | \[0x08, 0x01\] | | Tab | 0xB0 | \[0x0D, 0x01\] | | U | 0x73 | \[0x09, 0x04\] | | Up | 0x03 | \[0x02, 0x04\] | | V | 0x47 | \[0x06, 0x08\] | | W | 0x30 | \[0x05, 0x01\] | | X | 0xA7 | \[0x0C, 0x08\] | | Y | 0x70 | \[0x09, 0x01\] | | Z | 0xB7 | \[0x0D, 0x08\] | | Zenkaku\_Hankaku | 0xB4 | \[0x0D, 0x05\] | Retrieved from "[https://n64brew.dev/wiki/Keyboard?oldid=5638](https://n64brew.dev/wiki/Keyboard?oldid=5638) " --- # Flashcarts - N64brew Wiki [](https://n64brew.dev/wiki/Flashcarts#) Flashcarts ========== A flashcart (so named because they usually have flash) is a cartridge with onboard memory that can be used to develop homebrew. The following flashcarts have been produced for the N64: * [SummerCart64](https://summercart64.dev/) . An open-source, open-hardware flashcart packed with lots of development and gaming features. It is the fastest * [EverDrive64](https://n64brew.dev/wiki/Everdrive_64 "Everdrive 64") . Multiple hardware revisions have been published over the years, with X7 being the latest revision (as of 2025). * ED64Plus. This is a generic name often used only to refer to Chinese clones of the original EverDrive 64 V1. Normally they are the cheapest flashcarts on the market, while being quite limited in speed and reliability. * 64drive. A high-quality commercial flashcart developed by Retroactive, unfortunately not available anymore since 2020. * Neo Myth 64 * Retrostage N64 Blaster * Chinese Reproduction Carts (All CIC & Save types, rewritable) The following are flashcart projects that are either in development or abandoned: * PicoCart64 [https://github.com/kbeckmann/PicoCart64](https://github.com/kbeckmann/PicoCart64) * DREAMDrive64 [https://dreamcraftindustries.com/products/dreamdrive64](https://dreamcraftindustries.com/products/dreamdrive64) * DaisyDrive64 [https://github.com/nopjne/DaisyDrive64](https://github.com/nopjne/DaisyDrive64) * El Barato 64 [https://github.com/Hazematman/El-Barato-64](https://github.com/Hazematman/El-Barato-64) Retrieved from "[https://n64brew.dev/wiki/Flashcarts?oldid=5827](https://n64brew.dev/wiki/Flashcarts?oldid=5827) " --- # Controller - N64brew Wiki [](https://n64brew.dev/wiki/Controller#) Controller ========== The N64 controller, with it's tri-wing design, featured 14 button inputs, and an optical encoder based analog stick. The controller connects to the console over three wires, power, data, and ground. On the bottom, it also had a 32 pin edge board connector for additional accessory Paks, such as the [Controller Pak](https://n64brew.dev/wiki/Controller_Pak "Controller Pak") and [Rumble Pak](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak") . [![](https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/N64-Controller-Gray.jpg/960px-N64-Controller-Gray.jpg)](https://n64brew.dev/wiki/File:N64-Controller-Gray.jpg) Contents -------- * [1 Design](https://n64brew.dev/wiki/Controller#Design) * [2 Operation](https://n64brew.dev/wiki/Controller#Operation) * [2.1 Analog Stick](https://n64brew.dev/wiki/Controller#Analog_Stick) * [2.2 Accessory Port](https://n64brew.dev/wiki/Controller#Accessory_Port) * [2.2.1 Pak Detection](https://n64brew.dev/wiki/Controller#Pak_Detection) * [3 Homebrew Controllers](https://n64brew.dev/wiki/Controller#Homebrew_Controllers) Design ------ The controller's design was unique in that it had three handles, which allowed the player to hold the controller in multiple ways. However, typically the right hand would be on the right-most handle, and the left would alternate between the middle or left depending on whether the game utilized the D-pad or analog stick. The standard controller used rubber-like contacts underneath plastic button caps. When pressed, these contacts would close a circuit printed onto the PCB inside the controller; this is different from a clicky push button, but not uncommon for game controllers. Operation --------- The controller is managed by a single IC chip that works as an interface between the console and the other hardware components. The chip is proprietary and has a generic label "Nintendo NUS-CNT" plus a model number. There are three small PCBs connected via wires for the L, R, and Z buttons. Eleven pads on the main PCB are used for the other buttons. The analog stick is a self contained module that is connected via a 6-pin, JST-PH compatible connector. A 32-pin port connector is used to interface with different accessories. ### Analog Stick The analog stick uses a pair of optical encoding disks to determine its relative position. This works similar to linear encoders found in printers, except that instead of being a strip of tape/film, the N64 uses a rotating disk for each axis. Each disk has small holes along the circumference of the disk. These holes allow light to pass through to an optical sensor on the opposite side. When the stick is moved, there are two signals generated per axis, for a total of four, which are slightly offset from each other. One of the two signals is used as the interrupt signal (`XA` and `YA` for the x-axis and y-axis respectively), and the other (`XB` and `YB`) is compared with the state of the first in order to determine which direction (positive or negative) that analog stick is moving for that axis. If the stick is moving in the positive direction at a constant speed, the two signals will look similar to this:  [](https://n64brew.dev/wiki/File:Analogstick-xaxb-positive.svg) If the stick is moving in the negative direction, they will look similar to this:  [](https://n64brew.dev/wiki/File:Analogstick-xaxb-negative.svg)  [](https://n64brew.dev/wiki/File:Analogstick-pinout-diagram.png) Analog stick connector pinout To determine the direction along a particular axis and how far the stick has moved, the NUS-CNT chip will listen for any changing edge of that axis' interrupt signal (e.g. `XA` for the x-axis). _A changing edge means any time the signal goes from LOW to HIGH, or HIGH to LOW._ When an edge change is detected, the state of `XA` is compared against `XB`. If `input`, then the stick has moved +1 units. If `input`, then the stick has moved -1 units. While the analog stick reports _relative_ movement whenever it moves, the N64 console requires the current position, not relative. In order to provide that, the NUS-CNT chip keeps track of these movements. The initial values for each axis is zero. _Although not tested/quantified, it may be possible to extrapolate the speed at which the user is moving the stick. The signals produced vary in length between each edge change. The time between edge changes may be able to be used to indicate speed. While speed isn't needed by the console, this could still offer some additional data in a custom controller or interface._ The analog stick has a 6-pin connector: `VCC` is 3.3V. `GND` is common ground. `XA` and `XB` are the signal pins for the x-axis. `YA` and `YB` are for the y-axis. ### Accessory Port  [](https://n64brew.dev/wiki/File:AccessoryPortPinout.png) Pinout of the accessory port connector with respect to the pak's orientation Pak devices could be plugged into this port to expand the console's capabilities or to enhance the player's experience. The console communicates with these devices via the [Joybus Protocol](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") , however a microchip is used on the controller to interpret the Joybus commands and interface with the paks. #### Pak Detection If the `DETECT` pin is pulled HIGH, then a pak is inserted in the connector. Paks do this by directly connecting the pin to 3.3V. When the console sends the [0x00 Joybus](https://n64brew.dev/wiki/Joybus_Protocol#Additional_Command_Details "Joybus Protocol") command, the controller will return its hardware ID and then one additional byte that tells the console whether a pak is inserted (`0x01`) or not (`0x02`). If a game chooses to do so, it is possible to detect what kind of pak is inserted. _\*WIP Section\*_ Homebrew Controllers -------------------- While most of the official controller uses fairly common connectors and buttons, the port connector and the NUS-CNT chips are proprietary and hard to source. The vast majority of custom controllers use a salvaged NUS-CNT IC chip as the brain, to interface with the console. However, [Bigbass](https://n64brew.dev/wiki/User:Bigbass "User:Bigbass") has developed an open source alternative that utilizes a PIC microcontroller as the brain, and other modern components. More details are on the [project's Github page](https://github.com/bigbass1997/PIC-CNT64) . The [TAStm32](https://github.com/Ownasaurus/TAStm32) is another alternative designed as a replay device for Tool-Assisted Speedruns. The port connector uses an extremely uncommon pitch of 1.5mm, with a board thickness of 1.2mm. No sources are known at this time for purchasing these connectors. Retrieved from "[https://n64brew.dev/wiki/Controller?oldid=1485](https://n64brew.dev/wiki/Controller?oldid=1485) " --- # Joybus Protocol - N64brew Wiki [](https://n64brew.dev/wiki/Joybus_Protocol#) Joybus Protocol =============== The **Joybus Protocol** is a proprietary, non-standard, serial protocol by which the N64's [Serial Interface](https://n64brew.dev/wiki/Serial_Interface "Serial Interface") (as well as the GameCube and Game Boy Advance) communicates with controllers, controller accessories ([Controller Pak](https://n64brew.dev/wiki/Controller_Pak "Controller Pak") , [Rumble Pak](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak") , etc.), [keyboards](https://n64brew.dev/wiki/Randnet_Keyboard "Randnet Keyboard") , [mice](https://n64brew.dev/wiki/Mouse "Mouse") , [game cartridges](https://n64brew.dev/wiki/Game_Pak "Game Pak") , and other devices plugged into the console. The protocol is run between the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") and the various joybus devices. PIF-NUS acts as a master in the protocol, though in general it only communicates with accessories as instructed by VR4300. In fact, the joybus protocol is normally encapsulated in a [frame](https://n64brew.dev/wiki/PIF-NUS?wvprov=sticky-header#Joybus_frame_(controller_and_EEPROM_communication)) written in PIF RAM by VR4300, and replies are written back in the PIF-RAM for the VR4300 to accesses them. Contents -------- * [1 Operation](https://n64brew.dev/wiki/Joybus_Protocol#Operation) * [1.1 Reset](https://n64brew.dev/wiki/Joybus_Protocol#Reset) * [2 Command List](https://n64brew.dev/wiki/Joybus_Protocol#Command_List) * [3 Command Details](https://n64brew.dev/wiki/Joybus_Protocol#Command_Details) * [3.1 0xFF - Reset / Info](https://n64brew.dev/wiki/Joybus_Protocol#0xFF_-_Reset_/_Info) * [3.2 0x00 - Info](https://n64brew.dev/wiki/Joybus_Protocol#0x00_-_Info) * [3.2.1 Controller status byte](https://n64brew.dev/wiki/Joybus_Protocol#Controller_status_byte) * [3.3 0x01 - Controller State](https://n64brew.dev/wiki/Joybus_Protocol#0x01_-_Controller_State) * [3.3.1 Standard Controller](https://n64brew.dev/wiki/Joybus_Protocol#Standard_Controller) * [3.3.2 Mouse](https://n64brew.dev/wiki/Joybus_Protocol#Mouse) * [3.3.3 Densha de Go](https://n64brew.dev/wiki/Joybus_Protocol#Densha_de_Go) * [3.3.4 Other Devices](https://n64brew.dev/wiki/Joybus_Protocol#Other_Devices) * [3.4 0x02 - Read Controller Accessory](https://n64brew.dev/wiki/Joybus_Protocol#0x02_-_Read_Controller_Accessory) * [3.5 0x03 - Write Controller Accessory](https://n64brew.dev/wiki/Joybus_Protocol#0x03_-_Write_Controller_Accessory) * [3.6 0x04 - Read EEPROM Block](https://n64brew.dev/wiki/Joybus_Protocol#0x04_-_Read_EEPROM_Block) * [3.7 0x05 - Write EEPROM Block](https://n64brew.dev/wiki/Joybus_Protocol#0x05_-_Write_EEPROM_Block) * [3.8 0x06 - Real-Time Clock Info](https://n64brew.dev/wiki/Joybus_Protocol#0x06_-_Real-Time_Clock_Info) * [3.9 0x07 - Read Real-Time Clock Block](https://n64brew.dev/wiki/Joybus_Protocol#0x07_-_Read_Real-Time_Clock_Block) * [3.9.1 RTC Block 0 (Control Registers)](https://n64brew.dev/wiki/Joybus_Protocol#RTC_Block_0_(Control_Registers)) * [3.9.2 RTC Block 1 (SRAM)](https://n64brew.dev/wiki/Joybus_Protocol#RTC_Block_1_(SRAM)) * [3.9.3 RTC Block 2 (Date/Time)](https://n64brew.dev/wiki/Joybus_Protocol#RTC_Block_2_(Date/Time)) * [3.9.4 RTC Block 3 (Empty)](https://n64brew.dev/wiki/Joybus_Protocol#RTC_Block_3_(Empty)) * [3.10 0x08 - Write Real-Time Clock Block](https://n64brew.dev/wiki/Joybus_Protocol#0x08_-_Write_Real-Time_Clock_Block) * [3.11 0x13 - Read from Game Boy Pak](https://n64brew.dev/wiki/Joybus_Protocol#0x13_-_Read_from_Game_Boy_Pak) * [3.12 0x14 - Write to Game Boy Pak](https://n64brew.dev/wiki/Joybus_Protocol#0x14_-_Write_to_Game_Boy_Pak) * [4 Checksums](https://n64brew.dev/wiki/Joybus_Protocol#Checksums) * [4.1 Data CRC](https://n64brew.dev/wiki/Joybus_Protocol#Data_CRC) * [4.2 Address Checksum](https://n64brew.dev/wiki/Joybus_Protocol#Address_Checksum) * [4.3 References](https://n64brew.dev/wiki/Joybus_Protocol#References) Operation =========  [](https://n64brew.dev/wiki/File:Joybus_Bits.svg) The protocol utilizes four types of bits: Zero, One, the Console Stop Bit, and the Controller Stop Bit. Zero, One, and the Controller Stop Bits are 4μs long, while the Console Stop Bit is 3μs. Communication is always initiated by the console, by sending an 8 bit (one byte) command to the device plugged in (not necessarily always a standard controller), followed by additional arguments (zero or multiple bytes). A Console Stop Bit will usually follow (though the usage of this stop bit can be disabled or replaced with a wait time, see [PIF Joybus flag bits](https://n64brew.dev/wiki/PIF-NUS?wvprov=sticky-header#Flag_bits_in_PIF_command) ). For example, when the console wishes to write to a Controller Pak, it will send 0x03, followed by a 2 byte address, 32 bytes of data, and then the stop bit. The controller or device will read this command and any extra data, and then respond appropriately. The reply is normally made by at least one byte, but can contain multiple bytes, followed by the controller stop bit. The default state of the data pin is HIGH. If the connected device isn't in the process of sending a signal, it should not default to sending a HIGH signal (either set to LOW for reading, high-impedance, or disconnect). Wrongly setting the pin HIGH may disrupt the console and will prevent intermediate devices from reading any console signals. ### Reset The joybus protocol appears also to support a special "hardware reset" command on the wire, in which the line is pulled low for 1 ms. To ask the PIF to generate this signal, the frame must contain a [special escape code](https://n64brew.dev/wiki/PIF-NUS#Escape_codes "PIF-NUS") (0xFD) or a [TX flag](https://n64brew.dev/wiki/PIF-NUS#TX_byte:_special_flags "PIF-NUS") must be set, so this signal is sort of an "off-band" reset sent to the device. Command List ============ Because all commands are exactly 8 bits long, there can be a total of 256 commands. However, only about 16 are known for the N64, and a few more for GameCube and Game Boy Advance. The protocol architecture assumes that all Joybus devices must support two commands: 0x00 Info, and 0xFF Reset and Info. Besides these two commands, all other commands are actually device-specific. Software running on the N64 is supposed to first use the 0x00 Info command to discover connected devices, and then send them their own appropriate commands. In general, difference devices could thus reuse the same command ID, but this was never done for officially devices. This might be seen also a safeguard against the software having bugs or race conditions in device detection and thus ending up sending the wrong command for a device. Official devices never reply to commands they do not support, causing a timeout to be reported in the [RX flags in the PIF frame](https://n64brew.dev/wiki/PIF-NUS#RX_byte:_special_flags "PIF-NUS") . | Command | Description | Console | Devices | Tx Bytes | Rx Bytes | | --- | --- | --- | --- | --- | --- | | 0xFF | Reset & Info | N64, GC, GBA | All | 1 | 3 | | 0x00 | Info | N64, GC, GBA | All | 1 | 3 | | 0x01 | Controller State | N64 | [Controller](https://n64brew.dev/wiki/Controller "Controller")
, Mouse, Densha de Go, Dance Pad, Fishing Rod | 1 | 4 | | 0x02 | Read Controller Accessory | N64 | [Transfer](https://n64brew.dev/wiki/Transfer_Pak "Transfer Pak")
, Controller, [Bio Sensor](https://n64brew.dev/wiki/Bio_Sensor?action=edit&redlink=1 "Bio Sensor (page does not exist)")
, and Rumble Paks | 3 | 33 | | 0x03 | Write Controller Accessory | N64 | Transfer, Controller, Bio Sensor, and Rumble Paks | 35 | 1 | | 0x04 | Read EEPROM | N64 | Cartridge | 2 | 8 | | 0x05 | Write EEPROM | N64 | Cartridge | 10 | 1 | | 0x06 | RTC**(1)** Info | N64 | Dōbutsu no Mori (Animal Forest) Cartridge | 1 | 3 | | 0x07 | Read RTC**(1)** Block | N64 | Dōbutsu no Mori (Animal Forest) Cartridge | 2 | 9 | | 0x08 | Write RTC**(1)** Block | N64 | Dōbutsu no Mori (Animal Forest) Cartridge | 10 | 1 | | 0x09-0x0D | Unknown | N64 | [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit") | ? | ? | | 0x0E | Reserved | | | ? | ? | | 0x0F | Unknown | | | ? | ? | | 0x10 | Reserved | | | ? | ? | | 0x11 | Reserved | | | ? | ? | | 0x12 | Reserved | | | ? | ? | | 0x13 | Read Keypress | N64 | [Randnet Keyboard](https://n64brew.dev/wiki/Randnet_Keyboard "Randnet Keyboard") | 2 | 7 | | 0x13 | Read GB[\[1\]](https://n64brew.dev/wiki/Joybus_Protocol#cite_note-luigiPrinter-1) | N64, GB | [64GB Cable](https://n64brew.dev/wiki/64GB_Cable?action=edit&redlink=1 "64GB Cable (page does not exist)")
, MBC4 GB Cartridge | 3 | 33 | | 0x14 | Write GB[\[1\]](https://n64brew.dev/wiki/Joybus_Protocol#cite_note-luigiPrinter-1) | N64, GB | [64GB Cable](https://n64brew.dev/wiki/64GB_Cable?action=edit&redlink=1 "64GB Cable (page does not exist)")
, MBC4 GB Cartridge | 35 | 1 | | 0x14 | Read GBA | GC, GBA | GBA | 3 | 33 | | 0x15 | Write GBA | GC, GBA | GBA | 35 | 1 | | 0x16-0x2F | Unknown | | | ? | ? | | 0x30 | Force Feedback | GC | Steering Wheel | ? | ? | | 0x31-0x3F | Unknown | | | ? | ? | | 0x40 | Short Poll | GC | Controller | ? | 8 | | 0x41 | Read Origin | GC | Controller | ? | ? | | 0x42 | Calibrate | GC | Controller | ? | ? | | 0x43 | Long Poll | GC | Controller | 3 | 10 | | 0x44-0x53 | Unknown | | | ? | ? | | 0x54 | Poll | GC | Keyboard | ? | ? | | 0x55-0xFE | Unknown | | | ? | ? | **(1)** _Real Time Clock_ Command Details =============== ### 0xFF - Reset / Info While identical to 0x00 in what data is returned to the console, if a device has an intended reset function, it should be performed when this command is sent. The N64 controller for example will reset the internal position of its analog stick to (0, 0), essentially recalibrating it. Other devices may have similar functionality, or none at all, but either way they should still send back the same data as if the 0x00 command was sent. ### 0x00 - Info This command requests information about the device. Always contains a 2-byte identifier and one byte of extra data. | Identifier | Console | Device | Third byte (if empty, no details known) | | --- | --- | --- | --- | | 0x0000 | N64 | None (no device connected) | Always 0x00 | | 0x0500 | N64 | Controller | Bit 0 and 1 (mask 0x3) are a 2-bit value:

* 0: never seen in testing

* 1: an accessory is currently inserted
* 2: an accessory is currently not inserted
* 3: a new accessory was inserted since the last Info command

(see below for more information)

Bit 2 (mask 0x4): checksum error in previous command | | 0x0500 | N64 | Dance Pad | | 0x0001 | N64 | VRU | | 0x0200 | N64 | Mouse | | 0x0002 | N64 | Randnet Keyboard | | 0x0003 | N64 | 64GB Cable, MBC4 GB Cartridge | | 0x0080 | N64 | 4 Kbit EEPROM | Bitfield: 0x80=Write in progress | | 0x00C0 | N64 | 16 Kbit EEPROM | Bitfield: 0x80=Write in progress | | 0x2004**(1)** | N64 | Densha de Go | | 0x0004 | GBA | Game Boy Advance | | 0x0900**(1)** | GC | Controller | | 0x8800**(1)** | GC | Receiver | | 0x8B10**(1)** | GC | Wavebird | | 0x0800**(1)** | GC | Steering Wheel | | 0x0802**(1)** | GC | Keyboard | | 0x0808**(1)** | GC | Dance Mat | **(1)** _Requires verification yet._ #### Controller status byte The status byte for the N64 controller allows to detect whether an accessory is inserted in the controller or not. The status values 1 and 2 map to the intuitive meanings of "pak inserted" and "pak absent" (they basically map to the level status of the DETECT pin). Instead, status value 3 is special: it is basically returned the first time the Info packet is sent after a pak was newly inserted; another way of saying it is that the status value becomes 3 on the low-to-high transition of the DETECT pin, and stays at 3 until a read of the state is made by the N64. After 3 is returned, assuming the pak is not removed, next reads will always return 1. From a software perspective, this means that a software cannot miss the change of an accessory; if the software sees the value 3, it means that a new accessory is inserted, even if the previous value was 1 (in which case, the previous accessory was removed and a new one is inserted). Instead, readings of 1 mean that the same accessory is still inserted. ### 0x01 - Controller State The response for this command is always the same length of 4 bytes (32 bits), but the data it represents will change depending on the type of controller device. A Controller Stop Bit is always included after the response bytes. _Note that in the following waveform diagrams, any LOW bit should send a Zero Bit as described in the [operation section](https://n64brew.dev/wiki/Joybus_Protocol#Operation) above. Do not hold the line LOW constantly._ #### Standard Controller The most common is the standard N64 controller. In which case, the data is the current state of the inputs from the controller: the 14 buttons, the current position of the analog stick, and the reset (RST) bit. The default state of buttons and RST is Zero. If a button is pressed, it becomes One. If LT, RT, and Start are pressed, RST is One, Start becomes Zero, and the analog stick's position is reset to (0, 0). Each axis of the analog stick is a [Two's Complement](https://en.wikipedia.org/wiki/Two%27s_complement "w:Two's complement") byte, giving a decimal value ranging from -128 to +127, even though a standard controller may not reach the full range due to physical limitations. The bit order from left to right, of the response data, is as follows:  [](https://n64brew.dev/wiki/File:Controller-response.svg) #### Mouse The mouse follows a similar format to the standard controller. However, it only uses the A and B buttons, and the x/y axis bytes represent the relative position of the mouse.  [](https://n64brew.dev/wiki/File:Mouse-response.svg) #### Densha de Go  [](https://n64brew.dev/wiki/File:Train-controller-diagram.jpg) Input diagram of Densha de Go! train controller. The game Densha de Go! used an exclusive train controller instead of the standard tri-wing controller. It had five buttons, and two multi-position levers. Refer to the right image for corresponding positions and values for each lever.  [](https://n64brew.dev/wiki/File:Train-controller-response.svg) _(`Acc` refers to the accelerator lever)_ #### Other Devices While more devices exist that use this command, specific response information is not known at this time. ### 0x02 - Read Controller Accessory | | | | | | --- | --- | --- | --- | | TX (3 bytes) | | RX (33 bytes) | | | Bytes | Description | Bytes | Description | | 0 | Command code 0x02 | 0-31 | 32 bytes read from the accessory | | 1-2 | 16-bit address to read, aligned to 32 bytes

(plus a special CRC in the low 5 bits) | 32 | CRC8 of the read bytes | This command is used to read data from the controller accessory (pak) connected to the bottom of the controller. The protocol allows to perform reads of blocks of exactly 32-bytes, from a 16-bit address. Blocks can only be read from aligned addresses. The address space is accessory-dependent. The controller pak for instance maps the 32 KiB of SRAM to addresses 0x0000-0x7FFF; the rumble pak uses only addresses 0x8000-0xBFFF as a single bit register to start or stop the motor. Please refer to the documentation of each accessory for more information on its address space. Since the 16-bit address must be 32-byte aligned, the lower 5 bits are used for a checksum of the address itself, which is explained [in the next section](https://n64brew.dev/wiki/Joybus_Protocol#Address_Checksum) . This checksum is verified by the controller when the command is received, and is probably used as a way to verify that no bits were corrupted on the serial line. The controller will reply with the 32 bytes read from the accessory. In addition to that, a 8-bit CRC over the 32 bytes ([also explained below](https://n64brew.dev/wiki/Joybus_Protocol#Data_CRC) ) is appended to the reply, again probably to protect transfers from serial corruptions. The software on the N64 can verify if the CRC matches to make sure there was no corruption on the wire. The accessory must begin responding with data within about 62.5 microseconds. When a pak is not installed, this command will still complete successfully from a joybus perspective (so there will be no error signaled [in the PIF packet](https://n64brew.dev/wiki/PIF-NUS#RX_byte:_special_flags "PIF-NUS") (the command would fail only if the controller was not connected). In this case, the whole reply is made of zeroes, and the CRC byte is instead 0xFF (which is the complement of the correct CRC). From a software perspective, whenever the CRC is complemented, it means that the accessory is absent. When a pak is just installed, reads are still nullified as-if the pak is absent. Basically, the controller will not allow reads (or writes, see below) until the software on the N64 sends the Info command once to identify the presence of the pak; as explained in the Info command description above, the status byte in this first request will be 3 (to mean that a new pak was inserted). After this command is completed, it will be possible to perform reads (o writes) to the accessory. If the pak receives a read command with an invalid address checksum (either because it was generated invalid by the N64 or because it did get corrupted on the wire), it will behave as if the pak is disconnected, that is, return a reply made of zeroes with a complemented CRC (0xFF). ### 0x03 - Write Controller Accessory | | | | | | --- | --- | --- | --- | | TX (35 bytes) | | RX (1 bytes) | | | Bytes | Description | Bytes | Description | | 0 | Command code 0x03 | 0 | CRC8 of the written data | | 1-2 | 16-bit address to read, aligned to 32 bytes

(plus a special CRC in the low 5 bits) | | | | 3-34 | Data to be written to the device | | | This command is used to writes data from the controller accessory (pak) connected to the bottom of the controller. The behavior of this command is completely specular to command 0x02 Read Controller Accessory. Please refer to the command above for the detailed description of all the behaviors. It is worth notice that the CRC8 protecting the integrity of the transfer is computed by the controller and sent back to the N64. So if a corruption happens on the joybus wires, the write will still go through, but the N64 will then be able to detect that the corruption happened, and re-issue the write to try again writing the correct data. When the pak is absent, the write command behaves like the read command: the command completes successfully from a joybus perspective, and the only indication that nothing really happened is that the CRC8 byte in the reply will be complemented (compared to the correct one). ### 0x04 - Read EEPROM Block If EEPROM is available, this command can be used to read 8 bytes of save data at a time. The command (\`0x04\`) is followed by which block of EEPROM to read from: | Identifier | Capacity | Available Blocks | | --- | --- | --- | | 0x0080 | 4 Kibibits (512 bytes) | 0-63 | | 0x00C0 | 16 Kibibits (2048 bytes) | 0-255 | In a 512 byte EEPROM, the top two bits of block number are ignored: blocks 64-255 are repeats of the first 64 ### 0x05 - Write EEPROM Block If EEPROM is available, this command can be used to write 8 bytes of save data at a time. See "Read EEPROM Block" above for block addressing rules. The 8 bytes of data to write are included in the command immediately following the "block" byte. Notice that the completion of the joybus command does not imply that the EEPROM was written. The EEPROM would just latch the 8 bytes of data, and then proceed performing the actual write in background, after the joybus command was already completed. It may take up to 6 milliseconds for the data to be written to EEPROM. The 1-byte joybus reply specifies whether the write has started correctly or not. If a EEPROM write is requested while a previous one is still being performed, the EEPROM will reply with 0x80 (busy), and 0x00 otherwise. Real EEPROM data storage has inherent unreliability that requires special consideration: EEPROM chips may become corrupted spontaneously due to uncontrollable factors such as power failure during write or memory cell fatigue causing write failures. It is recommended to use a checksum or parity bits in each EEPROM block to check that the data has not been corrupted. EEPROMs used by retail cartridges are rated for up to 100,000 write cycles and may only retain saved data for up to 10 years. Emulators and flashcarts now provide reliable storage for EEPROM commands that do not suffer from these issues. ### 0x06 - Real-Time Clock Info In order to permit using both an EEPROM and the RTC at the same time, the RTC does not respond to command 0, and has its own identify command. If RTC (Real-Time Clock) is available, this command will return a 2 byte identifier (0x0010) and a status byte. If RTC is not available, this command will respond with three bytes containing zero. | Status Byte Bit | Meaning | Description | | --- | --- | --- | | 7 (0x80) | Stopped | Clock is halted, and it is safe to write to block 2 | | 6-2 (0x7C) | Unused | These bits have never been seen set | | 1 (0x02) | Crystal failure | If this bit is set, the crystal is not working | | 0 (0x01) | Battery failure | If this bit is set, the supply voltage of the RTC became too low | ### 0x07 - Read Real-Time Clock Block The RTC read command sends 2 bytes (1 for the command, 1 for block selection) and responds with 8 bytes of block data plus a status byte that has the same meaning as the "RTC Info" command's status byte. The RTC has three blocks each containing 8 bytes of data: | Block | Purpose | Comment | | --- | --- | --- | | 0 | Control registers | Determines the current clock "mode" | | 1 | Unused | 8 bytes of battery-backed SRAM | | 2 | Date/Time Fields | The current date and time in binary-coded decimal | | 3 | Empty | Always 0 | The top six bits of the block number are ignored: reading from or writing to blocks above the first 4 will wrap within the first four. All RTC reads also include a status byte with the same meaning as the "RTC Info" command's status byte. #### RTC Block 0 (Control Registers) Byte 0: | Bit number | Mode | Description | | --- | --- | --- | | 7-2 (0xFC) | Empty | Always 0 | | 1 (0x02) | Write Protect | Write protects field 2 (RTC) | | 0 (0x01) | Write Protect | Write protects field 1 (NVRAM) | Byte 1: | Bit number | Mode | Description | | --- | --- | --- | | 7 (0x80) | Unknown | Exists, changeable, no visible function | | 6-3,0 (0x79) | Empty | Always 0 | | 2,1 (0x06) | RTC Stop | If either bit is set, stops RTC from counting | Bytes 2, 3, 6, and 7 always read back as 0. Bytes 4 and 5 contain 7- and 6- bit numbers respectively that can be updated but have no visible function. #### RTC Block 1 (SRAM) RTC block 1 is not used by Animal Forest, and is not implemented by emulators or flashcarts. #### RTC Block 2 (Date/Time) RTC block 2 updates once per second with 8 bytes of data representing the current date and time. The fields are encoded using packed binary-coded decimals: | Byte | Purpose | Description | | --- | --- | --- | | 0 | Seconds | (0-59) | | 1 | Minutes | (0-59) | | 2 | Hours | (0-23) + 0x80 | | 3 | Day of Month | (1-31) | | 4 | Day of Week | (0-6) Sunday - Saturday | | 5 | Month | (1-12) | | 6 | Last Two Digits of Year | (0-99) | | 7 | Centuries since 1900 | (0-1) | #### RTC Block 3 (Empty) RTC block 3 always reads back all zeroes. ### 0x08 - Write Real-Time Clock Block The RTC write command sends 10 bytes (1 for the command, 1 for block selection, and 8 for the block data) and responds with a status byte that has the same meaning as the "RTC Info" command's status byte. The block data to write matches the same format as the block read responses in "Read RTC Block" above. The most-compatible routine for setting the time with the Joybus Real-Time Clock is: 1. Send "Write RTC Block" command to Control Registers (Block 0) with write protect disabled and clock stopped (0x0004...). 2. Wait 20 milliseconds for RTC block to write. 3. Send "Read RTC Status" command, check for "Stopped" (0x80) status: if stopped, proceed; otherwise abort. 4. Send "Write RTC Block" command to Date/Time Fields (Block 2) with new time to set. 5. Wait 20 milliseconds for RTC block to write. 6. Send "Write RTC Block" command to Control Registers (Block 0) with write protect enabled and clock running (0x0300...). 7. Poll "RTC Read Status" command until "Running" (0x00) status. 8. Wait 500 milliseconds before sending "RTC Read Block" command to Date/Time Fields (Block 2) Emulator and flashcart support for writing to the Real-Time Clock is not guaranteed: * Most emulators will ignore RTC write commands and always use the host system's local time. * EverDrive 64 3.0 and EverDrive64 X7 will ignore Joybus RTC write commands; date/time must be set through the Everdrive OS. * 64drive hw2 supports Joybus RTC writes, but requires a 500 millisecond delay after setting the time in order to read it back correctly. For homebrew, it is recommended to test whether the RTC implementation supports the write command to set the proper expectation with players. To do this: 1. Read the current RTC time. 2. Write a different time to the RTC. 3. Read the new RTC time. 4. If the new time is within a second of what was written: write back the original time, success; otherwise failure. ### 0x13 - Read from Game Boy Pak Works identically to [0x02 - Read Controller Accessory](https://n64brew.dev/wiki/Joybus_Protocol#0x02_-_Read_Controller_Accessory) , down to the address and data CRC, except that it reads from the Game Boy Pak which contains a MBC4 memory controller which supports JoyBus communication through the Nintendo 64 Controller port. ### 0x14 - Write to Game Boy Pak Similar to the previous command, this works identically like [0x03 - Write Controller Accessory](https://n64brew.dev/wiki/Joybus_Protocol#0x03_-_Write_Controller_Accessory) , except that it writes to the Game Boy Pak. Checksums ========= There are two different checksums used in some of the official commands. The first is an 8 bit CRC, and the other uses eleven different XOR values based on the position of each set bit. While the former is used for large chunks of data, the latter is used to check the 11 bit address for where that data is written to or read from. These checksums are used to ensure data integrity. The address checksum, which is 5 bits long, is always a part of the communication sent from the console to the device, just after the 11 bit address. ### Data CRC The 8 bit CRC known to be used in the controller accessory read/write commands, and the GBA read/write commands. It uses a standard CRC8 function, with a seed (initial value) of 0x00, and 0x85 for the polynomial. A CRC polynomial is a mathematical way to say that a particular number is used as a mask, or to be applied to the working CRC byte using an XOR operation. The mathematical form can be expressed as x 7 + x 2 + x 0 {\\displaystyle {\\displaystyle x^{7}+x^{2}+x^{0}}}  . In every known case, the connected device is always the side which generates this CRC. For read commands, it reads 32 bytes of data from a particular address, calculates the CRC, and sends all 32 bytes plus the CRC byte back to the console. For write commands, it calculates the CRC from the 32 bytes sent by the console, sends the CRC byte back, and performs the write operation. The order in which it does this may differ for different devices. For write commands, the device has a window of approx 62.5 microseconds after the console stop bit, to begin sending the CRC byte. ### Address Checksum When the console wishes to read data from, or write data to, a particular address on the connected device, it will send the top 11 bits of a 16 bit address, plus a 5 bit checksum. This means these operations must be done in 32 byte chunks, as the lower 5 bits of the address are assumed to be 0, to make room for the checksum. _**By definition, this checksum is not a CRC**_, because it doesn't use a cyclic code (as in, there is no bit shifting used), and the XOR value is not constant. The 5 bit checksum is calculated using the following table. The working checksum is initially set to zero. For each bit of the 11 address bits, starting at the upper-most bit, if the bit is set (is 1), then XOR the corresponding byte to the working checksum. If the bit is clear (is 0), do nothing and move onto the next bit. | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 0x01 | 0x1A | 0x0D | 0x1C | 0x0E | 0x07 | 0x19 | 0x16 | 0x0B | 0x1F | 0x15 | If the resulting checksum matches the one provided by the console, the address is valid. _The process as pseudocode:_[\[2\]](https://n64brew.dev/wiki/Joybus_Protocol#cite_note-pseudoAddress-2) for bits 15 -> 5 if the bit is set xor the checksum with the corresponding value in the above table References ---------- 1. ↑ [1.0](https://n64brew.dev/wiki/Joybus_Protocol#cite_ref-luigiPrinter_1-0) [1.1](https://n64brew.dev/wiki/Joybus_Protocol#cite_ref-luigiPrinter_1-1) LuigiBlood (2019). [Reverse enginnering the unreleased GameBoy Printer COLOR](https://luigiblood.tumblr.com/post/187348407478/reverse-enginnering-the-unreleased-gameboy-printer) . 2. [↑](https://n64brew.dev/wiki/Joybus_Protocol#cite_ref-pseudoAddress_2-0) joeldipops (2020). [Nintendo 64 Accessory Reference](https://github.com/joeldipops/TransferBoy/blob/master/docs/TransferPakReference.md#memory-addresses-and-checksum) . Retrieved from "[https://n64brew.dev/wiki/Joybus\_Protocol?oldid=5787](https://n64brew.dev/wiki/Joybus_Protocol?oldid=5787) " --- # Controller Pak - N64brew Wiki [](https://n64brew.dev/wiki/Controller_Pak#) Controller Pak ============== The Controller Pak is a memory card accessory, similar to memory cards used by the PlayStation consoles. Certain games allow saving of game files to the Controller Pak. Just like the [Rumble](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak") and [Transfer Paks](https://n64brew.dev/wiki/Transfer_Pak "Transfer Pak") , it plugs into the bottom of the Nintendo 64 [controller](https://n64brew.dev/wiki/Controller "Controller") . Some games are unable to save game files without the pak, however the games are still playable. The Controller Pak is also useful for carrying game saves between consoles and exchanging saves between multiple paks using a game's save manager. [![](https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Nintendo-64-Controller-Pak.jpg/960px-Nintendo-64-Controller-Pak.jpg)](https://n64brew.dev/wiki/File:Nintendo-64-Controller-Pak.jpg) Original Controller Paks by Nintendo have a total of 32 KiB of memory available for saves, and use a custom (FAT-inspired) filesystem to allocate data from different games, implemented in software by the Nintendo SDK. Some third-party pak (such as by Datel) feature more memory (eg. 256 KiB or 512 KiB), via a bank switching system. This system was supported by the official Nintendo SDK, making such paks work transparently on all commercial games, even though Nintendo itself did not produce or sold any multi-bank controller pak. Contents -------- * [1 Hardware](https://n64brew.dev/wiki/Controller_Pak#Hardware) * [1.1 Components](https://n64brew.dev/wiki/Controller_Pak#Components) * [1.2 Operation](https://n64brew.dev/wiki/Controller_Pak#Operation) * [2 Software](https://n64brew.dev/wiki/Controller_Pak#Software) * [3 File System](https://n64brew.dev/wiki/Controller_Pak#File_System) Hardware -------- ### Components The primary component that makes up the Controller Pak is a 28pin, 32,768-byte [SRAM](https://en.wikipedia.org/wiki/Static_random-access_memory "wikipedia:Static random-access memory") chip, backed by a 3.3V battery located on the opposite side of the PCB. The battery supplies a low current to the SRAM when either not plugged into a controller or when the console is turned off, preventing the SRAM from losing data. While the controller's accessory port supports a 16 bit address, the Controller Pak leaves the 16th bit disconnected, as the SRAM only uses 15 bits for addressing. At least some, if not all, of the official Controller Paks used the [LH52256CVN](https://n64brew.dev/wiki/File:LH52256CVN.pdf "File:LH52256CVN.pdf") SRAM chip manufactured by Sharp. Alternative SRAM chips do exist. Any chip that follows the same pinout and runs on 3.3V should be compatible. One example is the AS6C62256-55PCN made by Alliance Memory. While not produced anymore, Toshiba's TC55257DFL-85V and Chiplus' CS18LV02563 are also compatible. ### Operation The pins of the SRAM chip are mapped 1 to 1 with the [accessory port's pinout](https://n64brew.dev/wiki/Controller#Accessory_Port "Controller") , except for pin A15 (the 16th address bit). On OEM paks (official ones produced by Nintendo), pin A15 is connected to pin CE2 of the SRAM chip (Chip Enable 2), causing the SRAM to go into an idle mode when asserted. On some third-party paks, instead, pin A15 is completely disconnected.  [](https://n64brew.dev/wiki/File:20220327_205835.jpg) Example of a third-party (clone) controller pak where pin A15 (third from right) is disconnected The Chip Enable pin is inverted on the PCB using a transistor, meaning that the pin must be pulled HIGH to enable the chip. Write Enable and Output Enable should be pulled LOW to enable their respective function. Refer to the SRAM's datasheet for how to read/write data.  [](https://n64brew.dev/wiki/File:AccessoryPortPinout.png) Pinout of the accessory port connector with respect to the pak's orientation Software -------- When accessing the Controller Pak via the [joybus](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") protocol, the accessory presents the following memory map: | | | | --- | --- |OEM (official) Controller Pak memory map | Address | Description | | 0x0000 - 0x7FFF | SRAM: actual contents of the pak | | 0x8000 - 0xFFFF | Writes are ignored, reads return 0 | The Nintendo SDK (libultra) is designed to support a bank switching system, even though only third-party companies (like Datel) ever produced such paks. This is the memory map: | | | | --- | --- |Multi-bank (eg: Datel) Controller Pak memory map | Address | Description | | 0x0000 - 0x7FFF | Read/write current bank (32 KiB) | | 0x8000 - 0xFFFF | Bank-switching: the LSBs of the written word set the bank number.

If the bank number does not exist, the behavior is not fully investigated but it appears to just mirror. Eg: for a 4-bank cpak, writing 5 selects bank 1. Reads return 0. | There is no provision to read the number of available banks. Software is required to either ask that to the user, or perform a discovery process (eg: switching banks and checking contents until the first bank is seen again). Notice that this memory map is also compatible with the original one; in fact, ignoring the writes in range 0x8000-0xFFFF can be interpreted as "always switching to bank 0 which is the only available bank". Some clone Controller Paks instead are designed with A15 pin disconnected, exposing the following memory map: | | | | --- | --- |Third-party (clone) Controller Pak memory map | Address | Description | | 0x0000 - 0x7FFF | SRAM: actual contents of the pak | | 0x8000 - 0xFFFF | Mirror of the contents | Notice that this memory map is actually **not** compatible with the bank switching system. In fact, the Nintendo SDK will performa a bank switch by writing the bank number to 0x8000, but this will cause the bank number to be treated as data and written at 0x0000. Luckily, the first block (called "label area") is unused in the [standard filesystem](https://n64brew.dev/wiki/Controller_Pak/Filesystem "Controller Pak/Filesystem") which might explain why the designers of these clone PCBs never realized the mistake, File System ----------- Main article: [Controller Pak/Filesystem](https://n64brew.dev/wiki/Controller_Pak/Filesystem "Controller Pak/Filesystem") Save data management on the Controller Pak is facilitated through a simple proprietary filesystem. In all official and unofficial Controller Paks, there are 32,768 bytes of SRAM available, which is split into 256-byte sectors referred to as "pages". This allows for a maximum capacity of 128 pages, 5 of which are reserved for the filesystem data, and the remaining 123 for user software. Retrieved from "[https://n64brew.dev/wiki/Controller\_Pak?oldid=5635](https://n64brew.dev/wiki/Controller_Pak?oldid=5635) " --- # 64DD - N64brew Wiki [](https://n64brew.dev/wiki/64DD#) 64DD ==== The **64DD** is a magnetic disk drive peripheral for the [Nintendo 64](https://n64brew.dev/wiki/Nintendo_64 "Nintendo 64") game console developed by Nintendo. It was announced in 1995, prior to the Nintendo 64's 1996 launch, and after numerous delays was released only in Japan on December 1, 1999. The "64" references both the Nintendo 64 console and the 64 MB storage capacity of the disks, and "DD" is short for "disk drive" or "dynamic drive". The 64DD was codenamed "Leo" during development. Physical Memory Map ------------------- The following is the physical memory map for the 64DD hardware: | Address Range | | Name | Description | | --- | --- | --- | --- | | 0x05000000 | 0x050003FF | ASIC\_C2\_BUFF | C2 Sector Buffer | | 0x05000400 | 0x050004FF | ASIC\_SECTOR\_BUFF | User Sector Buffer | | 0x05000500 | 0x0500057F | Registers | [64DD interface](https://n64brew.dev/wiki/64DD#64DD_interface) | | 0x05000580 | 0x050005BF | MSEQ\_RAM\_ADDR | Micro Sequencer RAM | | 0x06000000 | 0x063FFFFF | DDROM | 64DD IPL ROM | It is usually accessed under the KSEG1 memory segment as uncached direct access to those registers. 64DD interface -------------- Main article: [64DD/Interface](https://n64brew.dev/wiki/64DD/Interface "64DD/Interface") The 64DD interface is made of several 16-bit memory-mapped registers allowing the CPU to control the 64DD hardware. 64DD commands ------------- Main article: [64DD/Commands](https://n64brew.dev/wiki/64DD/Commands "64DD/Commands") One of the main use of the 64DD is through commands. Commands are a single byte, with sometimes a 16-bit argument passed as data. Retrieved from "[https://n64brew.dev/wiki/64DD?oldid=5543](https://n64brew.dev/wiki/64DD?oldid=5543) " --- # Category:Game Jams - N64brew Wiki [](https://n64brew.dev/wiki/Category:Game_Jams#) Category:Game Jams ================== Pages in category "Game Jams" ----------------------------- The following 6 pages are in this category, out of 6 total. ### N * [N64brew Game Jam 2020](https://n64brew.dev/wiki/N64brew_Game_Jam_2020 "N64brew Game Jam 2020") * [N64brew Game Jam 2021](https://n64brew.dev/wiki/N64brew_Game_Jam_2021 "N64brew Game Jam 2021") * [N64brew Game Jam 2022](https://n64brew.dev/wiki/N64brew_Game_Jam_2022 "N64brew Game Jam 2022") * [N64brew Game Jam 2023](https://n64brew.dev/wiki/N64brew_Game_Jam_2023 "N64brew Game Jam 2023") * [N64brew Game Jam 2024](https://n64brew.dev/wiki/N64brew_Game_Jam_2024 "N64brew Game Jam 2024") * [N64brew Game Jam 2025](https://n64brew.dev/wiki/N64brew_Game_Jam_2025 "N64brew Game Jam 2025") Retrieved from "[https://n64brew.dev/wiki/Category:Game\_Jams?oldid=1664](https://n64brew.dev/wiki/Category:Game_Jams?oldid=1664) " --- # Getting Started - N64brew Wiki [](https://n64brew.dev/wiki/Getting_Started#) Getting Started =============== If you are not there already, consider [joining the N64brew discord server](https://discord.gg/P8THxFEaJz) ! We are very friendly :) Contents -------- * [1 Modern, recommended approach to N64 homebrew development](https://n64brew.dev/wiki/Getting_Started#Modern,_recommended_approach_to_N64_homebrew_development) * [1.1 Writing software](https://n64brew.dev/wiki/Getting_Started#Writing_software) * [1.1.1 Quick Start](https://n64brew.dev/wiki/Getting_Started#Quick_Start) * [1.1.2 The libdragon SDK](https://n64brew.dev/wiki/Getting_Started#The_libdragon_SDK) * [1.1.3 tiny3d](https://n64brew.dev/wiki/Getting_Started#tiny3d) * [1.1.4 Pyrite64](https://n64brew.dev/wiki/Getting_Started#Pyrite64) * [1.2 Running your ROM](https://n64brew.dev/wiki/Getting_Started#Running_your_ROM) * [1.2.1 Emulators](https://n64brew.dev/wiki/Getting_Started#Emulators) * [1.2.2 Flashcarts](https://n64brew.dev/wiki/Getting_Started#Flashcarts) * [1.3 Understanding the hardware](https://n64brew.dev/wiki/Getting_Started#Understanding_the_hardware) * [1.4 Tutorials](https://n64brew.dev/wiki/Getting_Started#Tutorials) * [1.5 Examples](https://n64brew.dev/wiki/Getting_Started#Examples) * [1.6 Making assets (for artists)](https://n64brew.dev/wiki/Getting_Started#Making_assets_(for_artists)) * [1.6.1 Textures](https://n64brew.dev/wiki/Getting_Started#Textures) * [1.6.2 Images](https://n64brew.dev/wiki/Getting_Started#Images) * [1.6.3 3D models](https://n64brew.dev/wiki/Getting_Started#3D_models) * [1.6.4 Music](https://n64brew.dev/wiki/Getting_Started#Music) * [2 Frequently Asked Questions](https://n64brew.dev/wiki/Getting_Started#Frequently_Asked_Questions) * [2.1 What about libultra?](https://n64brew.dev/wiki/Getting_Started#What_about_libultra?) * [2.2 Is testing on hardware required?](https://n64brew.dev/wiki/Getting_Started#Is_testing_on_hardware_required?) * [2.3 Can I do bare metal programming on N64 in assembly?](https://n64brew.dev/wiki/Getting_Started#Can_I_do_bare_metal_programming_on_N64_in_assembly?) * [3 Other Discord Servers](https://n64brew.dev/wiki/Getting_Started#Other_Discord_Servers) * [4 Historical, Abandoned, Discouraged](https://n64brew.dev/wiki/Getting_Started#Historical,_Abandoned,_Discouraged) Modern, recommended approach to N64 homebrew development -------------------------------------------------------- This describes the main building blocks and tools that are relevant today for making Nintendo 64 homebrew, games or software that runs on the Nintendo 64 console. ### Writing software Development for the N64 is typically done in the C programming language. C++ can also be used. You should be familiar with C. If you're new to C, you'll likely have an easier time learning the language on a PC first before tackling N64 development. Unless you use Pyrite64 (see below) you should be ready to write a game engine from scratch. #### Quick Start Build your first N64 homebrew ROM: 1. Install libdragon. Refer to [its wiki](https://github.com/DragonMinded/libdragon/wiki/Installing-libdragon) for instructions. 2. Install [ares](https://ares-emu.net/) . (on Linux you should [build from source](https://github.com/ares-emulator/ares/wiki/Build-Instructions-For-Linux) ) 3. Download and build [this Hello World example](https://github.com/Dragorn421/n64-hello-world) (see the README for instructions). 4. Run the Hello World example in ares. This should take around 10-30 minutes. For more details on libdragon, ares, and more, read on! #### The libdragon SDK A [SDK](https://en.wikipedia.org/wiki/Software_development_kit) facilitates development by providing libraries that abstract away the hardware and even contain features built on top. [libdragon](https://n64brew.dev/wiki/Libdragon "Libdragon") is an open-source SDK for N64 homebrew with many features and actively developed. libdragon is split between two branches: "trunk" and "preview". "trunk" APIs are guaranteed to not break with libdragon updates. This guarantee does not apply to the "preview" branch. The preview branch has more features. See the [libdragon README](https://github.com/DragonMinded/libdragon/blob/trunk/README.md) for details and a list of features on both branches. Refer to [the libdragon wiki](https://github.com/DragonMinded/libdragon/wiki/Installing-libdragon) for installation instructions. libdragon is extensively documented. You can read its documentation in its source code or [online (trunk branch)](https://libdragon.dev/ref/index.html) . #### tiny3d While libdragon (in its preview branch) implements OpenGL 1.1, it is hard to recommend to newcomers. If you want to make a 3D game, check out [tiny3d](https://github.com/HailToDodongo/tiny3d) which builds upon libdragon. tiny3d is a library for 3D rendering, featuring: * Mesh conversion and optimization tooling * Mesh rendering * Animated rigged meshes * Particles #### Pyrite64 [Pyrite64](https://hailtododongo.github.io/pyrite64/) is a game engine and editor for N64, building upon libdragon and tiny3d. In addition to what the libraries already provide, it also offers higher level features like assets management, physics, collisions, a node graph, scripting (in C++). This makes it much easier to bootstrap a game from scratch, as basically most of the engine-level code is already provided. If you are familiar with modern game engines like Unity or Godot, Pyrite is probably the smoother path for you. Don't expect a real Unity though: you will still be required to know C/C++ and do low-level programming here and there. ### Running your ROM #### Emulators A N64 emulator allows you to run a ROM on your PC. There are many N64 emulators, but for homebrew only two are worth mentioning: * [ares](https://ares-emu.net/) (the recommended go-to) * [gopher64](https://loganmc10.itch.io/gopher64) (less accurate than ares and lacking homebrew development features but faster) [ares](https://n64brew.dev/wiki/Ares "Ares") is the most accurate emulator and implements features to aid homebrew development (make sure to turn on its Homebrew Development Mode), and as such is the recommended go-to emulator. Other emulators are not accurate enough to run libdragon ROMs. #### Flashcarts A flashcart allows you to run a ROM on your Nintendo 64. Recommended flashcarts can be connected to a PC through USB for uploading a ROM to it directly, greatly improving iteration speed. * [SummerCart64](https://summercart64.dev/) * [EverDrive 64](https://krikzz.com/our-products/cartridges/ed64x7.html) Out of the two, the SummerCart64 is a better option as it has much faster USB transfer speed. Be wary of buying one too cheap from non-recommended sellers: they may have cheapened out on the components and the final product may not function well. Preferably buy it from one of the recommended sources, currently [Phenom Mod Store](https://store.phenommod.com/index.php?route=product/product&path=75&product_id=102) or through a group buy (check out our discord for that). The recommended tool on the PC side for interacting with flashcarts is [UNFLoader](https://github.com/buu342/N64-UNFLoader) . ### Understanding the hardware The main components of the N64 you have to know about are the CPU, RAM, RSP and RDP: * The [CPU](https://n64brew.dev/wiki/VR4300 "VR4300") executes code, driving the rest of the hardware. * The N64 has 4 MiB of [RAM](https://n64brew.dev/wiki/RDRAM "RDRAM") , or 8 MiB with an expansion pak. * The [RSP](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor") is a special CPU with support for vector instructions, for the purpose of processing audio and 3D geometry. Code running on the RSP is called "microcode" or "ucode". * The [RDP](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") is a chip for drawing rectangles and triangles to a framebuffer located in RAM. It is typically driven by the RSP. For more details on the N64 architecture, you can check out [this in-depth article](https://www.copetti.org/writings/consoles/nintendo-64/) . ### Tutorials * [N64Squid Libdragon Tutorial](https://n64squid.com/homebrew/libdragon/) - A tutorial series written by N64Squid about getting started with libdragon. * [The beginner's guide to making a game for the N64 using libdragon](https://www.youtube.com/watch?v=dJe6noy-WeI) - A video by James Lambert ### Examples libdragon and tiny3d come with examples in their respective examples folder. ### Making assets (for artists) Assets refer to textures, images, 3D models and music. #### Textures The N64 has a limited texture memory and special image formats that basically constrain textures applied to polygons to be either * maximum 32x64 pixels at 5-bit color depth per RGB channel (rgba16 format) * maximum 64x64 pixels with a palette of 16 colors (ci4 format) * maximum 64x128 pixels using 4-bit depth grayscale (i4 format) Note this list is not exhaustive, it merely highlights the memory limits and does not cover tricks such as multitexturing. For more details on N64 texture formats you can check out [this article](https://n64squid.com/homebrew/n64-sdk/textures/image-formats/) . Additionally texture dimensions are best kept as powers of 2s (e.g. 16, 32, 64) as that is a requirement for UV wrapping. #### Images If an image is not to be used as a texture to apply to a polygon but instead as a background image, a UI element or similar 2D piece, the texture memory limits no longer apply (libdragon can automatically split the image into chunks that fit the texture memory). #### 3D models For making 3D models, the [Blender](https://www.blender.org/) 3D modeling software is typically used, and the models exported to gltf for further conversion by dedicated tooling. If using tiny3d, the [fast64](https://github.com/Fast-64/fast64) add-on for Blender is also used in order to configure materials within N64 specs. #### Music The N64 doesn't have a dedicated sound/music chip, so there aren't really any restriction besides performance concerns. libdragon supports streamed music, so you can compose music in any tool you want and export to a wav or mp3 file. The file will then be processed by specific libdragon tooling. libdragon also supports xm for sequenced music, which takes a lot less cartridge space. Frequently Asked Questions -------------------------- ### What about libultra? [libultra](https://n64brew.dev/wiki/Libultra "Libultra") is the official (Nintendo-issued) SDK for the N64. You can choose to use it for making N64 homebrew, but the community has vastly shifted to using [libdragon](https://n64brew.dev/wiki/Libdragon "Libdragon") as it is more modern, open-source and unencumbered software, and has a ton of useful features. ### Is testing on hardware required? Yes, it is a good idea to test regularly on hardware (on a real N64 with a flashcart). Though most development can happen with testing on the [ares](https://n64brew.dev/wiki/Ares "Ares") emulator, performance (FPS) can currently not be assessed correctly on emulator alone. If you intend to make a N64 game, you should aim for it to work on hardware and also run at a decent framerate. Otherwise you would just be making a PC game with extra steps. If you don't have hardware, there is a channel dedicated to the purpose of hardware testing in our Discord (rom-testing). ### Can I do bare metal programming on N64 in assembly? Technically yes, but it is discouraged. While assembly is still the best (or only) option for 4th-gen consoles, N64 is extremely hard to configure at the hardware level. Even setting up a framebuffer on TV requires non-trivial register configuration with many pitfalls, without ever getting to RDP programming which has a very high complexity with many, many hazards. So unless you plan to do something extremely barebone (and are ready for complexity), prefer using C and libdragon. If you still want to try, the best set of examples is [Krom's repo](https://github.com/PeterLemon/N64) . Other Discord Servers --------------------- If you are looking for a N64-related discord server that does not focus on homebrew, you may be looking for one of these communities: * Nintendo64 - General N64 discussion, emulators, and tech support. [https://discord.gg/jqPzxUVVMJ](https://discord.gg/jqPzxUVVMJ) * Discord64 - General N64 development [https://discord.gg/sSkQTBpFhv](https://discord.gg/sSkQTBpFhv) * iQueBrew - Like us, but for the iQue Player specifically [https://discord.gg/SeZ3RPb](https://discord.gg/SeZ3RPb) * Project64 - The most popular N64 emulator [https://discord.gg/Cg3zquF](https://discord.gg/Cg3zquF) * Emulation Development - While not N64 specific, it contains a vast wealth of resources and knowledgeable people, even for N64 [https://discord.gg/dkmJAes](https://discord.gg/dkmJAes) * Hack64 - Hacking and modding N64 games [https://discord.gg/68WJ4TP](https://discord.gg/68WJ4TP) * N64 Vault - Hacking Goldeneye, Perfect Dark, Diddy Kong Racing, Jetforce Gemini, Mickey’s Speedway USA, Pokémon Snap, and Super Smash Bros 64 [https://discord.gg/RHNQgJq2](https://discord.gg/RHNQgJq2) * SM64 ROMHacks - Server dedicated to Super Mario 64 ROM hacking [https://discord.gg/BYrpMBG](https://discord.gg/BYrpMBG) * SM64 Port - Porting Super Mario 64 to PC [https://discord.gg/7bcNTPK](https://discord.gg/7bcNTPK) * N64 Decompilation Discord - Mostly catered towards Super Mario 64, but also has more general game reverse engineering [https://discord.gg/DuYH3Fh](https://discord.gg/DuYH3Fh) * OverKart 64 - Modding and hacking Mario Kart 64 [https://discord.gg/Zd4aJf6ENW](https://discord.gg/Zd4aJf6ENW) * Hylian Modding - Modding and hacking Legend of Zelda games [https://discord.gg/sZgH787](https://discord.gg/sZgH787) * Banjo's Backpack - Banjo Kazooie modding and hacking [https://discord.com/invite/Bm5wf9E](https://discord.com/invite/Bm5wf9E) * Paper Mario Modding - Hacking and modding Paper Mario [https://discord.gg/yTgKjaW](https://discord.gg/yTgKjaW) * ZeldaRET - Zelda Reverse Engineering Team (AKA Zelda Decompilation) [https://discord.zelda.deco.mp/](https://discord.zelda.deco.mp/) Historical, Abandoned, Discouraged ---------------------------------- For completeness, here you can find links to lists including (non recommended) alternatives to the above. * [List of SDKs](https://n64brew.dev/wiki/Category:Software_Development_Kits "Category:Software Development Kits") * [List of Nintendo 64 emulators](https://n64brew.dev/wiki/Category:Emulators "Category:Emulators") * [List of Nintendo 64 flashcarts](https://n64brew.dev/wiki/Flashcarts "Flashcarts") Retrieved from "[https://n64brew.dev/wiki/Getting\_Started?oldid=5918](https://n64brew.dev/wiki/Getting_Started?oldid=5918) " --- # Libultra - N64brew Wiki [](https://n64brew.dev/wiki/Libultra#) Libultra ======== **LibUltra** was distributed on the “Developer OS/Library” CDs, and is the official SDK used to make all commercial Nintendo 64 games during the console’s life. The final version of LibUltra is 2.0L. LibUltra is available for two toolchains: the SGI IRIX toolchain and the Windows GCC toolchain. **Modern developers should use the GCC version or they will get link errors.** The library files for the IRIX version are named `libultra.a`, `libultra_d.a`, and `libultra_rom.a`. The GCC version adds a “g”, and the names are `libgultra.a`, `libgultra_d.a`, and `libgultra_rom.a`. Contents -------- * [1 Installing libultra](https://n64brew.dev/wiki/Libultra#Installing_libultra) * [1.1 Linux with Modern SDK](https://n64brew.dev/wiki/Libultra#Linux_with_Modern_SDK) * [1.2 Windows 10 with WSL (Modern SDK)](https://n64brew.dev/wiki/Libultra#Windows_10_with_WSL_(Modern_SDK)) * [1.3 Windows 95, 98, or XP (32-Bit)](https://n64brew.dev/wiki/Libultra#Windows_95,_98,_or_XP_(32-Bit)) * [1.4 Linux or macOS with Wine](https://n64brew.dev/wiki/Libultra#Linux_or_macOS_with_Wine) * [1.5 Modern Windows with DOSBox](https://n64brew.dev/wiki/Libultra#Modern_Windows_with_DOSBox) * [1.6 Silicon Graphics](https://n64brew.dev/wiki/Libultra#Silicon_Graphics) * [2 Guides and Related Articles](https://n64brew.dev/wiki/Libultra#Guides_and_Related_Articles) Installing libultra ------------------- There are many environments and methods to install Libultra, pick whichever one you prefer. Originally, Libultra was only available on either SGI systems or for up-to 32-Bit Windows machines. Thankfully, through many dedicated members of our community, we have alternative solutions available; the most popular of these is Modern SDK by CrashOveride. Modern SDK is not a completely different SDK, it is exactly the same as Libultra, however it swaps out the old and crusty 16-bit compilers + tools for modern equivalents. Be aware that moving projects between both SDKs will require some tweaking, as Modern SDK uses linker scripts for assembling the final ROM over Nintendo’s proprietary specfile solution. Not only that, the more modern compiler might introduce new behaviour in your ROM if you used any sort of undefined behaviour. ### Linux with Modern SDK Instructions for this setup can be found [here](https://crashoveride95.github.io/n64hbrew/modernsdk/index.html) . ### Windows 10 with WSL (Modern SDK) Pretty much the same as the above, but with [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) in Windows 10. Use the same instructions linked in the Linux with Modern SDK section. ### Windows 95, 98, or XP (32-Bit) This is arguably the “easiest” method, but not necessarily the best as you’re stuck using really old tools and sharing files between machines. You can use either an actual old computer, or a virtual machine. We’re not going to cover how to install the OS, we’ll assume that’s already taken care of. Get a copy of the latest OS CD (OS/PC – v2.0L), toolkit (Developer Toolkit – v5.2), and developer tools (Developer Tools – v1.06). Burn them to a CD or mount the ISOs, and then install the OS first, then the toolkit, then finally the developer tools. While not required, highly recommended you get the Developer Documents v5.2 disk as well. If you prefer, you can use this [easy install CD](https://mega.nz/#!AOYDkSxA!MuAqt8iRBk0GGbaqaXVYB9tfZxsquKg5QkbCRL3VOLM) maintained by CrashOveride, which includes all four disks. You can also get SN64 or CodeWarrior, but these are really not recommended to be used because they’re incredibly finicky and even require special hardware to be used to their full extent. If you really insist, instructions for these are available [here](https://crashoveride95.github.io/n64hbrew/index.html) . ### Linux or macOS with Wine The official SDK should work fine with Wine for both Linux and macOS. ### Modern Windows with DOSBox If you really want to, you can use the official SDK (not the Modern one) on a modern 64-Bit Windows using DOSBox. Instructions for that can be found in [this comment](https://discord.com/channels/205520502922543113/587982213493030913/889688229329113139) by Magstriker on N64brew. You also have [this](https://discord.com/channels/205520502922543113/587982213493030913/1040441751091036160) upgrade to that, created by Agahnim. ### Silicon Graphics If you have a Silicon Graphics machine, the ultra64 website also has CDs for those devices. However SGI’s are not recommended for development because the hardware is becoming very old and hard to replace, and most community tools will not support these machines. Guides and Related Articles --------------------------- * [Libultra/Code segmentation guide](https://n64brew.dev/wiki/Libultra/Code_segmentation_guide "Libultra/Code segmentation guide") * [Libultra/Data Compression](https://n64brew.dev/wiki/Libultra/Data_Compression "Libultra/Data Compression") * [Libultra/Development Troubleshooting](https://n64brew.dev/wiki/Libultra/Development_Troubleshooting "Libultra/Development Troubleshooting") * [Libultra/Memory Allocation](https://n64brew.dev/wiki/Libultra/Memory_Allocation "Libultra/Memory Allocation") * [Libultra/Splitting Assets from Code](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code "Libultra/Splitting Assets from Code") * [Libultra/sgi-audio-tools](https://n64brew.dev/wiki/Libultra/sgi-audio-tools "Libultra/sgi-audio-tools") Retrieved from "[https://n64brew.dev/wiki/Libultra?oldid=5903](https://n64brew.dev/wiki/Libultra?oldid=5903) " --- # Todo - N64brew Wiki [](https://n64brew.dev/wiki/Todo#) Todo ==== Please focus on any red links currently on the [Main Page](https://n64brew.dev/wiki/Main_Page "Main Page") . (Note that I/O Interface pages are rather complex and use specific templates for register definitions) Additional needs include: * [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") needs expansion to include: * Boot behavior (entirely different operation than when console is running normally) * PIF RAM behavior under normal circumstances * Register definitions for the CP0 and CP1 (place in [VR4300](https://n64brew.dev/wiki/VR4300 "VR4300") ), [RSP#Registers](https://n64brew.dev/wiki/RSP#Registers "RSP") and [RDRAM#Registers](https://n64brew.dev/wiki/RDRAM#Registers "RDRAM") * [Translation lookaside buffer](https://n64brew.dev/wiki/Translation_lookaside_buffer?action=edit&redlink=1 "Translation lookaside buffer (page does not exist)") , of which the N64 has multiple, and may operate differently if in 64-bit or 32-bit addressing modes. * A page for hardware timings is desperately needed by emulator developers. Details should include: CPU pipeline cycles, all DMA and I/O timings for each I/O Interface, DMA Priority Arbitration, and possibly more. Information there will need to be intensely researched and verified, or else noted otherwise. * High quality (in-focus) scans/photographs are needed for all major hardware, including the front and back of: all motherboard revisions, paks, controllers/peripherals, and game cartridges. Place on appropriate pages. There may be a category created for all N64 games down the road, but that is a massive undertaking and thus is rather low priority right now. However, the [Game Pak](https://n64brew.dev/wiki/Game_Pak "Game Pak") page should still be worked on. #### Reminder When creating a page that should be included in a category, please add **\[\[Category:categoryName\]\]** _to the bottom of the page's source_ (where _categoryName_ is the name of the category). This will automatically include the page into the category's page list. Retrieved from "[https://n64brew.dev/wiki/Todo?oldid=4183](https://n64brew.dev/wiki/Todo?oldid=4183) " --- # Reality Coprocessor - N64brew Wiki [](https://n64brew.dev/wiki/RCP#) Reality Coprocessor =================== (Redirected from [RCP](https://n64brew.dev/wiki/RCP?redirect=no "RCP") ) The **Reality Coprocessor**, or **RCP**, is one of the two main processors on the Nintendo 64 board. Split into two components, the [Reality Signal Processor](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor") and [Reality Display Processor](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") , it cooperates with the other main processor, the [VR4300 CPU](https://n64brew.dev/wiki/VR4300 "VR4300") to draw 3D graphics, perform matrix calculations, and produce audio. The RCP is also connected to the onboard RDRAM, providing direct access to memory. It is located underneath the control deck shell's ventilation slots that are in front of the cartridge slot. [![N64 RCP Decapped](https://static.wikitide.net/n64wiki/thumb/e/e2/N64_RCP_Decapped.jpg/600px-N64_RCP_Decapped.jpg)](https://n64brew.dev/wiki/File:N64_RCP_Decapped.jpg) **N64 RCP Decapped** RCP Interface ------------- Main article: [RCP Interface](https://n64brew.dev/wiki/RCP_Interface?action=edit&redlink=1 "RCP Interface (page does not exist)") RCP is the core component of the N64 architecture. It is connected to all other peripherals (VR4300, ROM, RDRAM), and handles communications in all directions. For instance, VR4300 must go through RCP to access RDRAM. RCP is thus in charge of implementing the [full VR300 Memory Map](https://n64brew.dev/wiki/Memory_map "Memory map") . Please check [VR4300 interface](https://n64brew.dev/wiki/MIPS_R4300_interface "MIPS R4300 interface") for a detailed description of the bus between the CPU and the RCP at the electric level. RSP --- Main article: [Reality Signal Processor](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor") The **Reality Signal Processor**, or **RSP**, is the portion of the RCP responsible for matrix math, lighting calculations, clipping, shading, and other highly parallel graphics tasks, as well as audio processing. It is a programmable MIPS processor with a custom set of SIMD instructions for vectorized fixed point operations (exposed as COP2 -- a group of reserved instructions in the standard MIPS instruction set). The RSP is also able to directly drive the RDP (the hardware rasterizer) by accessing its registers, so that it can terminate the graphic pipeline by telling the RDP to draw triangles into the framebuffer. RDP --- Main article: [Reality Display Processor](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") The **Reality Display Processor**, or **RDP** is a large fixed-functionality internal block within RCP that is responsible for the final steps of video processing. The RDP receives instructions from the RSP (reality signal processor) and a rasteriser, texture unit (paired with 4KB of Texture memory), color combiner and blending unit complete each frame. The resulting data is then sent to the framebuffer in RAM before being fetched by the CPU and sent to the video encoder for display. Retrieved from "[https://n64brew.dev/wiki/Reality\_Coprocessor?oldid=5481](https://n64brew.dev/wiki/Reality_Coprocessor?oldid=5481) " --- # Reality Display Processor/Hazards - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#) Reality Display Processor/Hazards ================================= < [Reality Display Processor](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") RDP programming is full of hazards. It is by far the most complex part of the console and the silicon is extremely intolerant to wrong configurations. Most users trying to manually programming the chip without the help of a higher level library (such as libdragon's rdpq) often face issues such as rendering glitches or even RDP crashes. This page tries to list all known RDP hazards. Each hazard is assigned a unique code, to simplify identification and discussion. A metadata table for each hazard is also provided: | | | | --- | --- | | Key | Description | | Type | Categorize hazards depending on their effect. Possible values are:

* **Glitch:** the hazard will cause a minor graphic issue, that might even not be noticable
* **Corruption:** the hazard will cause a large and visible graphical that is hard to miss in most cases
* **Crash:** the RDP will crash. When crashed, it stops processing further commands. The only way to recover is to hard reset the console. | | Validator | Whether the Libdragon rdpq validator will detect and report this hazard, or not | | Libdragon | Whether Libdragon rdpq library shields programmers from hitting this hazard, or not | | Ares | Whether Ares will emulate the effects on this hazard matching the hardware behavior, or not | Contents -------- * [1 RH#001](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#RH#001) * [2 RH#002](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#RH#002) * [3 RH#003](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#RH#003) * [4 RH#004](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#RH#004) RH#001 ------ | Type | Validator | Libdragon | Ares | | --- | --- | --- | --- | | Corruption | Y | Y | N | Description Invalid access to the TEX1 / TEX1\_ALPHA combiner input in 1-Cycle Mode Effect TEX1 or TEX1\_ALPHA will contain the value of the next pixel in the same scanline, and garbage date on the last visible pixel on each scanline. Workaround If you need to sample two textures in the combiner, you must use the 2-Cycle Mode instead. Programming notes **Libdragon:** Trying to use TEX1 or TEX1\_ALPHA in a 1-step combiner causes a compile-time error, so it should not be possible to trigger this bug. RH#002 ------ | Type | Validator | Libdragon | Ares | | --- | --- | --- | --- | | Corruption | Y | Y | N | Description Invalid access to the TEX1 combiner input in the second cycle of 2-Cycle Mode Effect In the second cycle, the values of the TEX0 and TEX1 combiner inputs behave weirdly because of pipelining bugs: * TEX1 will contain the value of the next pixel of the _first texture_ in the same scanline, and garbage date on the last visible pixel on each scanline. * TEX0 instead will refer to the current pixel of the _second texture_ (that is, the same value you would access via TEX1 in the first cycle). | | | | | --- | --- | --- |Combiner inputs in 2-Cycle Mode | | TEX0 | TEX1 | | First cycle | Current pixel of the first texture | Current pixel of the second texture | | Second cycle | Current pixel of the second texture | Next pixel of the first texture

(or garbage on the last pixel of the scanline) | Workaround Restructure your combiner in a way not to access the second texture in the second cycle if possible, since there is no way to do so. Programming notes **Libdragon:** In rdpq, the slots have been renamed to avoid the confusing. In the second cycle, you must use TEX1 to refer to the current pixel of the first texture (just like you do in the first cycle), and TEX0\_BUG if you absolutely want to sample the first texture's next pixel. This tries to hide a bit the hazard, by at least uniformly mapping TEX0\* to the first texture and TEX1 to the second texture. Affected games **Monster Truck Madness** The text in the menu will appear corrupted if an emulator doesn't correctly reproduce the behavior of this hazard. RH#003 ------ | Type | Validator | Libdragon | Ares | | --- | --- | --- | --- | | Corruption | Y | Y | N | Description Invalid access to the COMBINED/COMBINED\_ALPHA combiner input in first combiner cycle Effect Accessing COMBINED or COMBINED\_ALPHA in the first combiner cycle (that is, the only cycle in 1-Cycle Mode, or the first cycle in 2-Cycle Mode) will provide the either output of the combiner of the previous pixel, or garbage data on the first pixel of the scanline. Workaround It is meaningless to use COMBINED/COMBINED\_ALPHA in the first cycle, as their goal is to access the output of the first cycle. Program your combiner correctly. Programming notes **Libdragon:** Trying to use COMBINED or COMBINED\_ALPHA in a the first step of a combiner causes a compile-time error, so it should not be possible to trigger this bug. RH#004 ------ | Type | Validator | Libdragon | Ares | | --- | --- | --- | --- | | Corruption | Y | Y | N | Description Invalid access to the SHADE/SHADE\_ALPHA combiner input in non-shaded primitive. Effect On non-shaded primitives (triangles without color components, or rectangles), the SHADE and SHADE\_ALPHA combiner inputs will contain garbage Workaround Make sure that your triangles contain a color component (RGBA) on each vertex, as the per-pixel interpolation of that component is what the SHADE input is meant to represent. If you need to draw a rectangle, you need to split it into two triangles, as RDP rectangles does not allow a color component to be specified. Programming notes **Libdragon:** When using rdpq\_triangle, make sure to specify a format that includes shades (eg: TRIFMT\_SHADE). To draw a rectangle with per-vertex color, split it into two triangles with two calls to rdpq\_triangle. Retrieved from "[https://n64brew.dev/wiki/Reality\_Display\_Processor/Hazards?oldid=5697](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards?oldid=5697) " --- # N64brew Game Jam 2022 - N64brew Wiki [](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#) N64brew Game Jam 2022 ===================== The third N64 homebrew game jam put together by the N64brew community on Discord[\[1\]](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#cite_note-1) . The theme, "**Spooky**", was announced on August 27, 2022. The game jam ran from August 30, 2022 through to the deadline on October 30, 2022. Interviews with jam contestants & judges happened once the jam was completed. [![The N64Brew logo. The words "SPOOKY Jam" are on the label, and jar that says "jam".](https://static.wikitide.net/n64wiki/6/6c/N64brew-spooky-game-jam-2022.png)](https://n64brew.dev/wiki/File:N64brew-spooky-game-jam-2022.png) The winner, Team Ultrarare, decided that the prize pot of $1369.65 USD should go to Child's Play[\[2\]](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#cite_note-2) , a charity dedicated to sending toys, books, and games to children's hospitals. Contents -------- * [1 Judges](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#Judges) * [2 Submissions](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#Submissions) * [3 Results](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#Results) * [4 External Links](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#External_Links) * [5 Reflist](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#Reflist) Judges ------ | Name | External link(s) | | --- | --- | | Buu342 | [https://github.com/buu342](https://github.com/buu342) | | N64 Squid | [https://n64squid.com/](https://n64squid.com/) | | Gerry (Behind The Code) | [https://twitter.com/\_BehindTheCode/](https://twitter.com/_BehindTheCode/) | Submissions ----------- | Entry | Solo/Team | Participant(s) | Engine/Framework | Source Code | External link(s) | | --- | --- | --- | --- | --- | --- | | [Styx](https://n64brew.dev/wiki/Styx?action=edit&redlink=1 "Styx (page does not exist)") | Team | Ultrarare (lambertjamesd, jtn191, SapphireTactics) | libultra | [https://github.com/N64brew-Game-Jam-2022/Styx](https://github.com/N64brew-Game-Jam-2022/Styx) | | [Super Snooper Spookers](https://n64brew.dev/wiki/Super_Snooper_Spookers?action=edit&redlink=1 "Super Snooper Spookers (page does not exist)") | Solo | CardboardBox (Anthony Blackman) | Libdragon | [https://github.com/N64brew-Game-Jam-2022/Super-Snooper-Spookers](https://github.com/N64brew-Game-Jam-2022/Super-Snooper-Spookers) | | [Spirit Harvest](https://n64brew.dev/wiki/Spirit_Harvest?action=edit&redlink=1 "Spirit Harvest (page does not exist)") | Team | Spirit Walkers (WadeMalone, bryc, Cobra!) | libultra | [https://github.com/N64brew-Game-Jam-2022/Spirit-Harvest](https://github.com/N64brew-Game-Jam-2022/Spirit-Harvest) | | [Slime Heist](https://n64brew.dev/wiki/Slime_Heist?action=edit&redlink=1 "Slime Heist (page does not exist)") | Team | A Spooky Sight (Yoshimaster96, Raphaël) | Libdragon | [https://github.com/N64brew-Game-Jam-2022/Slime-Heist](https://github.com/N64brew-Game-Jam-2022/Slime-Heist) | | [Dead Ritual](https://n64brew.dev/wiki/Dead_Ritual?action=edit&redlink=1 "Dead Ritual (page does not exist)") | Team | Half An Octapus (Kivan117, SpiritOf1776,Hypatia) | libultra | [https://github.com/N64brew-Game-Jam-2022/Dead-Ritual](https://github.com/N64brew-Game-Jam-2022/Dead-Ritual) | | [Fanger's Lightmare](https://n64brew.dev/wiki/Fanger%27s_Lightmare?action=edit&redlink=1 "Fanger's Lightmare (page does not exist)") | Team | Vintage Horror Pictures (pepsiman, Brozilla, Gary Jones III, Erockbrox) | Libdragon | [https://github.com/N64brew-Game-Jam-2022/Fangers-Lightmare](https://github.com/N64brew-Game-Jam-2022/Fangers-Lightmare) | | [Tiny Nightmare](https://n64brew.dev/wiki/Tiny_Nightmare?action=edit&redlink=1 "Tiny Nightmare (page does not exist)") | Team | TN64 Devs (zoncabe, jaltekruse,Swagneto, Mr. Glitch) | Libultra | [https://github.com/N64brew-Game-Jam-2022/Tiny-Nightmare](https://github.com/N64brew-Game-Jam-2022/Tiny-Nightmare) | | [Swamp Hero 64](https://n64brew.dev/wiki/Swamp_Hero_64?action=edit&redlink=1 "Swamp Hero 64 (page does not exist)") | Solo | torte00 | UltraED Game Engine & Libultra | [https://github.com/N64brew-Game-Jam-2022/Swamp-Hero-64](https://github.com/N64brew-Game-Jam-2022/Swamp-Hero-64) | Results ------- | | | | | --- | --- | --- |Finalists | Entry | Score | Rank | | Styx | 70/75 | 1st Place | | Super Snooper Spookers | 65/75 | 2nd Place | | Spirit Harvest | 57/75 | 3rd Place | | Slime Heist | 51/75 | 1st Runner Up | | Dead Ritual | 50/75 | 2nd Runner Up | | Fanger's Lightmare | 45/75 | 3rd Runner Up | | Tiny Nightmare | 41/75 | 4th Runner Up | | Swamp Hero | 38/75 | 5th Runner Up | External Links -------------- 1. [Jam Announcement & Theme Reveal Video](https://www.youtube.com/watch?v=pXG7Jam7FM0) 2. [Interview with the Judges and Contestants](https://www.youtube.com/watch?v=19JOcCS3_vs) 3. [Highlight reel of the entries](https://www.youtube.com/watch?v=esQFgOOwXlk) 4. [Winners Announcement Reel](https://www.youtube.com/watch?v=EztQBAE0wNQ) Reflist ------- 1. [↑](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#cite_ref-1) [https://discord.gg/WqFgNWf](https://discord.gg/WqFgNWf) 2. [↑](https://n64brew.dev/wiki/N64brew_Game_Jam_2022#cite_ref-2) [https://www.childsplaycharity.org/](https://www.childsplaycharity.org/) Retrieved from "[https://n64brew.dev/wiki/N64brew\_Game\_Jam\_2022?oldid=4927](https://n64brew.dev/wiki/N64brew_Game_Jam_2022?oldid=4927) " --- # Reality Signal Processor/CPU Core - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#) Reality Signal Processor/CPU Core ================================= < [Reality Signal Processor](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor") Contents -------- * [1 Scalar unit (SU)](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Scalar_unit_(SU)) * [1.1 Missing opcodes](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Missing_opcodes) * [1.2 Memory access](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Memory_access) * [2 Vector Unit (VU)](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Vector_Unit_(VU)) * [2.1 Vector registers and glossary](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Vector_registers_and_glossary) * [2.2 Accumulator](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Accumulator) * [2.3 Control registers](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Control_registers) * [2.4 Clamping](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Clamping) * [2.5 Element field](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Element_field) * [2.6 Broadcast modifier](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Broadcast_modifier) * [2.7 Instructions overview](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Instructions_overview) * [2.7.1 Loads and stores](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Loads_and_stores) * [2.7.2 Single-lane instructions](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Single-lane_instructions) * [2.7.3 Computational instructions](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Computational_instructions) * [2.7.4 Select instructions](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Select_instructions) * [2.7.5 VU/SU Moves](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#VU/SU_Moves) * [2.8 Instruction details](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Instruction_details) * [2.8.1 Scalar loads: LBV, LSV, LLV, LDV](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Scalar_loads:_LBV,_LSV,_LLV,_LDV) * [2.8.1.1 Assembly](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Assembly) * [2.8.1.2 Pseudo-code](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Pseudo-code) * [2.8.1.3 Description](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Description) * [2.8.1.4 Usage](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Usage) * [2.8.2 Scalar stores: SBV, SSV, SLV, SDV](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Scalar_stores:_SBV,_SSV,_SLV,_SDV) * [2.8.2.1 Assembly](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Assembly_2) * [2.8.2.2 Pseudo-code](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Pseudo-code_2) * [2.8.2.3 Description](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Description_2) * [2.8.2.4 Usage](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Usage_2) * [2.8.3 128-bit vector loads: LQV, LRV](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#128-bit_vector_loads:_LQV,_LRV) * [2.8.3.1 Assembly](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Assembly_3) * [2.8.3.2 Pseudo-code](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Pseudo-code_3) * [2.8.3.3 Description](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Description_3) * [2.8.3.4 Usage](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Usage_3) Scalar unit (SU) ---------------- The scalar is the half of the RSP core that is similar to a standard MIPS R4000 32-bit CPU. It has 32 32-bit registers (conventionally called `r0`\-`r31`) and implement most standard opcodes. This page does not describe the whole scalar unit as standard MIPS documentation suffices, but it highlights the main difference. ### Missing opcodes The following opcodes are not implemented by RSP: * **Multiplication units.** RSP does not have a multiplication unit so there is no MULT, MULTU, DIV, DIVU, MFHI, MFLO, MTHI, MTLO. * **64-bit instructions.** RSP only has 32-bit scalar registers in SU, so there is no 64-bit opcodes (the ones starting with D such as DADDIU, DSRL, etc.) nor 64-bit memory accesses such as LD, SD, LDL, SDL. * **No opcodes for misaligned memory accesses.** All memory accesses to DMEM can be correctly performed also to misaligned addresses, using the standard opcodes like LW / SW or LH / LHU / SH, so there is no LWL, LWR, SWL, SWR. * **No traps or exceptions.** RSP does not implement any form of interrupt or exception handling, so there is no SYSCALL nor trap instructions (TGE, TLT, etc.). BREAK is available but it has a special behavior (see below). * **No support for likely branches.** The "likely" variant of all branches is not supported. The missing opcodes are the ones ending with L (such as BEQL, BLEZL, etc.) ### Memory access RSP is a harvard architecture. All opcodes are fetched from IMEM (4KB) and all data is access in DMEM (4KB). The PC register is 12-bit. All higher address bits in branch / call instructions are thus ignored. When PC reaches the last opcode (at 0xFFC), execution continues to the first opcode in IMEM (PC wraps to 0x000). All accesses to DMEM are performed using the lowest 12 bits of the address calculated by the load/store instruction (higher bits are ignored). Moreover, contrary to standard MIPS architecture, the RSP can correctly perform misaligned memory accesses (eg: it is possibly to fetch a 32-bit word at address 0x001, that will contain the 4 bytes at 0x1-0x5). Standard MIPS architecture allows to do misaligned addresses only using the LWL/LWR or SWL/SWR couples, which are not required on the RSP. Vector Unit (VU) ---------------- The VU is the internal unit of the RSP CPU core that is able to perform fixed-point SIMD calculations. It is a proprietary design which does not follow any standard specification. Its opcodes and registers are exposed to the core via the COP2 interface. ### Vector registers and glossary VU contains 32 128-bit SIMD registers, each organized in 8 lanes of 16-bit each one. Most VU opcodes perform the same operation in parallel on each of the 8 lanes. The arrangement is thus similar to x86 SSE2 registers in EPI16 format. The vector registers array is called `VPR` in this document, so `VPR[4]` refers to the fifth register (usually called `v4` in assembly). When referring to specific portions of the register, we use the following convention: * `VPR[vt][4..7]` refers to byte indices, that is bytes from 4 to 7, counting from the higher part of the register (in big-endian order). * `VPR[vt]<4..7>` refers to specific lane indices, that is lanes from 4 to 7 counting from the higher part of the register (in big-endian order). * Within each lane, `VPR[vt]<2>(3..0)` refers to inclusive bit ranges. Notice that bits are counted as usual in little-endian order (bit 0 is the lowest, bit 15 is the highest), and thus they are written as `(high..low)`. Ranges are specified using the `beg..end` inclusive notation (that is, both `beg` and `end` are part of the range). The concatenation of disjoint ranges is written with a `,`, for instance: `[0..3,8..11]` means 8 bytes formed by concatenating 4 bytes starting at 0 with 4 bytes starting at 8. Vector lanes are usually interpreted as a fixed point number. As a homebrew programmer, it is useful to understand the meaning of each opcode and its correct usage while writing code, which goes beyond the exact hardware description of how bits are shuffled around. To refer to a fixed point number, we use the syntax `S1.15` where "S" means "signed" (while "U" is "unsigned"), "1" is the number of bits for the integral part, and "15" are the number of bits for the fractional part. ### Accumulator The RSP contains a 8-lane SIMD accumulator, that is used implicitly by multiplication opcodes. Each of the 8 lanes is 48-bits wide, that allows to accumulate intermediate results of calculations without the loss of precision that would incur when storing them into a 16-bit lane in a vector register. It is possible to extract the contents of the accumulator through the VSAR opcode; one call to this opcode can extract a 16-bit portion of each lane and store it into the specified vector register. The three portions are conventionally called `ACCUM_LO` (bits 15..0 of each lane), `ACCUM_MD` (bits 31..16 of each lane), and `ACCUM_HI` (bits 47..32 of each lane). If you exclude the VSAR instruction that cuts the accumulator piecewise for extracting it, it is better to think of it a single register where each lane is 48-bits wide. ### Control registers The VU contains 3 16-bit control registers: VCC, VCO, VCE. These registers are used as flag registers by several opcodes. As with most flags, even though they have a general meaning that is generally valid, they tend to also be used in some mind-twisting way to obtain the desired result. It doesn't really make sense to try to describe them at the general level and instead each instruction will explain if/how it uses or modifies the control registers. To read/write the contents of the control registers, the `ctc2` / `cfc2` instructions can be used. ### Clamping Multiplication opcodes perform a clamping step when extracting the accumulator into a vector register. Notice that each lane of the accumulator is always treated as a _signed_ 48-bit number. This is the pseudo-code for signed clamping (no surprises): function clamp_signed(accum) if accum < -32768 => return -32768 if accum > 32767 => return 32767 return accum The returned value is thus always within the signed 16-bit range. This is the pseudo-code for unsigned clamping: function clamp_unsigned(accum) if accum < 0 => return 0 if accum > 32767 => return 65535 return accum Notice that in unsigned clamping, the saturating threshold is 15-bit, but the saturated value is 16-bit. ### Element field Most VU instructions have a 3-register format with an additional modifier called "element field". For instance (using GNU assembly syntax): opcode v0, v1, v2,e(7) `e(7)` is the "element modifier". Normally (and especially in GNU syntax, which is more orthogonal and uniform), it refers to a specific lane of the third register, which is why it is common to format it without a leading whitespace. In this example, it "selects" lane 7 of register `v2`. The exact meaning of the element modifier varies for different instruction groups, and also the way it is assembled changes wildly. Pay attention to the description of each instruction group to check what the element modifier means and how it is encoded in the opcode. ### Broadcast modifier One of the most common uses of the element field is the broadcast modifier. This modifier is used by computational instructions and select instructions and allows to "broadcast" (duplicate) one or more lanes to other lanes, just for the purpose of the current opcode. For instance: vaddc $v01, $v04,e(1) `e(1)` is the broadcast modifier. Normally, the instruction would add the two registers lane by lane; with the modifier, the second lane (index 1) of `$v04` is added to all lanes of `$v01`. The modifier is stored in the `element` field of the opcode | `element` | GNU syntax | SGI syntax | Lanes being accessed | Description | | --- | --- | --- | --- | --- | | 0 | | | 0,1,2,3,4,5,6,7 | Normal register access (no broadcast) | | 1 | | | 0,1,2,3,4,5,6,7 | Normal register access (no broadcast) | | 2 | `e(0q)` | `[0q]` | 0,0,2,2,4,4,6,6 | Broadcast 4 of 8 lanes | | 3 | `e(1q)` | `[1q]` | 1,1,3,3,5,5,7,7 | Broadcast 4 of 8 lanes | | 4 | `e(0h)` | `[0h]` | 0,0,0,0,4,4,4,4 | Broadcast 2 of 8 lanes | | 5 | `e(1h)` | `[1h]` | 1,1,1,1,5,5,5,5 | Broadcast 2 of 8 lanes | | 6 | `e(2h)` | `[2h]` | 2,2,2,2,6,6,6,6 | Broadcast 2 of 8 lanes | | 7 | `e(3h)` | `[3h]` | 3,3,3,3,7,7,7,7 | Broadcast 2 of 8 lanes | | 8 | `e(0)` | `[0]` | 0,0,0,0,0,0,0,0 | Broadcast single lane | | 9 | `e(1)` | `[1]` | 1,1,1,1,1,1,1,1 | Broadcast single lane | | 10 | `e(2)` | `[2]` | 2,2,2,2,2,2,2,2 | Broadcast single lane | | 11 | `e(3)` | `[3]` | 3,3,3,3,3,3,3,3 | Broadcast single lane | | 12 | `e(4)` | `[4]` | 4,4,4,4,4,4,4,4 | Broadcast single lane | | 13 | `e(5)` | `[5]` | 5,5,5,5,5,5,5,5 | Broadcast single lane | | 14 | `e(6)` | `[6]` | 6,6,6,6,6,6,6,6 | Broadcast single lane | | 15 | `e(7)` | `[7]` | 7,7,7,7,7,7,7,7 | Broadcast single lane | ### Instructions overview #### Loads and stores | 31..26 | 25..21 | 20..16 | 15..11 | 10..7 | 6..0 | | --- | --- | --- | --- | --- | --- | | `LWC2` or `SWC2` | `base` | `vt` | `opcode` | `element` | `offset` | The instructions perform a load/store from DMEM into/from a vector register. * `base` is the index of a scalar register used as base for the memory access * `offset` is a signed offset added to the value of the base register (with some scaling, depending on the actual instruction). * `vt` is the vector register. * `element` is used to index a specific byte/word within the vector register, usually specifying the first element affected by the operation (thus allows to access sub-portions of the vector register). | | | | | | --- | --- | --- | --- |List of all loads and stores opcodes | Group | Opcode | Instruction | Description | | Scalar | 0x00 | `lbv` / `sbv` | Load / Store 1 byte into/from a VPR | | 0x01 | `lsv` / `ssv` | Load / Store 2 bytes into/from a VPR | | 0x02 | `llv` / `slv` | Load / Store 4 bytes into/from a VPR | | 0x03 | `ldv` / `sdv` | Load / Store 8 bytes into/from a VPR | | 128-bit | 0x04 | `lqv` | Load (up to) 16 bytes into a VPR, left-aligned | | 0x05 | `lrv` | Load (up to) 16 bytes into a VPR, right-aligned | | 0x04 | `sqv` | Store (up to) 16 bytes from a VPR, left-aligned | | 0x05 | `srv` | Store (up to) 16 bytes from a VPR, right-aligned | | 8-bit packed | 0x06 | `lpv` / `spv` | Load / store 8 8-bit signed values into a VPR | | 0x07 | `luv` / `suv` | Load / store 8 8-bit unsigned values into a VPR | | 0x08 | `lhv` / `shv` | Load / store 8 8-bit unsigned values into VPR, accessing every other byte in memory | | 0x09 | `lfv` / `sfv` | Load / store 4 8-bit unsigned values into VPR, accessing every fourth bytes in memory | | Transpose | 0x01 | `swv` | | | 0x0B | `ltv` | Load 8 lanes from 8 GPRs into a VPR | | 0x0B | `stv` | Store 8 lanes of a VPR into 8 GPRs | #### Single-lane instructions | 31..26 | 25 | 24..21 | 20..16 | 15..11 | 10..6 | 5..0 | | --- | --- | --- | --- | --- | --- | --- | | `COP2` | 1 | `vt_elem` | `vt` | `vd_elem` | `vd` | `opcode` | Single-lane instructions are an instruction group that perform operations on a single lange of a single input register (`VT`), and store the result into a single lane of a single output register (`VD`). Example syntax: vmov $v01, e(4), $v05, e(6) In this example, the value in lane `$v05<6>` is moved to lane `$v01<4>`. In the assembly syntax, the [broadcast modifier syntax](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Broadcast_modifiers) is used, but no actual broadcast is performed, as the instructions operate on the single specified lane. Only the single-lane broadcast modifiers (`e(0)` ... `e(7)`) are supported.+ In the opcode, the fields `vt_elem` and `vd_elem` are used to compute `se` and `de` that is to specify which lane, respectively of the input and output register, is affected. `vd_elem` is 5 bits long (range 0..31); the highest bits are always ignored, and the destination lane `de` is simply `vd_elem(2..0)`. `vt_elem` is 4 bits long (range 0..15). When `vt_elem(3)` is 1, `vt_elem(2..0)` is actually used as source lane `se`, as expected. When `vt_elem(3)` is 0, a hardware bug is triggered and portions of the lower bits of `vt_elem` are replaced with portion of the bits of `vd_elem` while computing `se`. Specifically, all bits in `vt_elem` from the topmost set bit and higher are replaced with the same-position bits in `vd_elem`. Notice that this behaviour is actually consistent with what happens when `vt_elem(3)` is 1, which means that there is no need to think of it as a special-case. Pseudo-code: de(2..0) = vd_elem(2..0) msb = highest_set_bit(vt_elem) se(2..0) = vd_elem(2..msb) || vt_elem(msb-1..0) | | | | | --- | --- | --- |Single-lane instructions | Opcode | Instruction | Description | | 0x33 | `vmov` | Copy one lane of a VPR into another VPR | | 0x30 | `vrcp` | Compute the 32-bit reciprocal of a 16-bit fixed point | | 0x34 | `vrsq` | Compute the 32-bit reciprocal square root of a 16-bit fixed point | | 0x32 | `vrcph` | Extract the higher 16-bit of the result of a previous VRCP | | 0x36 | `vrsqh` | Extract the higher 16-bit of the result of a previous VRSQ | | 0x31 | `vrcpl` | Compute the 32-bit reciprocal of a 32-bit fixed point | | 0x35 | `vrsql` | Compute the 32-bit reciprocal square root of a 32-bit fixed point | | 0x37 | `vnop` | No operation (?) | | 0x3F | `vnull` | No operation (?) | #### Computational instructions | 31..26 | 25 | 24..21 | 20..16 | 15..11 | 10..6 | 5..0 | | --- | --- | --- | --- | --- | --- | --- | | `COP2` | 1 | `element` | `vt` | `vs` | `vd` | `opcode` | Instructions have this general format: VINSN vd, vs, vt,e(…) where `e(…)` is the [broadcast modifier](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Broadcast_modifier) (as found in other SIMD architectures), that modifies the access to `vt` duplicating some lanes and hiding others. This is the list of opcodes in this group. | Opcode | Instruction | Description | | --- | --- | --- | | 0x00 | `vmulf` | Vector multiply S1.15 \* S1.15, with rounding and signed clamping | | 0x01 | `vmulu` | Vector multiply S1.15 \* S1.15 with rounding and unsigned clamping | | 0x04 | `vmudl` | Vector multiply U0.16 \* U0.16 with signed clamping | | 0x05 | `vmudm` | Vector multiply S0.16 \* U0.16 with signed clamping | | 0x06 | `vmudn` | Vector multiply U0.16 \* S0.16 with signed clamping | | 0x07 | `vmudh` | Vector multiply S0.16 \* S0.16 with signed clamping | | 0x08 | `vmacf` | Like VMULF, but also add the result to the accumulator | | 0x09 | `vmacu` | Like VMULU, but also add the result to the accumulator | | 0x0C | `vmadl` | Like VMUDL, but also add the result to the accumulator | | 0x0D | `vmadm` | Like VMUDM, but also add the result to the accumulator | | 0x0E | `vmadn` | Like VMUDN, but also add the result to the accumulator | | 0x0F | `vmadh` | Like VMUDH, but also add the result to the accumulator | | 0x10 | `vadd` | Vector add with carry | | 0x13 | `vabs` | Vector absolute value | | 0x14 | `vaddc` | Vector add writing overflow into carry | | 0x1D | `vsar` | Read a portion of the accumulator into a VPR | | 0x28 | `vand` | Vector bitwise and (`a & b`) | | 0x29 | `vnand` | Vector bitwise nand (`~(a & b)`) | | 0x2A | `vor` | Vector bitwise or (`a \| b`) | | 0x2B | `vnor` | Vector bitwise nor (`~(a \| b)`) | | 0x2C | `vxor` | Vector bitwise xor (`a ^ b`) | | 0x2D | `vnxor` | Vector bitwise nxor (`~(a ^ b)`) | #### Select instructions | 31..26 | 25 | 24..21 | 20..16 | 15..11 | 10..6 | 5..0 | | --- | --- | --- | --- | --- | --- | --- | | `COP2` | 1 | `element` | `vt` | `vs` | `vd` | `opcode` | Instructions have this general format: VINSN vd, vs, vt, e(…) where `e(…)` is the [broadcast modifier](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Broadcast_modifier) (as found in other SIMD architectures), that modifies the access to `vt` duplicating some lanes and hiding others. See the Computational instructions section for details. This is the list of opcodes in this group: | Opcode | Instruction | Description | | --- | --- | --- | | 0x20 | `vlt` | Select the lower value between two VPR | | 0x21 | `veq` | Compare two VPR to check if they are equal | | 0x22 | `vne` | Compare two VPR to check if they are different | | 0x23 | `vge` | Select the greater or equal value between two VPR | | 0x24 | `vcl` | Clip a VPR against two bounds (lower 16-bits) | | 0x25 | `vch` | Clip a VPR against two bounds (higher 16-bits) | | 0x26 | `vcr` | Clip a VPR against a pow-2 bound | | 0x27 | `vmrg` | Merge two VPR selecting each lane according to flags | #### VU/SU Moves | 31..26 | 25..21 | 20..16 | 15..11 | 10..7 | 6..0 | | --- | --- | --- | --- | --- | --- | | `COP2` | `opcode` | `rt` | `vs` | `vs_elem` | 0 | These are the standard MIPS opcodes for moving data in/out the coprocessor registers. | `opcode` | Instruction | Description | | --- | --- | --- | | 0x0 | `mfc2` | Copy a lane of a VPR into a GPR | | 0x2 | `cfc2` | Copy a VU control register into a GPR | | 0x4 | `mtc2` | Copy a GPR into a lane of a VPR | | 0x6 | `ctc2` | Copy a GPR into a VU control register | Vector moves follow the same format as standard MIPS coprocessor moves, but use part of the lower 11 bits (which are normally unused) to specify the element field, selecting which lane of the VPR is accessed. Notice that, `vs_elem` in this case is not a broadcast modifier: it specifies a byte offset (not a lane index!), so to copy a lane, `lane*2` must be specified. This is an example using GNU syntax: mtc2 a1, $v04,e(4) This example will copy the lower 16 bits of GPR `a1` into the fifth lane of `$v04`. This opcode is assembled with `vs_elem = 8`, as explained above. `mtc2` moves the lower 16 bits of the general purpose register `rt` to the bytes `VS[vs_elem+1..vs_elem]`. If `vs_elem` is 15, only `VS[vs_elem]` is written (with `rt[15..8]`). `mfc2` moves the 2 bytes `VS[vs_elem+1..vs_elem]` to GPR `rt`, sign extending the 16 bits value to 32 bits. If `vs_elem` is 15, the lower byte is taken from byte 0 of the register (that is, it wraps around). `ctc2` moves the lower 16 bits of GPR `rt` into the control register specified by `vs`, while `cfc2` does the reverse, moving the control register specified by `vs` into GPR `rt`, sign extending to 32 bits. Note that both `ctc2` and `cfc2` ignore the `vs_elem` field. For these instructions, the control register is specified as follows: | `vs` | Register | | --- | --- | | 0 | `VCO` | | 1 | `VCC` | | 2 | `VCE` | ### Instruction details #### Scalar loads: LBV, LSV, LLV, LDV | 31..26 | 25..21 | 20..16 | 15..11 | 10..7 | 6..0 | | --- | --- | --- | --- | --- | --- | | `LWC2` | `base` | `vt` | `opcode` | `element` | `offset` | ##### Assembly lsv $v01,e(2), 0,s0 ; Load the 16-bit word at s0 into the third lane of $v01 lbv $v04,8, 0,s1 ; Load the 8-bit word at s1 into the 9th byte of $v04 (MSB of lane 4) Notice that it is possible to specify the lane syntax for the `element` field to refer to a specific lane, but if the access is made using `llv` or `ldv` (4 or 8 bytes), it will overflow into the following lanes. ##### Pseudo-code addr \= GPR\[base\] + offset \* access\_size data \= DMEM\[addr..addr+access\_size\-1\] VPR\[vt\]\[element..element+access\_size\-1\] \= data ##### Description These instructions load a scalar value (1, 2, 4, or 8 bytes) from DMEM into a VPR. Loads affect only a portion of the vector register (which is 128-bit); other bytes in the register are not modified. The address in DMEM where the value is fetched is computed as `GPR[base] + (offset * access_size)`, where `access_size` is the number of bytes being accessed (eg: 4 for `llv`). The address can be misaligned: despite how memory accesses usually work on MIPS, these instructions perform unaligned memory accesses. The part of the vector register being accessed is `VPR[vt][element..element+access_size]`, that is `element` selects the first accessed byte within the vector register. When `element+access_size` is bigger than 15, fewer bytes are processed (eg: `llv` with `element=13` only loads 3 byte from memory into `VPR[vt][13..15]`). ##### Usage These instructions are seldom used. Normally, it is better to structure RSP code to work across full vectors to maximize parallelism. Input data should already be provided in vectorized format by the CPU, so that it is possible to use a vector load (`lqv`, in case the input is made of 16-bit data) or a packed load (`luv`/`lpv`, in case the input is made of 8-bit data). Consider also using `mtc2` to load a 16-bit value into a lane of a VPR when the value is available in a GPR. A possible use-case for these instructions is to reverse the order of the lanes. For instance, in audio codecs, windowing algorithms often work combining sequences audio samples with other sequences in reverse order. RSP does not have an instruction to reverse the order of the lanes, so in that case it might be necessary to manually reverse the lanes while loading using `lsv`: lqv $v00, 0,s0 ; Load 8 16-bit samples from DMEM at address s0 lsv $v01,e(7), 0,s1 ; Load 8 16-bit samples from DMEM at address s1 in reverse order lsv $v01,e(6), 2,s1 lsv $v01,e(5), 4,s1 lsv $v01,e(4), 6,s1 lsv $v01,e(3), 8,s1 lsv $v01,e(2), 10,s1 lsv $v01,e(1), 12,s1 lsv $v01,e(0), 14,s1 #### Scalar stores: SBV, SSV, SLV, SDV | 31..26 | 25..21 | 20..16 | 15..11 | 10..7 | 6..0 | | --- | --- | --- | --- | --- | --- | | `SWC2` | `base` | `vt` | `opcode` | `element` | `offset` | ##### Assembly ssv $v01,e(2), 0,s0 ; Store the 16-bit word in the third lane of $v01 into DMEM at address s0 sbv $v04,8, 0,s1 ; Store the 8-bit word in the 9th byte of $v04 (MSB of lane 4) into DMEM at address s1 Notice that it is possible to specify the lane syntax for the `element` field to refer to a specific lane, but if the access is made using `slv` or `sdv` (4 or 8 bytes), it will overflow into the following lanes. ##### Pseudo-code addr \= GPR\[base\] + offset \* access\_size data \= VPR\[vt\]\[element..element+access\_size\-1\] DMEM\[addr..addr+access\_size\-1\] \= data ##### Description These instructions store a scalar value (1, 2, 4, or 8 bytes) from a VPR into DMEM. The address in DMEM where the value will be stored is computed as `GPR[base] + (offset * access_size)`, where `access_size` is the number of bytes being accessed (eg: 4 for `SLV`). The address can be misaligned: despite how memory accesses usually work on MIPS, these instructions perform unaligned memory accesses. The part of the vector register being accessed is `VPR[vt][element..element+access_size]`, that is `element` selects the first accessed byte within the vector register. When `element+access_size` is bigger than 15, the element access wraps within the vector and a full-size store is always performed (eg: `slv` with `element=15` stores `VPR[vt][15,0..2]` into memory, for a total of 4 bytes). ##### Usage These instructions are seldom used. Normally, it is better to structure RSP code to work across full vectors to maximize parallelism. Data flow between RSP and VR4300 should be structured in vectorized format, so that it is possible to use a vector store (`sqv`, in case the output is made of 16-bit data) or a packed load (`suv`/`spv`, in case the output is made of 8-bit data). Consider also using `mfc2` to store a 16-bit value from the lane of a VPR into a GPR. See [scalar loads](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core#Scalar_loads:_LBV,_LSV,_LLV,_LDV) for an example of a use-case (reversing a vector) that can be implemented also via `ssv`. #### 128-bit vector loads: LQV, LRV | 31..26 | 25..21 | 20..16 | 15..11 | 10..7 | 6..0 | | --- | --- | --- | --- | --- | --- | | `LWC2` | `base` | `vt` | `opcode` | `element` | `offset` | | Insn | `opcode` | Desc | | --- | --- | --- | | `LQV` | 0x04 | load (up to) 16 bytes into vector, left-aligned | | `LRV` | 0x05 | load (up to) 16 bytes into vector, right-aligned | ##### Assembly // Standard 128-bit load from DMEM aligned address s0 into $v08 lqv $v08, 0,s0 // Loading a misaligned 128-bit vector from DMEM // (a0 is 128-bit aligned in this example) lqv $v00, 0x08,a0 // read bytes 0x08(a0) - 0x0F(a0) into left part of the vector (VPR\[vt\]\[0..7\]) lrv $v00, 0x18,a0 // read bytes 0x10(a0) - 0x17(a0) into right part of the vector (VPR\[vt\]\[8..15\]) // Advanced example using the "element" field lqv $v08,e(2), 0x08,a0 // read bytes 0x08(a0) - 0x0F(a0) into VPR\[vt\]\[4..11\] lrv $v08,e(2), 0x18,a0 // read bytes 0x10(a0) - 0x13(a0) into VPR\[vt\]\[12..15\] Notice that the element field is optional (defaults to 0) and is usually not specified because these instructions are meant to affect the whole vector. The element field can be specified using the lane syntax (`e(N)`) or a raw number which maps to the byte offset inside the vector. ##### Pseudo-code // lqv addr \= GPR\[base\] + (offset \* 16) end \= addr | 15 size \= MIN(end\-addr, 15\-element) VPR\[vt\]\[element..element+size\] \= DMEM\[addr..addr+size\] // lrv end \= GPR\[base\] + (offset \* 16) addr \= end & ~16 size \= MIN(end\-addr, 15\-element) VPR\[vt\]\[element..addr+size\] \= DMEM\[addr..addr+size\] ##### Description Roughly, these functions behave like `lwl` and `lwr`: combined, they allow to read 128 bits of data into a vector register, irrespective of the alignment. When the data to be loaded is 128-bit aligned within DMEM, `lqv` is sufficient to read the whole vector (`lrv` in this case is redundant because it becomes a no-op). The actual bytes accessed in DMEM depend on the instruction: for `lwv`, the bytes are those starting at `GPR[base] + (offset * 16)`, up to and excluding the next 128-bit aligned byte (`a0+0x10` in the above example); for `lrv`, the bytes are those starting at the previous 128-bit aligned byte (`a0+0x10` in the above example) up to and _excluding_ `GPR[base] + (offset * 16)`. Again, this is exactly the same behavior of `lwl` and `lwr`, but for 128-bit aligned loads. `element` is used as a byte offset within the vector register to specify the first byte affected by the operation; that is, the part of the vector being loaded with the instruction pair is `VPR[vt][element..15]`. Thus a non-zero element means that fewer bytes are loaded. ##### Usage `lqv` is the most standard way to fill a full VPR vector register loading its contents from DMEM. Given that it's usually possible to define the layout of data in DMEM, it is advisable to design it so that vectors are always aligned to 128-bit (16 bytes), using the `.align 4` directory: this allows to read the vector using just `lqv`, in 1 cycle (though the load has a 3-cycle latency like all instructions that write to a VPR). .data .align 4 CONST: .half 3, 2, 7, 0, 0x4000, 0x8000, 0x7F, 0xFFF \# Several constants used for an algorithm .text lqv $v31, %lo(CONST),r0 \# Load the constants One example of using `lqv` and `lrv` in pair is to perform a fast memcpy from a possible misaligned address to an aligned destination buffer: \# s0 is the pointer to source data in DMEM (possibly misaligned) \# s4 will point to the destination buffer in DMEM (aligned) \# t1 is the number of bytes to copy li s4, %lo(BUFFER) loop: lqv $v00, 0x00,s0 lrv $v00, 0x10,s0 sqv $v00, 0,s4 sub t1, 16 add s0, 16 bgtz t1, loop add s4, 16 Retrieved from "[https://n64brew.dev/wiki/Reality\_Signal\_Processor/CPU\_Core?oldid=5539](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core?oldid=5539) " --- # PIF-NUS - N64brew Wiki [](https://n64brew.dev/wiki/PIF#) PIF-NUS ======= (Redirected from [PIF](https://n64brew.dev/wiki/PIF?redirect=no "PIF") ) 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](https://n64brew.dev/wiki/Sharp_SM5_Microcontroller "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](https://n64brew.dev/wiki/Peripheral_Interface "Peripheral Interface") is used to read/write to the game ROM and devices like the [64DD](https://n64brew.dev/wiki/64DD "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 Contents -------- * [1 Pinout](https://n64brew.dev/wiki/PIF#Pinout) * [2 Internal ROMs and RAM](https://n64brew.dev/wiki/PIF#Internal_ROMs_and_RAM) * [3 RAM-based communication protocol](https://n64brew.dev/wiki/PIF#RAM-based_communication_protocol) * [4 Joybus frame (controller and EEPROM communication)](https://n64brew.dev/wiki/PIF#Joybus_frame_(controller_and_EEPROM_communication)) * [4.1 Frame parsing and handshakes](https://n64brew.dev/wiki/PIF#Frame_parsing_and_handshakes) * [4.2 Joybus handhakes](https://n64brew.dev/wiki/PIF#Joybus_handhakes) * [4.2.1 TX byte: special flags](https://n64brew.dev/wiki/PIF#TX_byte:_special_flags) * [4.2.2 RX byte: special flags](https://n64brew.dev/wiki/PIF#RX_byte:_special_flags) * [4.2.3 Flag bits in PIF command](https://n64brew.dev/wiki/PIF#Flag_bits_in_PIF_command) * [4.3 Escape codes](https://n64brew.dev/wiki/PIF#Escape_codes) * [5 Console startup](https://n64brew.dev/wiki/PIF#Console_startup) * [5.1 IPL2 checksum algorithm](https://n64brew.dev/wiki/PIF#IPL2_checksum_algorithm) * [5.2 IPL3 checksum algorithm](https://n64brew.dev/wiki/PIF#IPL3_checksum_algorithm) * [6 Console Reset](https://n64brew.dev/wiki/PIF#Console_Reset) Pinout ------ | | | | --- | --- | | | **Notice**

This section requires more research. Pin names and descriptions may be inaccurate. |  [](https://n64brew.dev/wiki/File:PIF_decap_pins_labeled.png) PIF Decapped with Pins numbered | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- |PIF Pinout (28 Pin SOP Package) | | 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](https://n64brew.dev/wiki/CIC-NUS "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](https://n64brew.dev/wiki/CIC-NUS "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](https://n64brew.dev/wiki/Serial_Interface "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](https://n64brew.dev/wiki/Serial_Interface "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](https://github.com/GenericHeroGuy/pif-sm5-rom/) contains a commented disassembly and a decompiled C code for it. The [jago85/UltraPIF\_MCU](https://github.com/jago85/UltraPIF_MCU) project 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](https://n64brew.dev/wiki/Initial_Program_Load "Initial Program Load") ) 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](https://github.com/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](https://n64brew.dev/wiki/Joybus_Protocol "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](https://github.com/GenericHeroGuy/pif-sm5-rom/) 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. The meaning of the bits are different during PIF reset mode (during boot, or after the RESET button is pressed) or during normal run: | | | | | | | --- | --- | --- | --- | --- |Description of commands: PIF in reset mode | Bit | Command | Description | Arguments | Results | | 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](https://github.com/DragonMinded/libdragon/blob/0efbe60fd7bb04065c4603de02b74738bc9d605a/src/entrypoint.S#L37-L38)
).

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](https://n64brew.dev/wiki/PIF#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](https://n64brew.dev/wiki/PIF#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 | | | | | | | | --- | --- | --- | --- | --- |Description of commands: PIF in run mode | 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 + 0x08 | Joybus flag bits | These bits are used as "flag bits" for Joybus frames. In other words, these bits do not represent a new command, but affect how joybus transactions are executed.

See [Joybus frame (controller and EEPROM communication)](https://n64brew.dev/wiki/Joybus_frame_(controller_and_EEPROM_communication)?action=edit&redlink=1 "Joybus frame (controller and EEPROM communication) (page does not exist)")
for more information. | 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](https://github.com/GenericHeroGuy/pif-sm5-rom/blob/69dfe40baeb806271e55a3bb69733ce08b1390c3/cmodel.c#L772-L795) 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 is `0x01 - 0x3F`, and the top 2 bits are ignored) * `RX` is the number of bytes to received from the device (valid range is `0x00 - 0x3F`, and the top 2 bits are ignored) * `tt` is the data to transmit (must be exactly `TX` bytes) * `rr` is the space where received data will be written (must be exactly `RX` 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](https://n64brew.dev/wiki/Joybus_Protocol "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](https://n64brew.dev/wiki/Joybus_Protocol#Command_List "Joybus Protocol") 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 device is reset (using the same reset functionality performed by the escape code 0xFD, see below). | #### 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, or when the command is not otherwise known/understood by the device. | #### Flag bits in PIF command During the execution phase, PIF also checks bits 0x02 and 0x03 of the PIF command byte (offset 63 in RAM). These bits allow to tweak how the PIF behaves after sending the TX bytes, before starting receive the RX bytes. | | | | | | --- | --- | --- | --- |Flag bits in PIF command byte | Bit 2 | Bit 3 | Delay between TX and RX | Description | | 0 | 0 | ~7 µs | Send the stop bit, then proceed receiving bytes | | 0 | 1 | ~7 µs | Send the stop bit, then proceed receiving bytes | | 1 | 0 | ~520 µs | Send the stop bit, wait, and then proceed receiving bytes | | 1 | 1 | ~520 µs | Wait, then proceed receiving bytes | Normally, those bits will be set to 0, so the standard joybus protocol will be executed (with the stop bit). By tweaking those bits, you can add an explicit wait loop, or even disable the stop bit from being sent. Notice that these flag bits affect all the channels in the PIF frame, it is not possible to configure different behavior per each channel. ### 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 instructs the PIF to emit a special reset signal on the line (the line is pulled down for a 1ms). The exact behavior of the various devices after receiving this signal is currently unknown. | | 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 --------------- 1. PIF and VR4300 boot at power on. 2. VR4300 starts running code from address `0xBFC0 0000` which is mapped to PIF ROM via SI interface. 3. PIF starts communicating with the [CIC](https://n64brew.dev/wiki/CIC-NUS "CIC-NUS") inside the cartridge 1. CIC sends 1 nibble (4-bits): region identifier (0x1 = NTSC, 0x5 = PAL) 2. 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 3. CIC sends a 6 byte checksum (again, slightly obfuscated). This is the expected result for the [IPL2 checksum algorithm](https://n64brew.dev/wiki/PIF#IPL2_checksum_algorithm) (see below). 4. 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. 1. If the values don't match, the PIF stops the boot by freezing the CPU (halting it via the NMI line) 5. PIF writes several booting information (including the two seeds) to the PIF-RAM word at offset `0x24-0x27` (mapped at `0xBFC0 07E4`), so that the CPU can later access them. 6. PIF writes bit 0x80 in the command byte to signal VR4300 that the data is now available in PIF-RAM. 7. Meanwhile, the VR3000 is executing the IPL1 code directly fetching opcodes from PIF-ROM. 1. These instructions are executed in this very slow manner. Thankfully IPL1 is only 52 instructions + some looping. 2. It performs some really basic hardware initialization. 3. 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. 4. Jump to RSP IMEM to execute IPL2 8. IPL2 is executed by the VR4300 reading the instructions from RSP IMEM 1. More general hardware initialization 2. 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. 3. If the booting information says that it is a 64DD disk, it will jump to `0xA600 0000` 4. Send command 0x10 to PIF, and PIF disables access to PIF-ROM (IPL1). 5. Load IPL3 from the [cartridge ROM](https://n64brew.dev/wiki/ROM_Header "ROM Header") (offset 0x40-0x1000) into the RSP DMEM 6. Run the [IPL2 checksum algorithm](https://n64brew.dev/wiki/PIF#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. 7. 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. 8. Jump to RSP DMEM to execute IPL3. 9. IPL3 is executed by the VR4300 reading the instructions from the RSP DMEM. 1. Initialize RDRAM 2. Depending on reset type 1. Power On: Invalidate VR4300 ICache & DCache 2. Reset : Writeback VR4300 ICache & DCache 3. 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. 4. 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. 5. Run the [IPL3 checksum algorithm](https://n64brew.dev/wiki/PIF#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. 6. The 8-byte checksum is compared against the checksum stored at offset 0x10 in the ROM (part of the [ROM Header](https://n64brew.dev/wiki/ROM_Header "ROM Header") ). If it doesn't match, VR4300 halts itself. 7. Reset RSP 8. Clear Interrupts 9. Clear IPL3 from DMEM 10. Clear IPL2 from IMEM 11. Jump to Game code in RDRAM. The initial PC is stored at offset 0x08 in the ROM (part of the [ROM Header](https://n64brew.dev/wiki/ROM_Header "ROM Header") ), though in some IPL3 variants it is slightly descrambled first. 10. 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. 1. The PIF (on console) and [CIC](https://n64brew.dev/wiki/CIC-NUS "CIC-NUS") (on cartridge) begins doing a communication protocol which follows the challenge/response authentication pattern. 2. 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](https://github.com/jago85/PifChecksum/blob/master/PifChecksum.c) 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](https://n64brew.dev/wiki/ROM_Header "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](http://n64dev.org/n64crc.html) . 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](http://n64dev.org/n64crc.html) ) 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: 1. User presses Console Reset button 2. PIF receives an interrupt signaling that the button was pressed 3. PIF toggles VR4300 Interrupt 2 (INT2) also known as "pre-NMI". 1. 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. 4. PIF sends the RESET command to [CIC](https://n64brew.dev/wiki/CIC-NUS "CIC-NUS") (command `0b11`) 5. CIC waits for 500ms (grace time) 6. CIC acknowledges the RESET command to PIF by writing a 0 bit. 7. PIF waits (indefinitely) until the reset button is released. 8. PIF toggles VR4300 Non-Maskable Interrupt (NMI) which resets it. 1. PIF also unlocks the internal PIF ROM so that the boot process can start executing [IPL1](https://n64brew.dev/wiki/Initial_Program_Load "Initial Program Load") . Retrieved from "[https://n64brew.dev/wiki/PIF-NUS?oldid=5759](https://n64brew.dev/wiki/PIF-NUS?oldid=5759) " --- # Reality Signal Processor/CPU Pipeline - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#) Reality Signal Processor/CPU Pipeline ===================================== < [Reality Signal Processor](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor") Contents -------- * [1 RSP pipeline](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#RSP_pipeline) * [2 VU / SU pipelines](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#VU_/_SU_pipelines) * [3 Pipeline stalls: write latency](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#Pipeline_stalls:_write_latency) * [4 Pipeline stalls: branches](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#Pipeline_stalls:_branches) * [5 Pipeline stalls: stores after loads](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#Pipeline_stalls:_stores_after_loads) * [6 Dual-issue](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#Dual-issue) * [7 Dual-issue and stalls](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#Dual-issue_and_stalls) * [8 Dual-issue: hardware bug with single-lane instructions](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline#Dual-issue:_hardware_bug_with_single-lane_instructions) ### RSP pipeline This page describes how the RSP CPU pipeline works, from a software point of view. It does not guess how the hardware internally work, but it just describes the effects of the pipelines on code execution ### VU / SU pipelines The CPU core contains two different pipelines that can run in parallel. * **VU (vector unit)**: this is the vector pipeline, that is able to execute the special vector instructions (encoded as COP2 opcodes). The opcodes that run in the VU are identified by the opcode name that starts with letter `v`. For instance, `vadd` and `vmod` are vector instructions that runs in the VU. **NOTE:** vector loads/stores such as `lqv` or `sqv` are not considered vector instructions and do not run on the VU, as testified by their opcode name, * **SU (scalar unit)**: this is the scalar pipeline, that is able to execute the standard MIPS 32-bit scalar instructions. For instance, `lw`, `srl`, `suv` are all instructions that run in the SU. ### Pipeline stalls: write latency In a normal condition, all RSP opcodes run exactly in 1 clock cycle but they might have different latency. This means that while the opcode itself appears to run in 1 clock cycle, the destination register might not be available in time for the next instruction. In general: * All instructions that write to a VU register run in 1 clock cycle but their destination register has a 4 cycle latency. This includes all VU opcodes (eg: \``vaddc`\`) but also SU opcodes that writes to a VU register such as all load ops (eg: `lqv`) and `mtc2`. * Most instructions that write to a SU register run in 1 clock cycle and have a 1 cycle latency. The following are the exceptions: * DMEM loads (`lw`, etc.) run in 1 cycle too but have a 3-cycle latency. Notice that stores do the same, but there is no way to cause a pipeline stall with stores, so we are not focusing on them. * `mfc0,``mfc2`, `cfc2` run in 1 cycle and have a 3-cycle latency Attempting to access a register that is not yet ready cause a _pipeline stall_ that halts the RSP until the register is available. For instance: vsubc $v02, $v10, $v11 \# Writes to $v02 vlt $v02, $v02, $v12 \# Reads from $v02 => STALL (3 cycles) Since `vsubc` has 4 cycle latency, the destination register is available only on the 4th cycle after it. Given that the next opcode `vlt` tries to read from `$v02`, a stall is issued that lasts for 3 cycles. Basically the effect is similar to: vsubc $v02, $v10, $v11 \# Writes to $v02 vnop vnop vnop vlt $v02, $v02, $v12 \# Reads from $v02 => NO STALL (it's on the 4th cycle after vsubc This is an example of a stall caused by the a SU instruction such as `lw`: lw t0, 0(s0) \# Writes to t0 sll t0, 2 \# Reads from t0 => STALL (2 cycles) and this is an example of a stall caused by a SU instruction that writes to a VU register: lpv $v00, 0(s0) \# Writes to $v00 vaddc $v00, $v01 \# Reads from $v00 => STALL (3 cycles) The instructions `ltv`/`stv` are unique in the fact that they read/write 8 vector registers at a time. Their latency is still 4 cycles, and the stall is caused by an access to any of the 8 involved registers: ltv $v08, 0(s0) # Writes to $v08-$v15 vaddc $v00, $v11 # Reads from $v11 => STALL (3 cycles) ### Pipeline stalls: branches Branches run in 3 cycles (including the delay slot): * One cycle for the branch instruction * One cycle for the delay slot * One cycle of delay ("pipeline bubble") for internally finalizing the branch. Example: and t0, 1 \# Cycle 0 bnez t0, label \# Cycle 1 vmulf $v00, $v01, $v02 \# Cycle 2 label: addiu a0, 1 \# Cycle 4 (cycle 3 was the bubble) Notice that if either the branch instruction or the delay slot causes stalls themselves, these will just delay the branch bubble, it is not "absorbed" by the other stalls: lw a0, (s0) \# Cycle 0 bnez t0, label \# Cycle 1 addiu a0, 8 \# Cycle 2-3 (1 stall because a0 wasn't ready yet) label: addiu a0, 1 \# Cycle 5 (cycle 4 was the bubble) ### Pipeline stalls: stores after loads A stall is generated any time a memory store follows **exactly** 2 cycles after a memory load, irrespective of what instructions they are, or what registers they do affect. For instance: lw t0, 0(s0) nop sqv $v04, 0(s1) \# STALL: write happening two cycles after load As an additional special case, the instructions `mfc0`, `mtc0`, `mfc2`, `mtc2` , `cfc2`, `ctc2` do not access memory, but they also cause the same exact stall, and they even count as **both** loads and stores, irrespective of the behavior of the actual instruction. For instance: lw t0, 0(s0) nop mtc0 v0, COP0\_SP\_STATUS \# STALL: mtc0 happening two cycles after load lw t0, 0(s0) nop mfc0 v0, COP0\_SP\_STATUS \# STALL: mfc0 happening two cycles after load (even if "mfc0" seems itself a load...) mtc2 t0, $v04.e2 nop sw t8, 4(s1) \# STALL: "sw" happening two cycles after "mtc2" mtc2 t0, $v04.e0 nop cfc2 v0, COP2\_VCC \# STALL: cfc2 happening two cycles after mtc2 ### Dual-issue Given that VU and SU mostly run in parallel, RSP is able to run **two instructions in just 1 clock cycle**. This happens when a SU and a VU instructions are run next to each other (though not always, see below): vmudh $v02, $v04, $v06.e7 add t0, t1 \# DUAL-ISSUE (SU after VU) In normal cases, the order does not matter: add t0, t1 vmudh $v02, $v04, $v06.e7 \# DUAL-ISSUE (VU after SU) In general, most optimized RSP code will try to interleave SU and VU instructions as much as possible to benefit from dual-issue. There are a few cases where it is not possible to dual-issue: * After a branch: the first instruction on the target of a branch can dual-issue only if it is 8-byte aligned. When writing a hot loop, make sure the loop start (target of the end-loop branch) is 8-byte aligned so that you don't lose the dual-issue opportunity on the first instruction. * The delay slot of a branch never dual-issue (whether the branch is taken or not). * If the first instruction of a pair **writes** to a vector register that is **either read or written** by the second instruction, the pair will not dual-issue. Since we are discussing vector registers here, this applies when the SU instruction is one of the few that access vector registers (that is, vector loads, `mfc2`, `mtc2`) . vand $v04, $v30, $v31 lqv $v04, 0(s0) \# NO DUAL ISSUE with previous op: writes to $v04 which was also written by vand vand $v04, $v30, $v31 mfc2 t0, $v04.e4 \# NO DUAL ISSUE with previous op: reads from $v04 which was written by vand Notice that in this case, it would be sufficient for the instructions to appear in the opposite order to dual-issue (though the semantic would be different, but just for the sake of pipeline analysis): mfc2 t0, $v04.e4 vand $v04, $v30, $v31 \# DUAL ISSUE with previous op: the previous instruction only reads from $v04 * Similarly, CFC2/CTC2 (SU instructions) can prevent dual-issue if they access a control register that is read/written by the instruction they dual-issue with. In this case, the RSP is a bit overbroad because VU instructions that access the control register are counted as both reading and writing it, even though they only read them. ctc2 t0, VCO vadd $v00, $v01, $v02 \# NO DUAL ISSUE: vadd reads from VCO, which was written by the ctc2 vmrg $v00, $v01, $v02 \# NOTE: vmrg only reads from VCC ctc2 t0, VCC \# NO DUAL-ISSUE: when pairing with ctc2, first instruction is treated as if it was also writing to VCC ### Dual-issue and stalls The RSP decides which pairs of instructions to dual-issue without any consideration to stalls, just by "looking" at the instruction stream. At the point at which the dual-issued instructions actually run, either of them (or both) could be subject to any of the stalls described above. In this case, **both** dual-issued instructions are delayed to account for the stalls. If both instructions must be stalled, they will be delayed by the longest amount until both of them do not require stalls anymore. lqv $v20\[0\], 0(s0) \# Cycle 1 addi t1, 1 \# Cycle 2 addi t2, 2 \# Cycle 5: DUAL-ISSUE with next vadd $v20, $v20, $v21 \# Cycle 5: DUAL-ISSUE with prev, causes 2 stalls addi t3, 3 \# Cycle 6 ### Dual-issue: hardware bug with single-lane instructions There is a hardware bug in RSP that prevents dual-issuing when using single-lane instructions as second instruction of the pair in special cases. The single-lane instructions affected by this bug are: `VRCP`, `VRCPL`, `VRCPH`, `VMOV`, `VRSQ`, `VRSQL`, `VRSQH`, `VNOP`. The bug is related to the interpretation of the `de` field of the opcode, which is treated as a register number (rather than an element modifier) for the purpose of checking read/write conflicts. This is better explained with an example: mtc2 t0, $v04.e2 vrcp $v01.e4, $v02.e7 \# NO DUAL ISSUE: hardware bug: "e4" wrongly treated as a reference to "$v04" The above code would normally dual-issue. The first instruction writes to VU register $v04, while the second instruction reads and writes from different registers ($v01 and $v02), so in theory there should be no conflict. Unfortunately, the hardware bug triggers here: the field in the opcode that encodes "e4" is misinterpreted by the RSP internal dual-issue conflict logic, and it believes that the instruction references "$v04" instead: so it does create a conflict with the previous one, that prevents dual-issue. Notice that using the modern GCC syntax (`.e0` - `.e7`), it is possible to create conflicts only with registers `$v00` - `$v07`. At the hardware level, though, it is possible to specify a full 5-bit index number (from 0 to 31), which is exposed via the old SGI syntax. For instance, these instructions use different opcodes encoding but produce exactly the same result: vrcp $v01\[e4\], $v02\[e7\] vrcp $v01\[e12\], $v02\[e7\] \# Same as $v01.e4 vrcp $v01\[e20\], $v02\[e7\] \# Same as $v01.e4 vrcp $v01\[e28\], $v02\[e7\] \# Same as $v01.e4 So a solution to manually workaround the dual-issue conflict is to switch to a different element encoding: mtc2 t0, $v04.e2 vrcp $v01\[e12\], $v02\[e7\] \# DUAL ISSUE: will still write to $v01.e4, but the hardware bug sees it as a fake reference to $v12 Retrieved from "[https://n64brew.dev/wiki/Reality\_Signal\_Processor/CPU\_Pipeline?oldid=5618](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline?oldid=5618) " --- # Reality Display Processor/Interface - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#) Reality Display Processor/Interface =================================== < [Reality Display Processor](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") The RDP interface is the set of registers that allow to control the RDP and make it perform the required rasterization jobs. RDP executes a stream of commands that are sent to it via DMA. The RDP interface allows to initiate and monitor the DMA transfers to RDP, and to query the current status of RDP. Contents -------- * [1 DMA transfers](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DMA_transfers) * [1.1 Incremental transfers](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#Incremental_transfers) * [1.2 Double buffering](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#Double_buffering) * [1.3 Programming considerations](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#Programming_considerations) * [2 RDP Interface Registers](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#RDP_Interface_Registers) * [2.1 0x0410 0000 (c8) - DPC\_START](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0410_0000_(c8)_-_DPC_START) * [2.2 0x0410 0004 (c9) - DPC\_END](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0410_0004_(c9)_-_DPC_END) * [2.3 0x0410 0008 (c10) - DPC\_CURRENT](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0410_0008_(c10)_-_DPC_CURRENT) * [2.4 0x0410 000C (c11) - DPC\_STATUS](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0410_000C_(c11)_-_DPC_STATUS) * [2.5 0x0410 0010 (c12) - DPC\_CLOCK](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0410_0010_(c12)_-_DPC_CLOCK) * [2.6 0x0410 0014 (c13) - DPC\_CMD\_BUSY](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0410_0014_(c13)_-_DPC_CMD_BUSY) * [2.7 0x0410 0018 (c14) - DPC\_PIPE\_BUSY](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0410_0018_(c14)_-_DPC_PIPE_BUSY) * [2.8 0x0410 001C (c15) - DPC\_TMEM\_BUSY](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0410_001C_(c15)_-_DPC_TMEM_BUSY) * [2.9 0x0420 0000 - DPS\_TBIST](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0420_0000_-_DPS_TBIST) * [2.10 0x0420 0004 - DPS\_TEST\_MODE](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0420_0004_-_DPS_TEST_MODE) * [2.11 0x0420 0008 - DPS\_BUFTEST\_ADDR](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0420_0008_-_DPS_BUFTEST_ADDR) * [2.12 0x0420 000C - DPS\_BUFTEST\_DATA](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#0x0420_000C_-_DPS_BUFTEST_DATA) DMA transfers ------------- DMA transfers allow to send a sequence of commands from RDRAM or DMEM to the RDP. When a DMA is triggered the RDP will start fetching commands in small batches into an internal FIFO queue, from which they will get run; the DMA will then wait for space to become available in this internal command FIFO before more data can be transferred. Commands can be stored in either RDRAM or DMEM. Bit 0 of DPC\_STATUS is used to select whether RDRAM or DMEM is used. When reading data from DMEM, the RDP uses an internal bus in the RCP called XBUS, so normally "using XBUS" is a shorthand expression for "programming the RDP to fetch commands from DMEM". Notice that both VR4300 and RSP can program the RDP to either use XBUS or not; there is no correlation between the CPU programming the RDP and the data source being used. The RDP is a deeply pipelined unit. There is thus no correlation between a DMA being finished and the respective commands being finished (e.g. pixels drawn into the framebuffer). Instead, the end of DMA can just be used as a signal that the RDRAM/DMEM buffer that stores the commands can be recycled, but no further information can be deduced regarding the actual execution of the commands. A few synchronization commands can be used to guarantee that work will have been completed by the RDP before the next command begins execution; see the page on RDP commands for more details. RDP commands are composed of one or more 64-bit (8 byte) words. For this reason, RDP DMA must fetch data from an address that is 64-bit aligned: in fact, the lowest 3 bits of the DMA address register are ignored. There is no destination register: the destination is the RDP itself and its internal command FIFO is not addressable in any way. ### Incremental transfers To allow the RDP to begin processing commands as soon as they are available (that is, while the VR4300 and RSP are generating them), the RDP DMA allows for incremental transfers; the DPC\_END register can be updated while a DMA is in progress (or after it has finished) and the effect is that the DMA will continue running until the new end pointer is reached. This operation is totally safe and free of race conditions. The intended purpose is that the VR4300 or the RSP can continue updating the DPC\_END register while they add more data to the command buffer in RDRAM/DMEM until it is full, at which point they can start another transfer to switch to another buffer. ### Double buffering DMA registers are double-buffered: this means that it is possible to program a new DMA transfer while another one is in progress. A new DMA transfer in this context means starting again from another buffer: we do not consider incremental transfers described above as "new transfers". To program a pending DMA transfer, just write to `DPC_START`/`DPC_END` a new buffer start/end address. The `START_PENDING` / `END_PENDING` bits in `DPC_STATUS` will be set to 1, signaling that a transfer is indeed pending. New writes to `DPC_END` will now update the pending transfer; in other words, after a new DMA transfer is pending, it is not possible to incrementally add more commands to the currently-running transfer. ### Programming considerations The choice between using XBUS or not is an open debate. There is no clear cut answer and it should be carefully considered depending on the expected performance implications: * If commands are already in RDRAM (eg: a static display list of RDP commands, read from ROM), then it is obviously more efficient to send them directly from there, without copying them first to DMEM. Libultra does not support this (in libultra, all RDP commands are always passed through RSP as they were RSP commands first, causing a double memory bandwidth impact if they are then sent back to RDRAM for RDP DMA); in libdragon, this is supported via [rdpq\_exec](https://github.com/DragonMinded/libdragon/blob/caf684a06096afa3e9fc8ec8e70dd6643dab419e/include/rdpq.h#L1346-L1364) . * Symmetrically, short display lists of RDP commands can be already available in RSP DMEM (as part of the data segment of a RSP microcode). In this case, pushing them directly to RDP via XBUS is surely the fastest option. * If commands are generated by the RSP (eg: triangles at the end of a T&L pipeline), consider the following aspects: * sending back all the commands to RDRAM will have an impact on memory bandwidth (first, to transfer them from DMEM to RDRAM, and later from RDRAM to RDP). Memory bandwidth is often a bottleneck on N64. * on the other hand, RDRAM allows for much larger buffers. When the buffers are small (like they typically are in DMEM), it means that the RSP could be forced to wait for the RDP to process the commands before producing new ones (basically this is back-pressure from RDP to RSP), and in turns it could cause a back-pressure on the VR4300. Often, RDP is the slowest among the three, so a larger buffer allows for better pacing. While preparing buffers on RDP commands, it is useful to take advantage of incremental transfers. This is a possible algorithm: 1. Prepare two buffers (in either DMEM or RDRAM). 2. Get ready to send the first buffer by setting `DPC_START` = `DPC_END` = pointer to the start of the first buffer. This will not actually transfer any byte (remember DPC\_END is an _exclusive_ bound, so if you set `DPC_START` = `DPC_END`, this means "0 byte buffer"), but will setup the DMA engine as such. 3. Generate RDP commands into the first buffer (assuming this is RSP, depending whether you are using XBUS or not, either just write them to DMEM, or also DMA them to RDRAM into the first buffer). Any time a new command is added to the buffer, write `DPC_END` to point past it. This basically tells the RDP that there are more commands to run, as soon as it is ready. 4. When the buffer is full, go back to point 2, switching to the next buffer. Notice that the RDP DMA on the first buffer will continue running until all commands have been fetched, so the new buffer will be effectively pending at this point. Anyway, you can continue working on the new buffer and keep writing `DPC_END`: this is totally race-free, whether the new transfer is still pending, is ongoing, or even if it is finished. 5. Consider that the RDP can only have one transfer pending. So anytime you write `DPC_START` to switch to a new buffer, first check if another transfer is already pending (by checking if the `START_PENDING` bit is set in `DPC_STATUS`). If it is pending, then you will need to wait for it. This also makes sure you don't start pushing new commands into the first buffer again, before the previous contents have been fully consumed. Another possible approach to push commands into the RDP is using a single buffer, and checking `DPC_CURRENT` to race against the DMA. The idea is using the buffer as a circular one, and have the DMA constantly trailing behind our write pointer. 1. Prepare a single buffer (in either DMEM or RDRAM). Write `DPC_START` = `DPC_END` = pointer to the start of the buffer. Notice that, as soon as the RDP accepts these register writes, `DPC_CURRENT` will also point there when read. 2. Generate RDP commands and write them into the buffer. Any time a new command is written, update `DPC_END`. At this point, there are no pending DMAs (`START_PENDING` = 0), and in general we will have that `DPC_START` <= `DPC_CURRENT` <= `DPC_END`. To visualize this, remember that `DPC_CURRENT` is basically the "read pointer", while `DPC_END` is our "write pointer", within the same circular buffer. 3. When we reach the end of the buffer, schedule a new DMA transfer on the same buffer from the beginning (so again `DPC_START` = `DPC_END` = pointer to the start of the buffer). At this point, this second transfer will be pending (`START_PENDING` = 1), but the RDP DMA will probably be still going through the buffer on the first time. So at this point we have `DPC_START` <= `DPC_END` < `DPC_CURRENT`. Notice in fact that reading `DPC_START` and `DPC_END` will return the _pending_ values (the new run on the buffer), while `DPC_CURRENT` will still report the currently running transfer, and will keep going until the end of the buffer. 4. Keep writing commands from the start of the buffer. This time, though, make sure that you never write past the current value of `DPC_CURRENT`. If you need to write a command but you have reached the current value of `DPC_CURRENT`, it means that you risk overwriting commands that have not been sent to RDP yet. So in this case, you will need to throttle (wait) for a bit. 5. As soon as the RDP has finished going through the buffer, it will run the pending transfer and thus start from the beginning of the buffer again. After this happens (you can check it with `START_PENDING` becoming 0), you can freely go through the buffer writing commands, without checking `DPC_CURRENT` anymore. In fact, at this point we are back to the initial situation in which `DPC_START` <= `DPC_CURRENT` <= `DPC_END` so it is possible to keep writing until the end of the buffer. In general, the second algorithm is more complex and requires a bit more code to be implemented, but it allows for less throttling and more efficient use of the memory. In fact, in the first scenario, whenever we have filled the available memory (two buffers) and we throttle, we will need to wait until the RDP finishes processing the whole first buffer. In the second scenario, instead, throttling is much reduced because as soon as the RDP processes one command, we get room for one more command to write. RDP Interface Registers ----------------------- The RDP interface registers are memory mapped into the VR4300 physical address space starting from `0x0410 0000`. Normally, accesses are performed through the virtual uncached segment, so at `0xA410 0000`. The exact same physical registers are also exposed as COP0 registers to RSP itself, and can thus be accessed using the `MTC0` / `MFC0` opcodes. Since access to all registers is shared by VR4300 and RSP, special care must be taken while writing software to decide who is in charge of each different resource / feature. For instance, normally DMA operations are performed by either the CPU or the RSP only; if the software architecture requires both to issue DMA transfers, some kind of mutex protocol must be established (for instance, using either the SIG bits in the `SP_STATUS` register, or the `SP_SEMAPHORE` register). | | | | | | --- | --- | --- | --- | | VR4300 address | RSP COP0 register | Name | Description | | 0x0410 0000 | c8 | [DPC\_START](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DPC_START) | Start address in RDRAM / DMEM for a DMA transfer of RDP commands | | 0x0410 0004 | c9 | [DPC\_END](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DPC_END) | End address in RDRAM / DMEM for a DMA transfer of RDP commands (exclusive bound) | | 0x0410 0008 | c10 | [DPC\_CURRENT](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DPC_CURRENT) | Current address in RDRAM / DMEM being transferred by the DMA engine | | 0x0410 000C | c11 | [DPC\_STATUS](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DPC_STATUS) | Status register | | 0x0410 0010 | c12 | [DPC\_CLOCK](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DPC_CLOCK) | | | 0x0410 0014 | c13 | [DPC\_BUF\_BUSY](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DPC_BUF_BUSY) | | | 0x0410 0018 | c14 | [DPC\_PIPE\_BUSY](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DPC_PIPE_BUSY) | | | 0x0410 001C | c15 | [DPC\_TMEM\_BUSY](https://n64brew.dev/wiki/Reality_Display_Processor/Interface#DPC_TMEM_BUSY) | | The registers mirror every 0x20 bytes across the whole range `0x0410'0000` - `0x041F'FFFF`. #### 0x0410 0000 (c8) - DPC\_START * * * | DPC\_START `0x0410 0000` (`c8`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | START\[23:16\] | | | | | | | | | 15:8 | RW-? | RW-? | RW-? | RW-? | RW-? | RW- | RW-? | RW-? | | START\[15:0\] | | | | | | | | | 7:0 | RW-? | RW-? | RW-? | RW-? | RW-? | U-0 | U-0 | U-0 | | START\[7:0\] | | | | | | | | | | | | --- | --- | | bit 23-0 | **START\[23:0\]:** Physical address of the start of the command list in RDRAM or DMEM. When reading, it always returns the last written value. | **Extra Details:** **START** This address points to the beginning of the command list from which RDP commands will be fetched by the DMA. After writing this register, the address is latched into the RDP interface, and the `START_PENDING` bit in `DPC_STATUS` becomes 1, but no transfer is started. Writing `DPC_END` will actually initiate the transfer. Selection of the data source (RDRAM or DMEM) is controller by bit 0 of `DPC_STATUS`. Writing `DPC_START` while another value is pending (`START_PENDING` is 1) will update the pending value. Notice though that this is a risky operation because of races: the pending transfer could in fact start at any point, and if you write a new pending value just before or just after the transfer starts, the behavior will be totally different; in general, it is better to avoid writing `DPC_START` if `START_PENDING` is 1. #### 0x0410 0004 (c9) - DPC\_END * * * | DPC\_END `0x0410 0004` (`c9`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | END\[23:16\] | | | | | | | | | 15:8 | RW-? | RW-? | RW-? | RW-? | RW-? | RW- | RW-? | RW-? | | END\[15:0\] | | | | | | | | | 7:0 | RW-? | RW-? | RW-? | RW-? | RW-? | U-0 | U-0 | U-0 | | END\[7:0\] | | | | | | | | | | | | --- | --- | | bit 23-0 | **END\[23:0\]:** Physical address of the end of the command list (in RDRAM or DMEM). When reading, it always returns the last written value. | **Extra Details:** **END** This address points to the end of the command list. The address is interpreted as an exclusive bound, so it must point _after_ the last command to transfer. Notice that writing `DPC_START`\=`DPC_END` is well formed, and will run a perfectly valid zero byte transfer (which can later extended via an incremental transfer). When `DPC_END` is written, the RDP does the following: * If `START_PENDING` (in `DPC_STATUS`) is 0, the write is considered an "incremental transfer", so the RDP DMA is programmed to continue the last transfer up to the new value of `DPC_END`. This works whether the previous transfer is still running or was already finished; in both cases, the transfer is continued/restored until the new `DPC_END` is reached; * If `START_PENDING` (in `DPC_STATUS`) is 1, the behavior depends on whether a transfer is running or not: * if no transfer is running, the new transfer is started (from `DPC_START` to `DPC_END`), and `START_PENDING` goes back to 0. * if a transfer is in progress, `END_PENDING` is set to 1 and the new transfer remains pending and will start as soon as the current transfer is finished. Further writes to `DPC_END` in this state will simply update the pending transfer's end address. **WARNING**: Do not start a DMA transfer or even process any command if you have previously enqueued a `SYNC_FULL` command. There is a RDP hardware bug that makes RDP sometimes crash if any other command is processed while `SYNC_FULL` is run. Thus, when you schedule a `SYNC_FULL` (usually at the end of the frame), it must be the last scheduled command (`DPC_END` must point immediately after it), and you must wait until the RDP has processed it and got back to fully idle status (`BUSY` bit goes to 0 in `DPC_STATUS`), before starting a new DMA transfer, even just an incremental one. It is fine to just write `DPC_START` though, as that doesn't start a transfer. #### 0x0410 0008 (c10) - DPC\_CURRENT * * * | DPC\_CURRENT `0x0410 0008` (`c10`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CURRENT\[23:16\] | | | | | | | | | 15:8 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CURRENT\[15:0\] | | | | | | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CURRENT\[7:0\] | | | | | | | | | | | | --- | --- | | bit 23-0 | **CURRENT\[23:0\]:** Read the current address being transferred by DMA. | **Extra Details:** **CURRENT** This address points after the last command that was transferred by DMA. It is possible to monitor this register to know how far the transfer has gone. In general, it is expected that `DPC_START` <= `DPC_CURRENT` <= `DPC_END`, and thus the portion of the buffer between `DPC_START` and `DPC_CURRENT` is free to be recycled for other uses. When a transfer is finished, `DPC_CURRENT` will always be equal to `DPC_END`. Notice that when `START_PENDING` or `END_PENDING` are 1, reading `DPC_START` and `DPC_END` will return the pending values, while reading `DPC_CURRENT` will always refer to the currently running (or last finished) transfer. #### 0x0410 000C (c11) - DPC\_STATUS * * * | DPC\_STATUS `0x0410 000C` (`c11`) - Read access | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | R-? | R-? | R-? | | — | — | — | — | — | START\_PENDING | END\_PENDING | DMA\_BUSY | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CBUF\_READY | CMD\_BUSY | PIPE\_BUSY | TMEM\_BUSY | GCLK | FLUSH | FREEZE | XBUS | | | | | --- | --- | | bit 10 | **START\_PENDING:** Set if DPC\_START was written and the value is still pending because DPC\_END was not written yet, or another transfer is in progress (see DPC\_START and DPC\_END). | | bit 9 | **END\_PENDING:** Set if DPC\_END was written and the value is still pending because another transfer is in progress (see DPC\_END) | | bit 8 | **DMA\_BUSY:** ? | | bit 7 | **CBUF\_READY:** ? | | bit 6 | **CMD\_BUSY:** 1 while the RDP is processing a command, 0 when it is idle. See the DPC\_CMD\_BUSY register for more information. | | bit 5 | **PIPE\_BUSY:** Becomes 1 as soon as a DMA transfer starts, and stays to 1 until a `SYNC_FULL` command is run. See the DPC\_PIPE\_BUSY register for more information. | | bit 4 | **TMEM\_BUSY:** 1 while the RDP is loading bytes to TMEM, 0 when it is idle (or stalled for RDRAM). See DPC\_TMEM\_BUSY for more information. | | bit 3 | **GCLK:** This bit is the main gated clock for the whole RDP pipeline; when it is 0, the pipeline is paused. The RDP stops this clock any time it is stalled for RDRAM. In other words, it behaves similar to PIPE\_BUSY, but it also goes to zero any time the RDP is stalled for RDRAM. | | bit 2 | **FLUSH:** While set, all RDP transfers in progress or started are immediately terminated | | bit 1 | **FREEZE:** While set, RDP will stop processing commands | | bit 0 | **XBUS:** 0: DMA transfer source is RDRAM; 1: DMA transfer source is DMEM (via XBUS) | | DPC\_STATUS `0x0410 000C` (`c11`) - Write access | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | W-? | W-? | | — | — | — | — | — | — | CLR\_CLOCK | CLR\_BUFFER\_BUSY | | 7:0 | W-? | W-? | W-? | W-? | W-? | W-? | W-? | W-? | | CLR\_PIPE\_BUSY | CLR\_TMEM\_BUSY | SET\_FLUSH | CLR\_FLUSH | SET\_FREEZE | CLR\_FREEZE | SET\_XBUS | CLR\_XBUS | | | | | --- | --- | | bit 9 | **CLR\_CLOCK:** Reset **DPC\_CLOCK** to zero. | | bit 8 | **CLR\_BUFFER\_BUSY:** Reset **DPC\_BUSY** to zero. | | bit 7 | **CLR\_PIPE\_BUSY:** Reset **DPC\_PIPE\_BUSY** to zero. | | bit 6 | **CLR\_TMEM\_BUSY:** Reset **DPC\_TMEM\_BUSY** to zero. | | bit 5 | **SET\_FLUSH:** Set the FLUSH bit to 1 | | bit 4 | **CLR\_FLUSH:** Clear the FLUSH bit to 0 | | bit 3 | **SET\_FREEZE:** Set the FREEZE bit to 1 | | bit 2 | **CLR\_FREEZE:** Clear the FREEZE bit to 0 | | bit 1 | **SET\_XBUS:** Set the XBUS bit to 1 | | bit 0 | **CLR\_XBUS:** Clear the XBUS bit to 0 | **Extra Details:** **FREEZE** During freeze, the RDP DMA engine is suspended (paused). If a transfer was ongoing, it is paused and will resume as soon as the freeze bit is reset to 0. During the freeze, it is still possible to write DPC\_START or DPC\_END, and the writes will still affect the START\_PENDING / END\_PENDING bits, but no transfer will be initiated. **FLUSH** While FLUSH is set, all DMA transfers are instantly terminated (flushed). Pulsing the FLUSH bit is a good way to force-reset the RDP DMA engine and make sure the RDP is ready to initiate a new transfer. **BUSY** This bit seems to refer to a more general state of RDP, which is not related to actually performing any task. When the bit is 0, the RDP is like "turned off". As long as a single command is sent to RDP, this bit goes to 1 and will stay there even if after that single command, you wait for seconds, far beyond the actual execution time of that command. It looks like the RDP is now "turned on". To turn it off again, the only known way is sending a SYNC\_FULL. After processing a SYNC\_FULL, the BUSY bit goes back to 0 again. #### 0x0410 0010 (c12) - DPC\_CLOCK * * * | DPC\_CLOCK `0x0410 0010` (`c12`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CLOCK\[23:16\] | | | | | | | | | 15:8 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CLOCK\[15:0\] | | | | | | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CLOCK\[7:0\] | | | | | | | | | | | | --- | --- | | bit 23-0 | **CLOCK\[23:0\]:** 24-bit counter running at RCP frequency | **Extra Details:** **CLOCK** This register accesses a read-only 24-bit clock that runs at the RCP frequency (which is 62.5 Mhz on standard N64, and 96 Mhz on iQue). The counter starts ticking from boot and does not stop (not even if you freeze the RDP via the \`FREEZE\` bit in \`DPC\_STATUS\`). The only possible interaction from the CPU/RSP is to reset it to 0 by writing the \`CLR\_CLOCK\` bit in \`DPC\_STATUS\`. Being the only counter available directly from RSP, it can be useful to perform benchmarks on it. #### 0x0410 0014 (c13) - DPC\_CMD\_BUSY * * * | DPC\_CMD\_BUSY `0x0410 0014` (`c13`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CMD\_BUSY\[23:16\] | | | | | | | | | 15:8 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CMD\_BUSY\[15:8\] | | | | | | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | CMD\_BUSY\[7:0\] | | | | | | | | | | | | --- | --- | | bit 23-0 | **CMD\_BUSY\[23:0\]:** 24-bit counter of actual RDP command activity at RCP frequency | **Extra Details:** **CMD\_BUSY** This register accesses a read-only 24-bit clock that counts up any RCP cycle in which the internal RDP command FIFO is not empty. The FIFO is filled via RDP DMA with new commands, and is flushed as commands get executed, so this counter is the closest thing to a "RDP activity counter". This counter is connected to the CMD\_BUSY bit in DPC\_STATUS, so it basically counts how many cycle that bit is set to 1. #### 0x0410 0018 (c14) - DPC\_PIPE\_BUSY * * * | DPC\_PIPE\_BUSY `0x0410 0018` (`c14`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | PIPE\_BUSY\[23:16\] | | | | | | | | | 15:8 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | PIPE\_BUSY\[15:0\] | | | | | | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | PIPE\_BUSY\[7:0\] | | | | | | | | | | | | --- | --- | | bit 23-0 | **PIPE\_BUSY\[23:0\]:** 24-bit counter of RDP pipeline activation time at RCP frequency | **Extra Details:** **PIPE\_BUSY** This register accesses a read-only 24-bit clock that counts up only in the intervals of time between the first command being received via RDP DMA, and the subsequent SYNC\_FULL command. Basically, this counter shows the RDP activity "gross time", because it keeps counting even if the RDP has nothing to do, until SYNC\_FULL is received. This counter is connected to the PIPE\_BUSY bit in DPC\_STATUS, so it basically counts how many cycle that bit is set to 1. #### 0x0410 001C (c15) - DPC\_TMEM\_BUSY * * * | DPC\_TMEM\_BUSY `0x0410 001C` (`c15`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | TMEM\_BUSY\[23:16\] | | | | | | | | | 15:8 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | TMEM\_BUSY\[15:0\] | | | | | | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | TMEM\_BUSY\[7:0\] | | | | | | | | | | | | --- | --- | | bit 23-0 | **TMEM\_BUSY\[23:0\]:** 24-bit counter of RDP TMEM activity time | **Extra Details:** **TMEM\_BUSY** This register accesses a read-only 24-bit clock that counts up any RCP cycle in which the RDP is loading the TMEM. Notice that the counter is stopped during cycles in which the RDP is stalled for RDRAM, so it is basically a "net" texture loading time. This counter is connected to the TMEM\_BUSY bit in DPC\_STATUS, so it basically counts how many cycle that bit is set to 1. #### 0x0420 0000 - DPS\_TBIST * * * | DPS\_TBIST `0x0420 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | R-? | R-? | R-? | | — | — | — | — | — | FAIL\[7:5\] | | | | 7:0 | R-? | R-? | R-? | R-? | R-? | RW-? | RW-? | RW-? | | FAIL\[4:0\] | | | | | DONE | GO | CHECK | | | | | --- | --- | | bit 10-3 | **FAIL\[7:0\]:** ? | | bit 2 | **DONE:** ? | | bit 1 | **GO:** ? | | bit 0 | **CHECK:** ? | #### 0x0420 0004 - DPS\_TEST\_MODE * * * **When Reading:** | DPS\_TEST\_MODE `0x0420 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | R-? | R-? | R-? | R-? | | 0 | | | | cspan0\[3:0\] | | | | | 23:16 | R-? | R-? | R-? | R-? | R-? | R-? | R-? | R-? | | cspan1\[3:0\] | | | | zspan0\[3:0\] | | | | | 15:8 | R-? | R-? | R-? | R-? | U-? | U-? | U-? | U-? | | zspan1\[3:0\] | | | | ? | ? | ? | ? | | 7:0 | U-1 | U-? | U-? | U-? | U-? | U-1 | U-? | R-1 | | 1 | ? | ? | ? | ? | 1 | ? | TEST\_ENABLE | | | | | --- | --- | | bit 31-28 | **0:** Always 0? | | bit 27-24 | **cspan0\[3:0\]:** Color span counter? Increments (and potentially overflows) based on how many 16-byte segments a drawn primitive covers. | | bit 23-20 | **cspan1\[3:0\]:** Color span counter? Synced with the other counter; if the other counter has the msbit unset this one has it set and vice-versa. | | bit 19-16 | **zspan0\[3:0\]:** Depth span counter? Increments (and potentially overflows) based on how many 16-byte segments a drawn primitive covers, only when depth read or write is enabled. | | bit 15-12 | **zspan1\[3:0\]:** Depth span counter? Synced with the other counter; if the other counter has the msbit unset this one has it set and vice-versa. | | bit 7 | **1:** Always 1? | | bit 2 | **1:** Always 1? | | bit 0 | **TEST\_ENABLE:** Whether span buffer testing is enabled. | **When Writing:** | DPS\_TEST\_MODE `0x0420 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | W-1 | | — | — | — | — | — | — | — | TEST\_ENABLE | | | | | --- | --- | | bit 0 | **TEST\_ENABLE:** Enables span buffer test access via **DPS\_BUFTEST\_ADDR** and **DPS\_BUFTEST\_DATA**. **Warning:** If the span test mode is used the RDP should be idle, else the RDP may hang. | #### 0x0420 0008 - DPS\_BUFTEST\_ADDR * * * | DPS\_BUFTEST\_ADDR `0x0420 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | | | | | | | | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | | | | | | | | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | | | | | | | | | 7:0 | U-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | — | ADDRESS\[6:0\] | | | | | | | | | | | --- | --- | | bit 6-0 | **ADDRESS\[6:0\]:** Sets the span buffer word address that **DPS\_BUFTEST\_DATA** will read from or write to. | Span buffers are 288 bytes of memory while a 7-bit word index can address up to 512 bytes; when some addresses are read/written with **DPS\_BUFTEST\_DATA** some bits are fixed to 0. The span buffer data is arrayed such that 72 bits are accessed over 4 word addresses. The data layout is shown below. The first two columns can contain color/depth/texture data while the third column can contain coverage. The first half of the rows up to 0x40 only hold color data while the second half of the rows from 0x40 onwards hold depth and texture data. Texture data is held in spans as it is loaded from RDRAM before being shuffled into TMEM. | 0 1 2 3 ----+------------------------------------ 0 | XXXXXXXX XXXXXXXX 000000XX 00000000 4 | XXXXXXXX XXXXXXXX 000000XX 00000000 8 | XXXXXXXX XXXXXXXX 000000XX 00000000 C | XXXXXXXX XXXXXXXX 000000XX 00000000 10 | XXXXXXXX XXXXXXXX 000000XX 00000000 14 | XXXXXXXX XXXXXXXX 000000XX 00000000 18 | XXXXXXXX XXXXXXXX 000000XX 00000000 1C | XXXXXXXX XXXXXXXX 000000XX 00000000 20 | XXXXXXXX XXXXXXXX 000000XX 00000000 24 | XXXXXXXX XXXXXXXX 000000XX 00000000 28 | XXXXXXXX XXXXXXXX 000000XX 00000000 2C | XXXXXXXX XXXXXXXX 000000XX 00000000 30 | XXXXXXXX XXXXXXXX 000000XX 00000000 34 | XXXXXXXX XXXXXXXX 000000XX 00000000 38 | XXXXXXXX XXXXXXXX 000000XX 00000000 3C | XXXXXXXX XXXXXXXX 000000XX 00000000 40 | XXXXXXXX XXXXXXXX 000000XX 00000000 44 | XXXXXXXX XXXXXXXX 000000XX 00000000 48 | XXXXXXXX XXXXXXXX 000000XX 00000000 4C | XXXXXXXX XXXXXXXX 000000XX 00000000 50 | XXXXXXXX XXXXXXXX 000000XX 00000000 54 | XXXXXXXX XXXXXXXX 000000XX 00000000 58 | XXXXXXXX XXXXXXXX 000000XX 00000000 5C | XXXXXXXX XXXXXXXX 000000XX 00000000 60 | XXXXXXXX XXXXXXXX 000000XX 00000000 64 | XXXXXXXX XXXXXXXX 000000XX 00000000 68 | XXXXXXXX XXXXXXXX 000000XX 00000000 6C | XXXXXXXX XXXXXXXX 000000XX 00000000 70 | XXXXXXXX XXXXXXXX 000000XX 00000000 74 | XXXXXXXX XXXXXXXX 000000XX 00000000 78 | XXXXXXXX XXXXXXXX 000000XX 00000000 7C | XXXXXXXX XXXXXXXX 000000XX 00000000 #### 0x0420 000C - DPS\_BUFTEST\_DATA * * * | DPS\_BUFTEST\_DATA `0x0420 000C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | DATA\[31:24\] | | | | | | | | | 23:16 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | DATA\[23:16\] | | | | | | | | | 15:8 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | DATA\[15:0\] | | | | | | | | | 7:0 | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | RW-? | | DATA\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-0 | **DATA:** Reading from this register reads the span buffer data at the current **DPS\_BUFTEST\_ADDR**. Writing to this register sets the span buffer data at the current **DPS\_BUFTEST\_ADDR** to the write value. | This register requires span buffer test access to be enabled in **DPS\_TEST\_MODE\_REG** to be functional. Retrieved from "[https://n64brew.dev/wiki/Reality\_Display\_Processor/Interface?oldid=5765](https://n64brew.dev/wiki/Reality_Display_Processor/Interface?oldid=5765) " --- # Lunar Assault 64 - N64brew Wiki [](https://n64brew.dev/wiki/Lunar_Assault_64#) Lunar Assault 64 ================ Contents -------- * [1 About](https://n64brew.dev/wiki/Lunar_Assault_64#About) * [2 Media](https://n64brew.dev/wiki/Lunar_Assault_64#Media) * [2.1 Screenshots](https://n64brew.dev/wiki/Lunar_Assault_64#Screenshots) * [2.2 Box Art](https://n64brew.dev/wiki/Lunar_Assault_64#Box_Art) * [3 Making the Game](https://n64brew.dev/wiki/Lunar_Assault_64#Making_the_Game) * [3.1 Before the Jam](https://n64brew.dev/wiki/Lunar_Assault_64#Before_the_Jam) * [3.2 Implementing the Game](https://n64brew.dev/wiki/Lunar_Assault_64#Implementing_the_Game) * [3.2.1 The Environment](https://n64brew.dev/wiki/Lunar_Assault_64#The_Environment) * [3.2.2 The Satellite Laser Attack](https://n64brew.dev/wiki/Lunar_Assault_64#The_Satellite_Laser_Attack) * [3.2.3 FMV Playback](https://n64brew.dev/wiki/Lunar_Assault_64#FMV_Playback) * [4 External Links](https://n64brew.dev/wiki/Lunar_Assault_64#External_Links) About ----- _Lunar Assault 64_ was my submission for the 2020 N64Brew Game Jam. During the game the player follows Wren Buster as they fell various Lunarbeasts on the moon. The game incorporates the theme of **size** by having large creatures to destroy, as well as a little protagonist's hopes and dreams in a big world. My big inspirations for the game were Sony PlayStation and Sega Saturn games, which usually mixed rudimentary 3D graphics with 2D art to create dramatic and emotional stories less-frequently seen on the Nintendo 64. I wanted to broaden and recontextualize the sort of game typically seen on the console. Media ----- ### Screenshots *  [](https://n64brew.dev/wiki/File:Lunar_Assault_64_Screenshot_4.png "Typical ingame perspective during a level") Typical ingame perspective during a level *  [](https://n64brew.dev/wiki/File:Lunar_Assault_64_Screenshot_1.png "While the player is zoomed in") While the player is zoomed in *  [](https://n64brew.dev/wiki/File:Lunar_Assault_64_Screenshot_2.png "Dialogue interlude") Dialogue interlude *  [](https://n64brew.dev/wiki/File:Lunar_Assault_64_Screenshot_3.png "Flavour text before starting a level") Flavour text before starting a level ### Box Art  [](https://n64brew.dev/wiki/File:Lunar_Assault_64_NTSC_Boxart.png) Making the Game --------------- ### Before the Jam I didn't have much of an interest in homebrew game development until I watched Rachel Simone Weil's [talk from MAGFest 2019](https://www.youtube.com/watch?v=FjGFkYRf8UI) . In the talk, Rachel puts forward the idea that a game console is a cultural artifact that can be examined and expanded on. Her words really resonated with me, as I felt like some of my feelings on the Nintendo 64 weren't shared with the public consciousness. This inspired me to attempt to develop a homebrew game that broadened the Nintendo 64's library. Going in, I knew that I didn't want my jam submission to be like a typical Nintendo 64 game. Many of the best-selling and well-remembered Nintendo 64 games took advantage of the console's unique hardware features such as antialiasing, z-buffering, and texture filtering. The cartridge medium of the console also put a higher price on storage and production compared to discs. I think that pushed a lot of Nintendo 64 games to be big-budget works that appealed to general audiences. It's easy to see how the reduced production costs of CDs on the Sony PlayStation enabled more esoteric titles with smaller print runs. Making a quirky and personal game for the jam seemed like a really good idea. I think the Sony PlayStation appealed to a lot of Gen-X players who grew up with Nintendo and Super Nintendo games. As those audiences got older, they appreciated the themes and experiences found in titles like _Final Fantasy VII_, _Metal Gear Solid_, or _LSD: Dream Emulator_. The Nintendo 64, meanwhile had games like _Mario Kart 64_ or _Pokemon Stadium_ which were rather geared for me when I was a young child. I wanted to inject some of my personal experiences as an adult into a console I played before I came of age. Visually, I knew I could turn off a lot of the Nintendo 64's trademark features. It was possible (and more performant!) to render triangles without z-buffering, antialiasing, and texture filtering. The SDK included the `gspF3DLP.Rej` microcode, which improved triangle-processing speed while skipping perspective correction. `gspF3DLP.Rej` also yielded a larger vertex buffer for 3D models. These features appealed to me, as a PlayStation-looking game might help distance my submission from something like _Super Mario 64_ or _Banjo-Kazooie_. I had also been inspired how games like _Crime Crackers_ and _Bulk Slash_ showcase a character's portrait alongside the 3D action. It can help inject a protagonist into something otherwise very gameplay-oriented. *  [](https://n64brew.dev/wiki/File:Bulk_Slash_gameplay.gif "Bulk Slash") Bulk Slash *  [](https://n64brew.dev/wiki/File:Crime_Crackers_gameplay.gif "Crime Crackers") Crime Crackers ### Implementing the Game  [](https://n64brew.dev/wiki/File:Lunar_Assault_64_Progress_Sticky_Notes.jpg) My to-do list for the jam submission. #### The Environment The game's levels are a 128x128 array of unsigned bytes. Each byte represents a height. Upon game start, the map iterates the heights and generates display lists of quads for the heights. The ground height is smoothed out for the four corners of the quds via bilinear interpolation.  [](https://n64brew.dev/wiki/File:Lunar_Assault_64_test_map.gif) I had some experience with three.js and Phaser in the past, so I wrote a small level editor in JavaScript that mimicked the display list generation. I was able to paint and shape with the mouse and then save them as a binary file to be included in the ROM. The "tiles" that make up the map also have an index of 0 to 15, which determine what section of a 32x32 texture to use. Only one load into TMEM happens for rendering the environment and actors. Wren and the Lunarbeasts are drawn with vertex colouring.  [](https://n64brew.dev/wiki/File:Level_Editor_for_Lunar_Assault_64_.gif) The environment has a lot of quads, so to improve performance z-buffering is disabled. The level was divided into 4x4 tile sections, where each tile has the vertices and rendering commands for an area. We don't sort the quads by position on render, but rather loop through rendering them based on the direction the player is facing. This isn't perfect and produces strange artifacts, but I think it contributes to a unique look for a Nintendo 64 game which normally would have a "clean" 3D look that matched the coming decade of games. Wren, the satellite laser, and Lunarbeasts are all rendered with Z-buffering enabled. The display commands switch between two cameras for precision purposes. A small, close one for z-buffered objects, and a larger, far one for the environment. #### The Satellite Laser Attack The Lunarbeasts and their limbs are made up of a display list, [a transform](https://en.wikipedia.org/wiki/Transformation_matrix) , and a [signed-distance field](https://en.wikipedia.org/wiki/Signed_distance_function) . Limbs could be parented onto other limbs and inherit their transform. They were more or less animated programmatically with interpolation and sine waves. Checking if the player had correctly struck a weak spot involved [raymarching](https://en.wikipedia.org/wiki/Volume_ray_casting) from the scope across the environment. Since limbs and hitboxes were transformed, the rays would be computed against a hitbox's inverse matrix as well as the inverses of its parents. This didn't always work right and and some precision problems, but overall I felt the gameplay was compelling enough. The game applies a slight amount of noise to all the colours in the environment, and that noise amount gets cranked up for a moment if the player is able to strike a weak spot. I did this to help give a "pop" feeling and imply that the player is looking through some sort of electronic display. The only downside of this effect is that it breaks a lot of Nintendo 64 emulators. #### FMV Playback FMV playback was expensive for Nintendo 64 games and wasn't very common. I knew that the SDK supplied a library to help implement this, and I wanted to try including it to introduce the premise of the game and help make it feel more unique. I quickly wrote out a premise for a minute's worth of dialogue in a text editor.  [](https://n64brew.dev/wiki/File:Lunar_Assault_64_Blender_Screenshot.png) I blocked out an animation for the game's intro in Blender. It was a lot more fun than debugging when the RCP would hang up! Since the final video was going to be rendered at 240p, I didn't really have to worry about too much detail. I don't exactly have the strongest voice for a narrator, so I called my Dad and asked him to record lines with a microphone from his home. It was a fun remote thing to do as the both of us were mostly indoors during the COVID-19 pandemic. After applying a little bit of reverb to his lines, they fit in with the video very nicely. When I was a young child, the [content of video games was a contentious issue](https://www.youtube.com/watch?v=lhwM3ZMTCR0) and something my parents were conscious and worried about. It felt oddly satisfying to have a recording of my Dad saying the word "shitty" immortalized into a Nintendo 64 ROM. I attempted initially have the video play back at 24 frames-per-second, but I found it tended to stress the decoder too much. CrashOverride was helpful in re-encoding and helping me understand the HVQM tools better. External Links -------------- [Playthrough of the game on N64Brew's discord channel](https://www.youtube.com/watch?v=4D9HI-nIohg) Retrieved from "[https://n64brew.dev/wiki/Lunar\_Assault\_64?oldid=5040](https://n64brew.dev/wiki/Lunar_Assault_64?oldid=5040) " --- # N64brew Game Jam 2020 - N64brew Wiki [](https://n64brew.dev/wiki/N64brew_Game_Jam_2020#) N64brew Game Jam 2020 ===================== The N64brew Game Jam 2020 was the first homebrew game jam organized by the N64brew community on Discord. It was announced on October 1, 2020, and the theme, **Size**, was revealed on October 8, 2020. The jam began on October 13, 2020, and ran for two months, concluding on December 13, 2020. [![The N64Brew Game Jam logo. The words "game jam" are illustrated as the label on a jam jar.](https://static.wikitide.net/n64wiki/e/ed/N64Brew_Game_Jam_Logo.png)](https://n64brew.dev/wiki/File:N64Brew_Game_Jam_Logo.png) After the development period ended, entries were judged across multiple categories. The top three entries received cash prizes funded by community donations. The winners were announced on December 21, 2020, followed by a video announcement posted to YouTube on December 24, 2020. Judges ------ | Name | External link(s) | | --- | --- | | Buu342 | [https://github.com/buu342](https://github.com/buu342) | | David Doak | [https://en.wikipedia.org/wiki/David\_Doak](https://en.wikipedia.org/wiki/David_Doak) | | Allan Findlay | [https://lscmainframe.kontek.net/features/afindlay.html](https://lscmainframe.kontek.net/features/afindlay.html) | | Snooplax | [https://www.youtube.com/channel/UCIajM7KQQ8wjOJ-aMnFealQ](https://www.youtube.com/channel/UCIajM7KQQ8wjOJ-aMnFealQ) | | Neil Voss | [https://en.wikipedia.org/wiki/Neil\_Voss](https://en.wikipedia.org/wiki/Neil_Voss) | Submissions ----------- | Entry | Solo/Team | Participant(s) | Engine/Framework | Source Code | External link(s) | | --- | --- | --- | --- | --- | --- | | [64noid](https://n64brew.dev/wiki/64noid?action=edit&redlink=1 "64noid (page does not exist)") | Solo | gamemasterplc | libultra | [https://github.com/N64brew-Game-Jam-2020/64noid](https://github.com/N64brew-Game-Jam-2020/64noid) | | | [Big Burger](https://n64brew.dev/wiki/Big_Burger?action=edit&redlink=1 "Big Burger (page does not exist)") | Solo | Allie | libultra | [https://github.com/N64brew-Game-Jam-2020/Big-Burger](https://github.com/N64brew-Game-Jam-2020/Big-Burger) | | | [Castle64](https://n64brew.dev/wiki/Castle64?action=edit&redlink=1 "Castle64 (page does not exist)") | Solo | manfried | libultra | [https://github.com/N64brew-Game-Jam-2020/Castle64](https://github.com/N64brew-Game-Jam-2020/Castle64) | | | [JUIC'N 64](https://n64brew.dev/wiki/JUIC%27N_64?action=edit&redlink=1 "JUIC'N 64 (page does not exist)") | Solo | kivan117 | libdragon | [https://github.com/N64brew-Game-Jam-2020/Juicn-64](https://github.com/N64brew-Game-Jam-2020/Juicn-64) | | | [Kumi-Daiko Beatoff 64](https://n64brew.dev/wiki/Kumi-Daiko_Beatoff_64?action=edit&redlink=1 "Kumi-Daiko Beatoff 64 (page does not exist)") | Team | Team Riistahillo | libultra | [https://github.com/N64brew-Game-Jam-2020/Kumi-Daiko-Beatoff-64](https://github.com/N64brew-Game-Jam-2020/Kumi-Daiko-Beatoff-64) | [https://zhamul.itch.io/kumi-daiko-beatoff-64](https://zhamul.itch.io/kumi-daiko-beatoff-64) | | [Lunar Assault 64](https://n64brew.dev/wiki/Lunar_Assault_64 "Lunar Assault 64") | Solo | danielface | libultra | [https://github.com/N64brew-Game-Jam-2020/Lunar-Assault-64](https://github.com/N64brew-Game-Jam-2020/Lunar-Assault-64) | [https://danbolt.itch.io/lunar-assault-64](https://danbolt.itch.io/lunar-assault-64) | | [Retro Dash](https://n64brew.dev/wiki/Retro_Dash?action=edit&redlink=1 "Retro Dash (page does not exist)") | Solo | SpiritOf1776 | libdragon | [https://github.com/N64brew-Game-Jam-2020/Retro-Dash](https://github.com/N64brew-Game-Jam-2020/Retro-Dash) | | | [Sblobber64](https://n64brew.dev/wiki/Sblobber64?action=edit&redlink=1 "Sblobber64 (page does not exist)") | Team | vrgl117 games | libdragon | [https://github.com/N64brew-Game-Jam-2020/Sblobber64](https://github.com/N64brew-Game-Jam-2020/Sblobber64) | | | [Shrunk in the Wash? Just add water!](https://n64brew.dev/wiki/Shrunk_in_the_Wash%3F_Just_add_water!?action=edit&redlink=1 "Shrunk in the Wash? Just add water! (page does not exist)") | Solo | joeldipops | libdragon | [https://github.com/N64brew-Game-Jam-2020/Just-Add-Water](https://github.com/N64brew-Game-Jam-2020/Just-Add-Water) | | | [Tecto](https://n64brew.dev/wiki/Tecto?action=edit&redlink=1 "Tecto (page does not exist)") | Team | Team Tecto | libultra | [https://github.com/N64brew-Game-Jam-2020/Tecto](https://github.com/N64brew-Game-Jam-2020/Tecto) | | [Telocation](https://n64brew.dev/wiki/Telocation?action=edit&redlink=1 "Telocation (page does not exist)") | Team | Team Ultra Rare | libultra | [https://github.com/N64brew-Game-Jam-2020/Telocation](https://github.com/N64brew-Game-Jam-2020/Telocation) | [https://jtn191.itch.io/telocation-gemini](https://jtn191.itch.io/telocation-gemini) | | [Test 3rd Person Demo](https://n64brew.dev/wiki/Test_3rd_Person_Demo?action=edit&redlink=1 "Test 3rd Person Demo (page does not exist)") | Team | Team Ultranauts | libultra | [https://github.com/N64brew-Game-Jam-2020/Test-3rd-Person-Demo](https://github.com/N64brew-Game-Jam-2020/Test-3rd-Person-Demo) | | | [The Swoop 64](https://n64brew.dev/wiki/The_Swoop_64?action=edit&redlink=1 "The Swoop 64 (page does not exist)") | Team | Team Zenden | libultra | [https://github.com/N64brew-Game-Jam-2020/The-Swoop-64](https://github.com/N64brew-Game-Jam-2020/The-Swoop-64) | | | [Thornmarked](https://n64brew.dev/wiki/Thornmarked?action=edit&redlink=1 "Thornmarked (page does not exist)") | Team | Team Vanadium | libultra | [https://github.com/N64brew-Game-Jam-2020/thornmarked](https://github.com/N64brew-Game-Jam-2020/thornmarked) | | Results ------- | | | | | --- | --- | --- |Finalists | Entry | Score | Rank | | Telocation Gemini | 117/125 | 1st | | Kumi-Daiko Beatoff 64 | 104/125 | 2nd (tie) | | sblobber 64 | 104/125 | 2nd (tie) | | The Swoop 64 | 102/125 | 3rd (tie) | | Lunar Assault 64 | 102/125 | 3rd (tie) | External Links -------------- 1. [Jam Announcement Video](https://www.youtube.com/watch?v=10kX9DwDTog) 2. [Theme Reveal Trailer](https://www.youtube.com/watch?v=10kX9DwDTog) 3. [Interview with the Judges and Contestants](https://www.youtube.com/watch?v=VixNMSLTv-o) Retrieved from "[https://n64brew.dev/wiki/N64brew\_Game\_Jam\_2020?oldid=5777](https://n64brew.dev/wiki/N64brew_Game_Jam_2020?oldid=5777) " --- # Reality Signal Processor/Interface - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#) Reality Signal Processor/Interface ================================== < [Reality Signal Processor](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor") The RSP interface is accessed by VR4300 via memory mapped registers at the physical address `0x040x xxxx`. However, because all memory accesses in the VR4300 are made using virtual addresses, it is normally used `0xA40x xxxx` to access the interface in the uncached segment. Contents -------- * [1 DMEM and IMEM](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#DMEM_and_IMEM) * [2 DMA transfers](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#DMA_transfers) * [2.1 Double buffering](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#Double_buffering) * [3 RSP Internal Registers](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#RSP_Internal_Registers) * [3.1 SP\_DMA\_SPADDR](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_SPADDR) * [3.2 SP\_DMA\_RAMADDR](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_RAMADDR) * [3.3 SP\_DMA\_RDLEN](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_RDLEN) * [3.4 SP\_DMA\_WRLEN](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_WRLEN) * [3.5 SP\_STATUS](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_STATUS) * [3.6 SP\_DMA\_FULL](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_FULL) * [3.7 SP\_DMA\_BUSY](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_BUSY) * [3.8 SP\_SEMAPHORE](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_SEMAPHORE) * [4 RSP PC register](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#RSP_PC_register) * [4.1 SP\_PC](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_PC) DMEM and IMEM ------------- Both RSP memory banks are fully memory mapped into the VR4300 address space, as follows: | Address range | | Memory | | --- | --- | --- | | 0x04000000 | 0x04000FFF | RSP DMEM | | 0x04001000 | 0x04001FFF | RSP IMEM | Accesses are usually performed using 32-bit reads and writes by VR4300. Different access sizes follow the standard behavior of RCP accesses documented in the [Memory Map](https://n64brew.dev/wiki/Memory_map#Physical_Memory_Map_accesses "Memory map") . Since the memory is single-port, it can only be accessed by either the VR4300 or the RSP itself at the same time (including its internal DMA engine). Notice that there is no bus arbiter: an access happening at the same time by both processors will cause problems: typically what happens is that VR4300 wins the race, so the RSP write is lost, or the RSP read returns the same data read by the VR4300 (even if the address was different). Also, if a DMA was in progress, the address of the memory access performed by VR4300 becomes the current address of the DMA transfer, corrupting it. So, in general, VR4300 should access DMEM/IMEM only when RSP is halted. DMA transfers ------------- DMA transfers can be initiated by either VR4300 or RSP. They can transfer from/to RDRAM to/from IMEM/DMEM very efficiently, much faster than copying the data word by word using VR4300 over the memory mapped addresses of the memory banks. The speed of transfer is about 3.7 bytes per VR4300 (PClock) cycle (plus some small fixed overhead). It is the fastest DMA engine in the N64. The DMA engine allows to transfer multiple "rows" of data in RDRAM, separated by a "skip" value. This allows for instance to transfer a rectangular portion of a larger image, by specifying the size of each row of the selection portion, the number of rows, and a "skip" value that corresponds to the bytes between the end of a row and the beginning of the following one. Notice that this applies only to RDRAM: accesses in IMEM/DMEM are always linear. DMA transfers only happen between 8-byte aligned addresses (in both RDRAM and IMEM/DMEM). DMA registers do not allow misaligned addresses to be written, as the lowest 3 bits are ignored and fixed to 0. The same applies to the length registers, so that the transfer size is always a multiple of 8. A single DMA transfer can only transfer to/from one of DMEM or IMEM. It is not possible for instance to write data to both DMEM and IMEM in a single transfer. If the transfer hits the end of either memory area, it wraps around to the beginning of it. ### Double buffering All DMA registers are double-buffered: this means that it is possible to program a DMA transfer while another one is in progress. As soon as the first transfer finishes, the second one will start. The RSP status register reports in separate bits whether there is a transfer ongoing, and whether there is a transfer pending. Reading from the DMA registers always return information on the ongoing transfer, or the last finished transfer. Pending values that are written to the registers can not be read back, until the transfer begins: at that point, they become visible as the transfer progresses. RSP Internal Registers ---------------------- The internal RSP registers are memory mapped into the VR4300 physical address space starting from 0x0404 0000. Normally, accesses are performed through the virtual uncached segment, so at 0xA404 0000. The exact same physical registers are also exposed as COP0 registers to RSP itself, and can thus be accessed using the MTC0 / MFC0 opcodes. Since access to all registers is shared by VR4300 and RSP, special care must be taken while writing software to decide who is in charge of each different resource / feature. For instance, normally DMA operations are performed by either the CPU or the RSP only; if the software architecture requires both to issue DMA transfers, some kind of mutex protocol must be established (for instance, using either the SIG bits in the SP\_STATUS register, or the SP\_SEMAPHORE register). | | | | | | --- | --- | --- | --- | | VR4300 address | RSP COP0 register | Name | Description | | 0x0404 0000 | c0 | [SP\_DMA\_SPADDR](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_SPADDR) | Address in IMEM/DMEM for a DMA transfer | | 0x0404 0004 | c1 | [SP\_DMA\_RAMADDR](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_RAMADDR) | Address in RDRAM for a DMA transfer | | 0x0404 0008 | c2 | [SP\_DMA\_RDLEN](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_RDLEN) | Length of a DMA transfer. Writing this register triggers a DMA transfer from RDRAM to IMEM/DMEM | | 0x0404 000C | c3 | [SP\_DMA\_WRLEN](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_WRLEN) | Length of a DMA transfer. Writing this register triggers a DMA transfer from IMEM/DMEM to RDRAM. | | 0x0404 0010 | c4 | [SP\_STATUS](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_STATUS) | RSP status register. | | 0x0404 0014 | c5 | [SP\_DMA\_FULL](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_FULL) | Report whether there is a pending DMA transfer (mirror of `DMA_FULL` bit of SP\_STATUS) | | 0x0404 0018 | c6 | [SP\_DMA\_BUSY](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_DMA_BUSY) | Report whether there is a DMA transfer in progress (mirror of `DMA_BUSY` bit of SP\_STATUS) | | 0x0404 001C | c7 | [SP\_SEMAPHORE](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface#SP_SEMAPHORE) | Register to assist implementing a simple mutex between VR4300 and RSP. | ### SP\_DMA\_SPADDR | SP\_DMA\_SPADDR `0x0404 0000` (RSP COP0: `c0`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | | | MEM\_BANK | MEM\_ADDR\[11:8\] | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | U-0 | U-0 | U-0 | | MEM\_ADDR\[7:3\] | | | | | 0 | 0 | 0 | | | | | --- | --- | | bit 31-13 | **Undefined:** Initialized to `0` | | bit 12 | **MEM\_BANK:** Bank accessed by the transfer
0 = DMEM
1 = IMEM | | bit 11-0 | **MEM\_ADDR\[11:0\]:** DMEM or IMEM address used in SP DMAs. Notice that the lowest 3 bits are always 0. | **Extra Details:** **MEM\_BANK** This bit selects the memory bank that will be accessed by the DMA transfer. Notice that, even though the memory banks appear to be contiguous in VR4300 address space, it is not possible to perform a single DMA transfer that spans across two banks. Each transfer will only access a single bank. For instance, to load a microcode, it is normally necessary to do two separate transfers: one for IMEM and one for DMEM. **MEM\_ADDR** This field contains the address in SP memory where the DMA transfer begins. The address is always aligned to 8 bytes, as the lowest 3 bits cannot be written. Notice that after writing to this register, the value is latched by SP but it is kept "pending" until the transfer is initiated via writes to `SP_DMA_WRLEN` or `SP_DMA_RDLEN`. Reads will continue returning the current (non-pending) value that refers to either an ongoing DMA transfer, or the last finished one. After a DMA transfer is finished, reading this register contains the address after the last one that was written. **Reads** Reading this register while a transfer is progress returns the current SP pointer (that is, the value is updated while the transfer is in progress). Notice that this is true even if another value was previously written to this register and it is pending: see the section on double buffering above for more information. ### SP\_DMA\_RAMADDR | SP\_DMA\_RAMADDR `0x0404 0004` (RSP COP0: `c1`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | U-0 | U-0 | U-0 | | DRAM\_ADDR\[7:3\] | | | | | 0 | 0 | 0 | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **DRAM\_ADDR\[23:0\]:** RDRAM address used in SP DMAs. Notice that the lowest 3 bits are always 0. | **Extra Details:** **DRAM\_ADDR** This field contains the address in RDRAM memory where the DMA transfer begins. The address is always aligned to 8 bytes, as the lowest 3 bits cannot be written. Notice that after writing to this register, the value is latched by SP but it is kept "pending" until the transfer is initiated via writes to `SP_DMA_WRLEN` or `SP_DMA_RDLEN`. Reads will continue returning the current (non-pending) value that refers to either an ongoing DMA transfer, or the last finished one. After a DMA transfer is finished, reading this register contains the address after the last one that was written. **Reads** Reading this register while a transfer is progress returns the current RDRAM pointer (that is, the value is updated while the transfer is in progress). Notice that this is true even if another value was previously written to this register and it is pending: see the section on double buffering above for more information. ### SP\_DMA\_RDLEN This register is used to initiate a DMA transfer from RDRAM to DMEM/IMEM. It must be written as third register, after programming `SP_DMA_SPADDR` and `SP_DMA_RAMADDR`. As soon as it is written, if the DMA engine was idle, a DMA transfer is started. Otherwise, the DMA transfer is enqueued (double-buffered), waiting for the previous one to be finished. | SP\_DMA\_RDLEN `0x0404 0008` (RSP COP0: `c2`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | SKIP\[11:4\] | | | | | | | | | 23:16 | RW-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | SKIP\[3\] | 0 | 0 | 0 | COUNT\[7:4\] | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | COUNT\[3:0\] | | | | RDLEN\[11:8\] | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | U-0 | U-0 | U-0 | | RDLEN\[7:3\] | | | | | 0 | 0 | 0 | | | | | --- | --- | | bit 31-20 | **SKIP\[11:0\]:** Number of bytes to skip in RDRAM after each row. Notice that the lowest 3 bits are always 0. | | bit 19-12 | **COUNT\[7:0\]:** Number of rows to transfer minus 1. | | bit 21-0 | **RDLEN\[11:0\]:** Number of bytes to transfer for each row minus 1. Notice that the lowest 3 bits are always 0. | **Extra Details:** **RDLEN** Like other DMA transfers in N64, this field holds the number of bytes to transfer minus 1. Since the DMA engine works in 64-bit words, writing 0 (or any value up to and including 7) starts a transfer of exactly 8 bytes. After the DMA transfer is finished, this field contains the value `0xFF8`; the reason is that the field is internally decremented by 8 for each transferred word, so the final value will be `-8` (in hex, `0xFF8` **COUNT and SKIP** Setting `COUNT` to 0 initiates a linear transfer of `RDLEN` plus 1 bytes (rounded up to 8 bytes); in this case, the value of `SKIP` is effectively ignored as only one row is transferred. With any other value, `COUNT` indicates the number of rows, to transfer a portion of a rectangular image, and `SKIP` indicates the so-called row stride, that is number of bytes to add to jump from the end of a row to the beginning of next one. After a DMA transfer is finished, `COUNT` is reset to 0, and `SKIP` is unchanged. **Reads** Reading this register while a transfer is progress returns the updated `RDLEN` and `COUNT` values (that is, the value is updated while the transfer is in progress). Notice that this is true even if another value was previously written to this register and it is pending: see the section on double buffering above for more information. Notice also that `SP_DMA_WRLEN` and `SP_DMA_RDLEN` both always returns the same data on read, relative to the current transfer, irrespective on the direction of the transfer. ### SP\_DMA\_WRLEN This register is used to initiate a DMA transfer from DMEM/IMEM to RDRAM. It must be written as third register, after programming `SP_DMA_SPADDR` and `SP_DMA_RAMADDR`. As soon as it is written, if the DMA engine was idle, a DMA transfer is started. Otherwise, the DMA transfer is enqueued (double-buffered), waiting for the previous one to be finished. | SP\_DMA\_WRLEN `0x0404 000C` (RSP COP0: `c3`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | SKIP\[11:4\] | | | | | | | | | 23:16 | RW-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | SKIP\[3\] | 0 | 0 | 0 | COUNT\[7:4\] | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | COUNT\[3:0\] | | | | WRLEN\[11:8\] | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | U-0 | U-0 | U-0 | | WRLEN\[7:3\] | | | | | 0 | 0 | 0 | | | | | --- | --- | | bit 31-20 | **SKIP\[11:0\]:** Number of bytes to skip in RDRAM after each row. Notice that the lowest 3 bits are always 0. | | bit 19-12 | **COUNT\[7:0\]:** Number of rows to transfer minus 1. | | bit 21-0 | **WRLEN\[11:0\]:** Number of bytes to transfer for each row minus 1. Notice that the lowest 3 bits are always 0. | **Extra Details:** Please refer to `SP_DMA_RDLEN` for details. ### SP\_STATUS The SP\_STATUS register is the main status register for the RSP. Like many other flag registers in N64, it has two different layouts when accessed for reading and writing: this allows to perform atomic set / clear operations on each flag using a simple memory write operation, without risking race conditions that would be frequent if a read-modify-write sequence was issued by the processor. Note that writing both the set and clear bits for a particular flag in the same write results in no effect, the prior state of the flag is preserved. | SP\_STATUS `0x0404 0010` (RSP COP0: `c4`) - Read access | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | — | SIG7 | SIG6 | SIG5 | SIG4 | SIG3 | SIG2 | SIG1 | | 7:0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | SIG0 | INTBREAK | SSTEP | IO\_BUSY | DMA\_FULL | DMA\_BUSY | BROKE | HALTED | | | | | --- | --- | | bit 7-14 | **SIG:** Status of the 8 custom bits that can be freely used to communicate state between VR4300 and RSP. | | bit 6 | **INTBREAK:** Configure the RSP to trigger a RSP MI interrupt when the `BREAK` is run.
0 = When `BREAK` is run, just halt the RSP core without triggering a RSP MI interrupt.
1 = When `BREAK` is run, halt the RSP core and trigger a MI interrupt. | | bit 5 | **SSTEP:** Set to `1` when single-step mode is activated. In single-step mode, RSP auto-halts itself after a single opcode is run. See the details below. | | bit 4 | **IO\_BUSY:** Set to `1` when the RSP is accessing either DMEM or IMEM. (\*TODO\*: verify this) | | bit 3 | **DMA\_FULL:** Set to `1` when there is a DMA transfer pending in the DMA register in addition to a DMA already in progress. | | bit 2 | **DMA\_BUSY:** Set to `1` when there is a DMA transfer currently in progress. | | bit 1 | **BROKE:** Set to `1` when the RSP executes a `BREAK` opcode. It must be manually reset. | | bit 0 | **HALTED:** Current running status of the RSP
0 = Running
1 = Idle / halted | | SP\_STATUS `0x0404 0010` (RSP COP0: `c4`) - Write access | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | W-0 | | — | — | — | — | — | — | — | SET\_SIG7 | | 23:16 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | CLR\_SIG7 | SET\_SIG6 | CLR\_SIG6 | SET\_SIG5 | CLR\_SIG5 | SET\_SIG4 | CLR\_SIG4 | SET\_SIG3 | | 15:8 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | CLR\_SIG3 | SET\_SIG2 | CLR\_SIG2 | SET\_SIG1 | CLR\_SIG1 | SET\_SIG0 | CLR\_SIG0 | SET\_INTBREAK | | 7:0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | CLR\_INTBREAK | SET\_SSTEP | CLR\_SSTEP | SET\_INTR | CLR\_INTR | CLR\_BROKE | SET\_HALT | CLR\_HALT | | | | | --- | --- | | bit 9-24 | **CLR\_SIG/SET\_SIG:** Set to `0` or `1` the 8 available bitflags that can be used as communication protocol between RSP and CPU. | | bit 8 | **SET\_INTBREAK:** Enable the `INTBREAK` flag. When this flag is enabled, running a `BREAK` opcode will generate a RSP MI interrupt, in addition to halting the RSP. | | bit 7 | **CLR\_INTBREAK:** Disable the `INTBREAK` flag. When this flag is disabled, running a `BREAK` opcode will not generate any RSP MI interrupt, but it will still halt the RSP. | | bit 6 | **SET\_SSTEP:** Enable single-step mode. When this mode is activated, the RSP auto-halts itself after every opcode that is run. The VR4300 can then trigger a new step by unhalting it. | | bit 5 | **CLR\_SSTEP:** Disable single-step mode. | | bit 4 | **SET\_INTR:** Manually trigger a RSP MI interrupt on the VR4300. It might be useful if the RSP wants to manually trigger a VR4300 interrupt at any point during its execution. | | bit 3 | **CLR\_INTR:** Acknowledge a pending RSP MI interrupt. This must be done any time a RSP MI interrupt was generated, otherwise the interrupt line on the VR4300 will stay asserted. | | bit 2 | **CLR\_BROKE:** Clear the `BROKE` flag, that is automatically set every time a `BREAK` opcode is run. This flag has no effect on the running/idle state of the RSP; it is just a latch that remembers whether a `BREAK` opcode was ever run. | | bit 1 | **SET\_HALT:** Pause running RSP code (set the `HALTED` flag) | | bit 0 | **CLR\_HALT:** Start running RSP code from the current RSP PC (clear the `HALTED` flag) | **Extra Details:** **HALT** The HALT flag can be thought of as a "pause" flag. When the RSP is halted by writing the `SET_HALT` bit, the RSP core pauses the pipeline without flushing it, maintaining the current PC but also the intermediate status like pending writebacks and delay slots. If a new ucode is loaded instead, make sure to also write SP\_PC to the new entry point (writing to SP\_PC also fully discards the RSP core pipeline). Given the "pause" behavior, it would look like the VR4300 could pause and unpause the RSP at any time during its execution without side effects. Unfortunately, this only works "most" of the time: there is at least one hardware bug that can cause corruption when a halt is triggered within a specific sequence of opcodes. This bug has been [observed during libdragon development](https://github.com/DragonMinded/libdragon/blob/2c8b04b7f08fe5e732101fe64d23590f464d6b51/include/rsp_queue.inc#L254-L259) , but the developers could not manage to isolate it or reduce it to a small snippet of code. In general, it is thus very risky to prepare a communication protocol that comprehends VR4300 pausing/unpausing the RSP at random times while it is running. **HALT and DMA** Setting the HALT bit does not pause the DMA transfers in progress. The DMAs will continue running until they finish. This will be reflected by the status register, so that it is possible that both `HALT` and `DMA_BUSY` are set. This is specifically important if VR4300 halts the RSP and wants to access IMEM/DMEM immediately: it is important to wait for `DMA_BUSY` to be cleared before accessing the memory banks, or corruption can happen (see above for more information on what happens when both VR4300 and RSP access the memory banks at the same time). **SSTEP** The single step mode allows the RSP to execute a single instruction and pause itself. In particular, whenever `SSTEP` is set while the RSP is running, RSP will pause itself before next instruction by setting the `HALT` flag. To perform single-stepping through RSP code, VR4300 should set the `SSTEP` flag and then reset `HALT` to execute exactly one instruction. Unfortunately, this hardware mode is very buggy. There are at least two specific bugs that have been isolated. The presence of these two bugs are enough to consider the feature broken beyond any expectation of being useful. * Conditional branch instructions are sometimes broken; that is a branch is taken where it should not or viceversa * `MTC0` / `MFC0` are broken: the instructions have a 2-cycle latency but it looks like the pending writeback is lost in single step mode; `MFC0` actually writes `(PC+4)/4` into the register (where `PC` is the address of the `MFC0` instruction itself). `MTC0` simply doesn't work, and the target register is not written. **SIG** Signal bits are software-controlled bits with no hardware meaning. They can be set or reset by writing to the status register. Since both VR4300 and RSP can access the status register, they can be used to perform a simple communication / handshaking protocol between the two CPUs. For instance the RSP might set `SIG0` to `1` when some data has been processed and sent back to RDRAM via DMA, so that VR4300 can access the results after it sees `SIG0` being set. Because of the design of the hardware register that allows for atomic modification of bits thanks to the separate write access structure, it is possible for VR4300 and RSP to set/reset different signal bits at the same time without risking race conditions. **DMA\_FULL** This bit is set whenever a DMA transfer is pending, that is it has been programmed via the DMA registers but it has not started yet because another transfer is in progress. This is possible because of the double-buffering of the DMA registers (explained above). Notice that this bit goes to `0` a few clock cycles \*before\* the previous DMA transfer is finished (probably the RSP internally has some preparation work for DMA that is able to parallelize with the last memory writes of another transfer). Anyway, as soon as the bit goes to zero, it is possible to enqueue a new DMA transfer. ### SP\_DMA\_FULL | SP\_DMA\_FULL `0x0404 0014` (RSP COP0: `c5`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | R-0 | | — | — | — | — | — | — | — | DMA\_FULL | | | | | --- | --- | | bit 31-1 | **Undefined:** Initialized to `0` | | bit 0 | **DMA\_FULL:** Mirror of DMA\_FULL bit in SP\_STATUS | ### SP\_DMA\_BUSY | SP\_DMA\_FULL `0x0404 0018` (RSP COP0: `c6`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | R-0 | | — | — | — | — | — | — | — | DMA\_BUSY | | | | | --- | --- | | bit 31-1 | **Undefined:** Initialized to `0` | | bit 0 | **DMA\_BUSY:** Mirror of DMA\_BUSY bit in SP\_STATUS | ### SP\_SEMAPHORE | SP\_SEMAPHORE `0x0404 001C` (RSP COP0: `c7`) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | | — | — | — | — | — | — | — | SEMAPHORE | | | | | --- | --- | | bit 31-1 | **Undefined:** Initialized to `0` | | bit 0 | **SEMAPHORE:** Semaphore bit to implement a hardware-assisted mutex with atomic access. The bit behaves normally on reads and writes, with the only difference that after each read, the bit is always automatically set to 1 by the hardware (though the previous value is returned). | **Extra Details:** **SEMAPHORE** The goal of this bit is to help implementing a mutex between VR4300 and RSP. The mutex can be used to guard access to any shared hardware resource, a typical example being the DMA engine. To acquire the mutex, the CPU (either VR4300 or RSP) should spin reading the `SEMAPHORE` bit until it reads 0. At that point, the bit is automatically flipped to 1 by the hardware, so reading 0 means "the semaphore was free, and you have just acquired it". After the CPU is done using the shared resource, it can simply write 0 to `SEMAPHORE` to release it. RSP PC register --------------- RSP has an internal PC (program counter) register that cannot be explicitly accessed via RSP opcodes. Instead, a memory mapped register is available to VR4300 to control the RSP PC while RSP is halted. The register is called `SP_PC`. Notice that VR4300 is allowed to access SP\_PC only while RSP is halted. Reading from SP\_PC while RSP is running returns garbage data, and writing to it causes RSP to misbehave. ### SP\_PC | SP\_PC `0x0408 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | PC\[11:8\] | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | R-0 | | PC\[7:2\] | | | | | | 0 | 0 | | | | | --- | --- | | bit 31-12 | **Undefined:** Initialized to `0` | | bit 11-0 | **PC\[11:0\]:** Read/write the RSP PC (program counter). Notice that the lowest two bits are always 0. | **Extra Details:** **PC** Reads while RSP is running returns random bits. Reads while RSP is halted return the address of the instruction that the RSP will execute when it is unhalted. Writes will also reset the RSP CPU core pipeline, so any pending writeback or branch are discarded. Retrieved from "[https://n64brew.dev/wiki/Reality\_Signal\_Processor/Interface?oldid=5376](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface?oldid=5376) " --- # N64brew Game Jam 2021 - N64brew Wiki [](https://n64brew.dev/wiki/N64brew_Game_Jam_2021#) N64brew Game Jam 2021 ===================== The second homebrew game jam put together by the N64brew community on Discord. The entries were designed around the preselected theme, **Control**, which was announced at the beginning of the jam, on October 8, 2021, which ran until December 7, 2021. After two months of work, the entries were judged based on multiple categories. The total prize pot for charitable donations was $1,254 USD. The winner of the 2021 N64Brew Game Jam, Danielface, chose the Rainforest Trust[\[1\]](https://n64brew.dev/wiki/N64brew_Game_Jam_2021#cite_note-1) , a nonprofit environmental organization focused on the purchase and protection of tropical lands to strategically conserve threatened species. [![The N64Brew Game Jam logo. The words "game jam" are on the label, and a pricetag that says "#2".](https://static.wikitide.net/n64wiki/f/fa/Jam2Logo.png)](https://n64brew.dev/wiki/File:Jam2Logo.png) Judges ------ | Name | External link(s) | | --- | --- | | Buu342 | [https://github.com/buu342](https://github.com/buu342) | | LuigiBlood | [https://luigiblood.neocities.org/](https://luigiblood.neocities.org/) | | Samuel “Kaiser” Villarreal | [https://villsa.wordpress.com/](https://villsa.wordpress.com/) | | Kaze Emanuar | [https://www.youtube.com/channel/UCuvSqzfO\_LV\_QzHdmEj84SQ](https://www.youtube.com/channel/UCuvSqzfO_LV_QzHdmEj84SQ) | | Giles Goddard | [https://twitter.com/giles](https://twitter.com/giles) | Submissions ----------- | Entry | Solo/Team | Participant(s) | Engine/Framework | Source Code | External link(s) | | --- | --- | --- | --- | --- | --- | | [Asteroids 64](https://n64brew.dev/wiki/Asteroids_64?action=edit&redlink=1 "Asteroids 64 (page does not exist)") | Solo | gamemasterplc | [Libdragon](https://n64brew.dev/wiki/Libdragon "Libdragon") | [https://github.com/gamemasterplc/n64game](https://github.com/gamemasterplc/n64game) | | | [Bird Kart](https://n64brew.dev/wiki/Bird_Kart?action=edit&redlink=1 "Bird Kart (page does not exist)") | Team | Team Zenden (Zest, Squiddy, Miluaces) | [Libultra](https://n64brew.dev/wiki/Libultra "Libultra") | [https://github.com/zestydevy/jam2](https://github.com/zestydevy/jam2) | | [Bug Game](https://n64brew.dev/wiki/Bug_Game?action=edit&redlink=1 "Bug Game (page does not exist)") | Team | UltraBrew (Hazematman, Darklink623, Nick Carver) | Libdragon | [https://github.com/Hazematman/Bug-Game](https://github.com/Hazematman/Bug-Game) | [https://hazematman.itch.io/bug-game](https://hazematman.itch.io/bug-game) | | [Featherlight](https://n64brew.dev/wiki/Featherlight?action=edit&redlink=1 "Featherlight (page does not exist)") | Team | Early Birds (YoshiMaster96, lepidotós) | Libultra | [https://github.com/Yoshimaster96/N64BrewGameJam2021](https://github.com/Yoshimaster96/N64BrewGameJam2021) | | | [Fission Failure 64](https://n64brew.dev/wiki/Fission_Failure_64?action=edit&redlink=1 "Fission Failure 64 (page does not exist)") | Team | vrgl117 games (vieux, Isabel, ealdeguer, jphosho) | Libdragon | [https://github.com/vrgl117-games/FissionFailure64](https://github.com/vrgl117-games/FissionFailure64) | | | [Mission Lost Control](https://n64brew.dev/wiki/Mission_Lost_Control?action=edit&redlink=1 "Mission Lost Control (page does not exist)") | Team | Team Ultra Rare (lambertjamesd, jtn191, SapphireTactics, whatswithandy) | Libultra | [https://github.com/lambertjamesd/n64brew20201](https://github.com/lambertjamesd/n64brew20201) | | [Power Struggle](https://n64brew.dev/wiki/Power_Struggle?action=edit&redlink=1 "Power Struggle (page does not exist)") | Team | The Shaders (Wiseguy, InTheBeef, SausageSage) | Libultra | [https://github.com/TheShaders/PowerStruggle](https://github.com/TheShaders/PowerStruggle) | | | [sUGGOH](https://n64brew.dev/wiki/SUGGOH?action=edit&redlink=1 "SUGGOH (page does not exist)") | Solo | someone2639 | Libultra | [https://github.com/farisawan-2000/n64gamejam-2/](https://github.com/farisawan-2000/n64gamejam-2/) | | | [Tandem Trouble](https://n64brew.dev/wiki/Tandem_Trouble?action=edit&redlink=1 "Tandem Trouble (page does not exist)") | Team | Terminal Viscosity (kivan117, Quilt, SpiritOf1776, Pixel) | Libultra | [https://github.com/matthewcpp/n64brew-gamejam-2021](https://github.com/matthewcpp/n64brew-gamejam-2021) | | | [Voidblade](https://n64brew.dev/wiki/Voidblade?action=edit&redlink=1 "Voidblade (page does not exist)") | Solo | anacierdem | Libdragon | [https://github.com/anacierdem/n64brew-jam](https://github.com/anacierdem/n64brew-jam) | [https://alinacierdem.com/voidblade-a-n64-postmortem/](https://alinacierdem.com/voidblade-a-n64-postmortem/) | | [Wizard Of The Board](https://n64brew.dev/wiki/Wizard_Of_The_Board?action=edit&redlink=1 "Wizard Of The Board (page does not exist)") | Solo | danielface | Libultra | [https://github.com/danbolt/n64-jam-2](https://github.com/danbolt/n64-jam-2) | [https://danbolt.itch.io/wizard-of-the-board](https://danbolt.itch.io/wizard-of-the-board) | | [zzgunner](https://n64brew.dev/wiki/Zzgunner?action=edit&redlink=1 "Zzgunner (page does not exist)") | Solo | Ryzee119 | Libdragon | [https://github.com/Ryzee119/zzgunner](https://github.com/Ryzee119/zzgunner) | | Results ------- | | | | --- | --- |Finalists | Entry | Score | | Wizard of the Board | 110/125 | | Mission Lost Control | 105/125 | | Voidblade | 97/125 | | Power Struggle | 96/125 | | Fission Failure 64 | 95/125 | | Tandem Trouble | 80/125 | | zzgunner | 75/125 | | Bug Game | 62/125 | | Bird Kart | 49/125 | | Featherlight | 47/125 | | Asteroids | 47/125 | | sUGGOH | 40/125 | External Links -------------- 1. [Jam Announcement Video](https://www.youtube.com/watch?v=95faptE3-mc) 2. [Theme Reveal Trailer](https://www.youtube.com/watch?v=s4OOBU3sluc) 3. [Interview with the Judges and Contestants](https://www.youtube.com/watch?v=0yxd2s-0t-s) 4. [Highlight reel of the entries](https://www.youtube.com/watch?v=GMDI4Z-oGHQ) 5. [Winners Announcement Reel](https://www.youtube.com/watch?v=o7wheu3AIKw) 1. [↑](https://n64brew.dev/wiki/N64brew_Game_Jam_2021#cite_ref-1) [https://www.rainforesttrust.org/](https://www.rainforesttrust.org/) Retrieved from "[https://n64brew.dev/wiki/N64brew\_Game\_Jam\_2021?oldid=4933](https://n64brew.dev/wiki/N64brew_Game_Jam_2021?oldid=4933) " --- # Reality Display Processor/Pipeline - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline#) Reality Display Processor/Pipeline ================================== < [Reality Display Processor](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") The RDP has four operating modes controlled by the **cycle\_type** field in the [Set Other Modes](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x2F_-_Set_Other_Modes "Reality Display Processor/Commands") command. These modes determine how each pixel in the RDP pipeline is processed. * **1-Cycle Mode**: 1 cycle (@ 62.5 MHz) per pixel (pipelined) with all pipeline stages active * **2-Cycle Mode**: 2 cycles (@ 62.5 MHz) Per pixel (pipelined) with all pipeline stages active, some running twice per pixel * **Fill Mode**: Accelerated rendering of solid-color rectangles * **Copy Mode**: Accelerated blitting of sprites with no additional processing The RDP also has a separate loading pipeline that is selected when load commands ([Load Block](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x33_-_Load_Block "Reality Display Processor/Commands") , [Load Tile](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x34_-_Load_Tile "Reality Display Processor/Commands") , or [Load TLUT](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x30_-_Load_TLUT "Reality Display Processor/Commands") ) are executed. This shares some resources with the rendering pipelines to save hardware cost. Contents -------- * [1 1-Cycle Pipeline](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline#1-Cycle_Pipeline) * [2 2-Cycle Pipeline](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline#2-Cycle_Pipeline) * [3 Fill Pipeline](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline#Fill_Pipeline) * [4 Copy Pipeline](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline#Copy_Pipeline) * [5 Loading Pipeline](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline#Loading_Pipeline) * [6 Synchronization](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline#Synchronization) 1-Cycle Pipeline ---------------- The 1-Cycle pipeline renders any combination of shaded, textured, z-buffered, anti-aliased primitives, each with one stage of texturing, color combiner and blending. The pipeline can be broken down as follows, however note that some of these stages may be executed in parallel and some may be spread out over multiple clock cycles so consider this a logical view of the pipeline only. (TODO: each of these stages deserves much more detail on their operation, either on this page or in subpages) * **Rasterize** Determines the set of pixels that the primitive should cover. Interpolates the necessary attributes of the primitive for each pixel such as shade color, depth value and texture coordinates. Computes a coverage value for each pixel. Only once the last pixel in the primitive has been enqueued into the first stage of the rest of the pipeline are any new RDP commands executed. * **Texture Perspective Correction** Only if **persp\_tex\_en** enabled. Divides the (s,t) attributes of each pixel by the pixel's _w_ attribute. * **Texture Coordinate Shift, Clamp, Mirror, Mask** This stage maps the input texture coordinates for the primitive into local texture coordinates for the texture tile this primitive is to be rendered with. Conversion to relative texture coordinates by subtracting the tile's upper-left (s,t) coordinates happens between shift and clamp. * **Texture Sampling** Four texels are sampled from TMEM using the results of the prior steps. All texels irrespective of storage format are converted to RGBA32 when sampled. * **Texture Filtering** The four texel samples are combined into a single result by the selected filter mode. * **RGBA and Depth Correction** The RGBA shade color and depth value are subpixel-corrected using the computed coverage value. * **Color Combiner** The Color Combiner is evaluated on its inputs to produce the output color. * **Chroma Key** (TODO) * **Alpha Fixup** Using **cvg\_x\_alpha** and **alpha\_cvg\_sel**, adjusts the alpha value output from the color combiner and also adjusts the coverage value seen by later stages. * **Dither Shade Alpha** Apply alpha dither to interpolated shade alpha for use as a blender input. If **alpha\_cvg\_select** is disabled, also applies to alpha used for alpha compare. * **Image Read** If **image\_read\_en** is set in othermodes, read memory color and coverage from color image. If disabled, memory coverage is set to full (7) and memory color is undefined. * **Depth Compare and Blend Enable Generation** If **z\_compare\_en** is set in othermodes. Depth compare algorithm determined by **z\_mode** in othermodes. Depth source determined by **z\_source\_sel** in othermodes. Blending is enabled if either **force\_bl** is set or if **antialias\_en** is set and the pixel is determined to be an edge pixel. If a pixel fails the depth test it is discarded and is not written to the color image. * **Alpha Compare** Alpha compare enabled if **alpha\_compare\_en** is set in othermodes. If a pixel fails the alpha compare test, discard it. * **Coverage Pixel Rejection** If **antialias\_en** is set and coverage is 0, discard the pixel. If **antialias\_en** unset and LSB of coverage is 0, discard the pixel. * **Blender** If **clr\_on\_cvg** is set and coverage has overflowed, take the memory color as-is and do not blend. If the blend enable signal generated earlier in the pipeline is true, perform blending by evaluating the blender on its inputs. If blending was not enabled, take the value of the first input to the blender as-is. When blending, only divide if **force\_bl** is not set. The alpha input to the blender is reduced to 5 bits by taking the most significant 5 bits. * **RGB Dither** Perform RGB dithering, reduce color depth from 8-bit per channel to 5-bit per channel if targeting a 16-bit color image. * **Color Image Write** Pixels that are not rejected by earlier stages are written to the on-chip span buffers to await flushing to RDRAM. * **Depth Image Write** If **z\_update\_en** is set, write the new depth value for this pixel to the depth span to await flushing to the Z-Buffer in RDRAM. 2-Cycle Pipeline ---------------- The 2-Cycle pipeline renders any combination of shaded, textured, z-buffered, anti-aliased primitives, each with two stages of texturing, color combiner and blending. The pipeline is much like the 1-Cycle pipeline but with two consecutive texturing stages, two consecutive color combine stages, and two consecutive blending stages. Unlike the 1-Cycle pipeline, beware the many pipeline hazards the 2-Cycle pipeline presents, many of which are so far documented on the [RDP Commands page](https://n64brew.dev/wiki/Reality_Display_Processor/Commands "Reality Display Processor/Commands") . Fill Pipeline ------------- The Fill pipeline is the simplest of the pipelines, it retains only enough functionality to fill screen regions with a solid color sourced from the fill color register. * **Rasterize** Determines the set of pixels that the primitive should cover. Unlike 1 and 2-cycle modes most attributes are not interpolated and no coverage is computed. Texture coordinates are not generated. * **Color Image Write** Writes pixels out to the color image using the current fill color value. Pixels are written out 64-bits (8 8-bit pixels, 4 16-bit pixels, 2 32-bit pixels) at a time. Writes are committed straight to RDRAM without passing through the span buffers. Copy Pipeline ------------- The Copy pipeline is relatively simple, many of the core pipeline features as skipped to enable fast copying of textures from TMEM directly to the color image with few changes. * **Rasterize** Determines the set of pixels that the primitive should cover. Texture coordinates are interpolated for each pixel. * **Texture Perspective Correction** Performs perspective correction of texture coordinates if **persp\_tex\_en** is set in othermodes. This is expected to be disabled in general. * **Texture Coordinate Shift, Mirror and Mask** Performs shifting, mirroring and masking of texture coordinates using the shift, mirror and mask values specified by the selected texture tile. Shifting is applied before subtracting the tile upper-left (s,t) values, mirroring and then masking is applied after the subtraction to tile-relative texture coordinates. Clamping is not performed in Copy mode. * **Texture Sampling and TLUT Decoding** Samples texels from TMEM using the tile-relative texture coordinates. If **tlut\_en** is set in othermodes the final texel will be sourced from a palette and the tile format is ignored, for tiles that indicate a 4-bit texel size the TMEM address for the palette is indicated in the tile's palette field, the tile size is otherwise ignored. Texels are sampled 64-bits at a time irrespective of tile format/size configuration. * **Alpha Compare** Performs alpha compare on the sampled texels if **alpha\_compare\_en** is set in othermodes, only those texels passing the alpha compare test are written out to the color image as pixels. The precise operation of alpha compare varies depending on the selected color image format (notably, NOT the render tile format): * For 16-bit color images the alpha compare test simply checks if the alpha bit (LSB) is set and if so the test passes. * For 8-bit color images the alpha compare tests against either a random threshold or the blend color register alpha value depending on the value of **dither\_alpha\_en** in othermodes. If the 8-bit texel value is greater than or equal to the selected threshold the test passes. * For 4-bit color images alpha compare always fails if enabled. * **Color Image Write** Writes all texels that passed the alpha compare test out to the color image as pixels. Loading Pipeline ---------------- The loading pipeline is only indirectly configurable (through tile settings, the [Set Texture Image](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x3D_-_Set_Texture_Image "Reality Display Processor/Commands") command, and through load commands that kick off this pipeline) and facilitates the loading of texture data from RDRAM into TMEM. The loading pipeline appears to share some resources with the rendering pipelines as they cannot be executed in parallel. It can be unsafe to run a loading command followed immediately by a primitive rendering command right away and vice-versa, the two must be separated by synchronization to let the current operation finish before the next begins. Synchronization --------------- The RDP does not automatically interlock most global rendering attributes. Instead, it is up to the user to insert [Pipe Sync](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x27_-_Sync_Pipe "Reality Display Processor/Commands") , [Tile Sync](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x28_-_Sync_Tile "Reality Display Processor/Commands") , or [Load Sync](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x26_-_Sync_Load "Reality Display Processor/Commands") commands as-needed. Syncs should be inserted after primitive drawing and before attribute changes, as well as when switching between any of the rendering pipelines and the loading pipeline. Four attributes do not require sync: * Prim Color * Prim Depth * Scissor * Tile Size All sync commands work the same way, after the current primitive/texture pixels have been determined and enqueued onto the rendering/loading pipeline, a sync will insert additional wait cycles before processing the next command waiting in the FIFO. **Pipe Sync** waits the longest (50 cycles), followed by **Tile Sync** (33 cycles) and then **Load Sync** (25 cycles). If syncs are not properly inserted, the RDP will adopt the new settings too early and pixels at the end of the last primitive will be produced using a mix of the old and new settings. Depending on which settings are changed, this may lead to crashes. Different attributes are sampled at different stages in the RDP pipeline, depending on which attribute is corrupted the number of pixels affected will vary. Unsynced changes to attributes sampled towards the end of a pixel's lifetime in the pipeline will corrupt more pixels. In 1-Cycle Mode, the number of pixels corrupted is almost equal to the number of cycles corrupted, except there is 1 dead cycle at the end of every line in a primitive where the pipeline is cycled but no pixel is output. This dead cycle counts as a pixel for the purpose of counting corruptions. | | | | | | --- | --- | --- | --- |Effect of unsynced attribute changes | Global Setting Changed | Cycles Corrupted (1-Cycle Mode) | Cycles Corrupted (2-Cycle Mode) | Extra Details | | Depth image address | **TBC** | **TBC** | | | Color image settings | 0 | 0 | Applies to both the color image base address and the image format/size | | image\_read\_en | 0 | 0 | | | z\_compare\_en | 0 | 0 | | | z\_update\_en | 0 | 0 | | | convert\_one | N/A | **TBC** | | | persp\_tex\_en | 7 | 4 | | | Tile Settings | 13 | 10 | Applies to any tile setting besides tile size | | tex\_lod\_en | 13 | 12 | | | detail\_tex\_en | **TBC** | **TBC** | | | sharpen\_tex\_en | **TBC** | **TBC** | | | sample\_type | 17 | 14 | | | tlut\_en | 18 | 16 | | | tlut\_type | 20 | 18 | | | mid\_texel | 21 | 18 | | | bi\_lerp\_0 | 21 | **TBC** | | | bi\_lerp\_1 | 21 | **TBC** | | | Combiner Settings/Inputs | 24 | 22 | Applies to changing the combiner configuration, or changing any of the active inputs to the combiner (such as env color) besides prim color | | Set Convert | 24 | 22 | | | Chroma Key Parameters | 24 | **TBC** | | | alpha\_cvg\_select | 25 | 24 | | | cvg\_x\_alpha | 25 | 24 | | | key\_en | 25 | 24 | | | z\_source\_sel | 25 | 24 | | | alpha\_dither\_sel | 25 | 24 | | | Blender Settings/Inputs | 26 | 24 | Applies to changing the blender configuration, or changing any of the active inputs to the blender (such as fog color) | | dither\_alpha\_en | 26 | 26 | | | z\_mode | 27 | 26 | | | force\_blend | 27 | 26 | | | antialias\_en | 27 | 26 | | | cvg\_dest | 28 | 28 | | | color\_on\_cvg | 28 | 28 | | | rgb\_dither\_sel | 29 | 28 | | | alpha\_compare\_en | 29 | 28 | | The following snippet shows how attributes that require syncs are updated internally in just 1 cycle: // red fill rectangle SET ENV COLOR (255, 0, 0, 255) FILL RECTANGLE (...) // corrupt two pixels with green towards the end, without the NOP this would corrupt just one pixel as NOPs and attribute setters execute in just 1 pipeline cycle SET ENV COLOR (0, 255, 0, 255) NOP // corrupt remaining pixels with blue SET ENV COLOR (0, 0, 255, 255) Retrieved from "[https://n64brew.dev/wiki/Reality\_Display\_Processor/Pipeline?oldid=5674](https://n64brew.dev/wiki/Reality_Display_Processor/Pipeline?oldid=5674) " --- # SysAD Interface - N64brew Wiki [](https://n64brew.dev/wiki/MIPS_R4300_interface#) SysAD Interface =============== (Redirected from [MIPS R4300 interface](https://n64brew.dev/wiki/MIPS_R4300_interface?redirect=no "MIPS R4300 interface") ) The MIPS interface is a bidirectional interface on the N64 that allows 32bits of address and data transfers with a 5bit control bus and 3 controlling signals (There are another 3 controls that are used for multi-cpu setups but are not used in the N64). The bus almost works like a packet system where the control bus select what is being read or written and how much data as well from the CPU or [RCP](https://n64brew.dev/wiki/RCP "RCP") . **Masterclock**: This is a clock feed from the RCP to the CPU. All signals are done on the Rising clock edge of the master clock. The internal CPU speed is multiplied using this clock to 93.75mhz. But all access between the RCP and CPU is at the masterclock rate (62.5mhz) **EoK** : This is a inverted ready signal from the RCP. When LOW, this signals that the RCP is ready to accept a command from the CPU. When the EoK is high the CPU will be placed in a wait state before the next set of data is sent from the CPU. This is mostly used when doing writes from the CPU so the RCP can be ready to send the next command. **Evalid**: This is the inverted signal from the RCP to say that there is valid data and commands on the SYSAD and SYSCMD buses. **Pvalid**: This is an inverted signal that says that the CPU is sending a valid SYSAD and SYSCMD signal on the buses. This can be held low (valid data) if it is waiting to send data to the RCP if the EoK is still high. **SYSAD \[31:0\]**: This is a Bidirectional Address and Data bus that is 32 bits. This can send single or burst data between the CPU and RCP when requested (Bursting is used for cache memory or double word read/writes accesses) **SYSCMD\[4:0\]**: This is a Bidirectional bus that tells the CPU or RCP is it a read/write, how much data to be sent or the end of data. Contents -------- * [1 SYSCMD Cheat sheet](https://n64brew.dev/wiki/MIPS_R4300_interface#SYSCMD_Cheat_sheet) * [2 Instruction and Data: non-cached reads](https://n64brew.dev/wiki/MIPS_R4300_interface#Instruction_and_Data:_non-cached_reads) * [3 Data: Word non-cached write](https://n64brew.dev/wiki/MIPS_R4300_interface#Data:_Word_non-cached_write) * [4 Data: Double word non-cached write 64 bits](https://n64brew.dev/wiki/MIPS_R4300_interface#Data:_Double_word_non-cached_write_64_bits) * [5 Data: Cached write 128 bits](https://n64brew.dev/wiki/MIPS_R4300_interface#Data:_Cached_write_128_bits) * [6 Data: Cached read 128 bits](https://n64brew.dev/wiki/MIPS_R4300_interface#Data:_Cached_read_128_bits) * [7 Instruction: Cached read 256 bits](https://n64brew.dev/wiki/MIPS_R4300_interface#Instruction:_Cached_read_256_bits) * [8 CPU Throughput tips, tricks and ideas](https://n64brew.dev/wiki/MIPS_R4300_interface#CPU_Throughput_tips,_tricks_and_ideas) SYSCMD Cheat sheet ================== | | | | | | | | --- | --- | --- | --- | --- | --- | | SYS Cmd Type | Bit 4 - Command or Data | Bit 3 | Bit 2 | Bit 1 - Size | Bit 0 - Size | | Command – Data On bus | 1 - Command | 0 - data flag | 0 | 0 | 0 | | Command – End Data | 1 - Command | 1 - Last data flag | 0 | 0 | 0 | | Command – Response data | 1 - Command | 0 - data flag | 1 – Response data | 0 | 0 | | Read – 32 bits | 0 – Data req | 0 - read | 0- Single read | 1 | 1 – 32 bits | | Read – 64 Bits | 0 – Data req | 0 - read | 1 – Block read | 0 | 0 – 64 bits | | Read – 128 Bits | 0 – Data req | 0 - read | 1 – Block read | 0 | 1 – 128 bits | | Read – 256 Bits | 0 – Data req | 0 - read | 1 – Block read | 1 | 0 – 256 bits | | Write – 8 bits | 0 – Data req | 1- write | 0- Single write | 0 | 0 – 8 bits | | Write – 16 bits | 0 – Data req | 1- write | 0- Single write | 0 | 1 – 16bits | | Write – 24 bits | 0 – Data req | 1- write | 0- Single write | 1 | 0 – 24 bits | | Write – 32 bits | 0 – Data req | 1- write | 0- Single write | 1 | 1 – 32 bits | | Write – 64 bits | 0 – Data req | 1- write | 1- block write | 0 | 0 – 64 bits | | Write – 128 bits | 0 – Data req | 1- write | 1- block write | 0 | 1 – 128 bits | | Write – 256 bits – this is only used in testing of icache | 0 – Data req | 1- write | 1- block write | 1 | 0 – 256 bits | Instruction and Data: non-cached reads ====================================== Both instruction reads and data word non cached reads run the same way.  [](https://n64brew.dev/wiki/File:CPU_Read_32bit.png) 32 bit read from the CPU 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to Read – 32 bits (All reads that are 8/16/24 and 32 are done as a 32-bit reads and the CPU does the shifting internally) * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next clock cycle the Pvalid goes high and the CPU keeps the SYSAD/CMD on the buses waiting for the Evalid to do low 4\.      Once the Evalid goes low, the CPU goes into High-Z mode and listens to the two buses. At that same time the RCP does the following: * The SYSAD bus from the RCP outputs the data that the CPU has requested * The SYSCMD outputs the End Data command 5\.      On the next master clock cycle the RCP puts the Evalid back high and puts both buses back in high-Z. Then the CPU does its next command Data: Word non-cached write =========================== All 8, 16, 24 and 32 bit writes are done as a single ’32 bit’ write structure but the last 2 bits of the SYSCMD bus say what type of write is to happen.  [](https://n64brew.dev/wiki/File:CPU_Write_32bit.png) 32 bit write from the CPU 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to Write – 8/16/24/32 bits * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next clock cycle the CPU will output the data to write and EoK from the RCP goes high to say it is accepting the data. Then the following happens: * The SYSAD puts the data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 4\.      On the next master clock cycle the CPU will place the Pvalid high to complete the write and RCP holds the EoK High to say that it is processing the write and will stay high until the write is completed internally. During this time the CPU can place then next command address on the busses and will stay in a hold state until the Eok Goes back low, Then the next master clock (So the EoK will be low for a full cycle) the RCP will then process that command. Now for 8/16/and 24 bits the data out will be address aligned on the data bus and repeated. This is so no processing is needed in the RCP on the alignment of the data. For 40/48/56 writes this is processed as two separate 32 write commands where the LSB is written first then the MSB is written next. Data: Double word non-cached write 64 bits ========================================== 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to Write – 64 bits * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next clock cycle the CPU will output the data to write and EoK from the RCP goes high to say it is accepting the data. Then the following happens: * The SYSAD puts the LSB data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 4\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the MSB data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 5\.      On the next master clock cycle the CPU will place the Pvalid high to complete the write and RCP holds the EoK High to say that it is processing the write and will stay high until the write is completed internally. During this time the CPU can place then next command address on the busses and will stay in a hold state until the Eok Goes back low, Then the next master clock (So the EoK will be low for a full cycle) the RCP will then process that command. Data: Cached write 128 bits =========================== The address for cache writes will always be 128 bit aligned for the address (So that last 4 bits will be 0000) This is because the D-cache ram in the CPU is 128 bits and when a dirty write happens the full 128 entry is written back to ram (cache dump opcodes run the same way too but only write back entrys that are marked as dirty)  [](https://n64brew.dev/wiki/File:Cpu_Write_dcache_128bit.png) 128bit D-cache write from the CPU 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to Write – 64 bits * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next clock cycle the CPU will output the data to write and EoK from the RCP goes high to say it is accepting the data. Then the following happens: * The SYSAD puts the first 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 4\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the second 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 5\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the third 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 6\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the fourth 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 7\.      On the next master clock cycle the CPU will place the Pvalid high to complete the write and RCP holds the EoK High to say that it is processing the write and will stay high until the write is completed internally. During this time the CPU can place then next command address on the busses and will stay in a hold state until the Eok Goes back low, Then the next master clock (So the EoK will be low for a full cycle) the RCP will then process that command. Data: Cached read 128 bits ========================== The address for cache reads will always be 64 bit aligned for the address (So that last 3 bits will be 000) Now why 64 bit address aligned? Well to keep speed up in the 4300i CPU the D-cache is 128 bits long but the CPU can read up to 64 bits for data at most, so we want to put the 64bit data as quickly as possible in the 128bit word aligned entry. Two things can happen if bit 4 of the data address requested is high or low. If low, the data to the CPU is sent normally from LSB to MSB words and the CPU will work on its merry way as it has received its needed 64 bits first. This is what we call a sequential order transfer. But if the 4th bit is high, the RCP will do something that will shock you. The CPU will send the 64 word aligned address (address 8000\_0018 data is sent to the RCP), First the 64 bits from that address requested are sent to the CPU. After that what data is sent next? The data from the address above it (8000\_0020) or below (8000\_0010)?  [](https://n64brew.dev/wiki/File:Dcache_read_128bit.png) CPU Read for Dcache 128 bits We have to look at how the D-cache is set up in the CPU. Each entry is 128 word aligned so with this in mind the previous 64bits of data are placed in the x8 (MSB) part of the d-cache ram so what happens to the low 64bits? This is where the RCP will then send the data from the previous address to the CPU. (This would be address 8000\_0010) This is what we call a subblock ordering (please look at page 339 on the VR4300 64-bit UM PDF on this). For this steps 4 and 5 are swapped with steps 6 and 7. 1\.      First the CPU checks that the EoK is Low saying that the RCP is ready to accept data. 2\.      Then it does the following 3 things on the next master clock cycle: * The SYSAD puts the address the CPU is requesting placed on it. * SYSCMD is set to read – 128 bits * Pvalid goes low to state that there is valid data on the two buses. 3\.      On the next cycle the CPU will put the Pvalid high and wait for the RCP to respond. During this time the SYS buses will keep the address and command on the buses 4\.      Once the RCP has the data, the Evalid goes low, the CPU goes into High-Z mode and listens to the two buses. At that same time the RCP does the following: * The SYSAD has the first 32-bit data to write. * The SYSCMD outputs the End Data command * Evalid stays low to state that there is valid data on the two buses. 5\.      On the next clock cycle the RCP will output the data to write and the following happens: * The SYSAD has the second 32-bit data to write. * The SYSCMD outputs the End Data command * Evalid stays low to state that there is valid data on the two buses. 6\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the third 32-bit data to write. * The SYSCMD outputs the End Data command * Evalid stays low to state that there is valid data on the two buses. 7\.      On the next clock cycle the CPU will output the data to write and the following happens: * The SYSAD puts the fourth 32 bit data to write. * SYSCMD is set to Command – Response data * Pvalid stays low to state that there is valid data on the two buses. 8\.      On the next master clock cycle the RCP will place the Evalid high to complete the transfer. Instruction: Cached read 256 bits ================================= The address for I-cache reads will always be 256 bit aligned for the address (So that last 5 bits of the address will always be 00000) This is because the I-Cache memory is setup as a 256 aligned entry and there is no smarts in the 4300i CPU to know if all of the cache entry is full. Thus, the CPU must have a full entry in the icache before it can load instructions. The 256 reads run the same as the D-cache reads (and are sequential order). But the command sent to the RCP is the read – 256 it command. And 8x 32 bit data accesses are sent over the SYSAD bus  [](https://n64brew.dev/wiki/File:Read_icache_256bit.png) Read from the CPU that is 256 Bits for the I-cache CPU Throughput tips, tricks and ideas ===================================== These ideas are subjective and are only from a hardware over look perspective. * Cache, cache and cache all ram accesses. Ram is hard to get from the RCP and the CPU can be waiting for a long time before data is passed on from the RDRAM. * All RCP register access should not be done via cache memory locations in CPU. As the write backs will not work correctly and will cause the RCP to crash. Also keep them at 32 bit read and writes as they only work like this and there is no waste in bandwidth over the 32bit bus. * The SYSAD bus is advertised as a 250mbyte/second interface. But due to the bidirectional and wait states, I would believe max throughput would be more to the 200Mbyte/second or less mark. * When caching memory try and keep to a 16kbyte instruction blocks (thus keeping your cache hits higher) and data in 8Kbyte blocks. Cache opcodes can also help in the fulling and dumping of memory locations. These program locations can be “pre-cached/fulled” by using the CP0 co-processer and then activating it using the cache opcode. These opcodes are very helpful if used correctly and some keep the CPU running. * And something I would love to see done. Use the DMEM and IMEM in the RSP core like a fast ram access (Just remember these are 32 bit writes only) so no caching. But you can DMA to from ram to them and then used this as a cache ‘bootcode’ for the cache opcode process. Then run in cache memory. This ram is very fast and has about a 4-5 clock cycle wait time, where the RDRAM has about 10-20+ clock wait time for a data process to happen. Retrieved from "[https://n64brew.dev/wiki/SysAD\_Interface?oldid=5037](https://n64brew.dev/wiki/SysAD_Interface?oldid=5037) " --- # Initial Program Load - N64brew Wiki [](https://n64brew.dev/wiki/IPL3#) Initial Program Load ==================== (Redirected from [IPL3](https://n64brew.dev/wiki/IPL3?redirect=no "IPL3") ) | | | | --- | --- | | ![](https://upload.wikimedia.org/wikipedia/commons/b/b4/Ambox_important.svg) | **Notice!**

This page is intended solely for research, archival, and preservation purposes. Disassembled code may be subject to copyright law. | The **Initial Program Load** (commonly called **IPL**, or the **boot sequence**) is a set of instructions that the console performs every time it starts. There are multiple stages to this process, referred to as: IPL1, IPL2 and IPL3. When a [64DD](https://n64brew.dev/wiki/64DD "64DD") is in use, there is also an IPL4 stage. Upon power-on, NMI, or "soft" reset, the program counter is set by hardware to `0xBFC00000`, which is the beginning of the PIF ROM address space. This ROM is baked into the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") chip. _When reading disassemblies, always remember that branch instructions have delay slots (which some branches may discard under certain circumstances). The operations performed in delay slots, may not be related to the branch itself._ Each line is formatted like so: `[address][opcode][assembly instruction] # comments`. Contents -------- * [1 IPL1](https://n64brew.dev/wiki/IPL3#IPL1) * [2 IPL2](https://n64brew.dev/wiki/IPL3#IPL2) * [2.1 IPL3](https://n64brew.dev/wiki/IPL3#IPL3) * [2.1.1 Nintendo proprietary IPL3](https://n64brew.dev/wiki/IPL3#Nintendo_proprietary_IPL3) * [2.1.2 Libdragon open-source IPL3](https://n64brew.dev/wiki/IPL3#Libdragon_open-source_IPL3) * [3 IPL4](https://n64brew.dev/wiki/IPL3#IPL4) IPL1 ==== This stage spans only the first `0xD4` bytes (`0xBFC00000 - 0xBFC000D3`) as executing instructions directly out of the PIF is relatively slow. Only what is absolutely necessary, is executed here. IPL1 resets the console to a consistent reset state, and moves the remaining bytes of the PIF ROM, that is IPL2, into the RSP's IMEM (`0xA4001000`). The goal of IPL1 is basically to move out execution context from the PIF as soon as possible for two reasons: first, running code directly from PIF requires a very slow serial transfer for each transferred word, and given that CPU cache doesn't work in this context, execution is **very** slow. Second, as soon as the CPU is out of this context, the PIF ROM can be locked again, for security reasons, hoping to minimize the window in which PIF returns the ROM on the bus and can thus be easily dumped. \### IPL1 ### 0xBFC00000 - 0xBFC000D3 (0xD4 bytes long) ### \# SEGMENT 1 # Initialize CP0 Status & Config registers \[0xBFC00000\]\[0x3C093400\]\[LUI t1, 0x3400\] \# t1 = 0x34000000 \[0xBFC00004\]\[0x40896000\]\[MTC0 t1, SR\] \# SR = t1 (enables CP0, CP1, and FPU registers) \[0xBFC00008\]\[0x3C090006\]\[LUI t1, 0x0006\] \# t1 = 0x00060000 \[0xBFC0000C\]\[0x3529E463\]\[ORI t1, t1, 0xE463\] \# t1 = 0x0006E463 \[0xBFC00010\]\[0x40898000\]\[MTC0 t1, Config\] \# Config = t1 (sets SysAD port writeback pattern to "D", sets Big-Endian mode, and sets KSEG0 as a cached region) \# SEGMENT 2 # \# 2a: Wait for RSP halt \[0xBFC00014\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \[0xBFC00018\]\[0x8D080010\]\[LW t0, t0, 0x0010\] \# t0 = value stored at 0xA4040010 (RSP\_STATUS register) \[0xBFC0001C\]\[0x31080001\]\[ANDI t0, t0, 0x0001\] \# t0 = t0 & 0x0001 (isolates the 'halt' bit) \[0xBFC00020\]\[0x5100FFFD\]\[BEQL t0, zr, 0xFFFD\] \# if t0 == 0, branch to 0xBFC00018 (this is a spin loop, waiting for the RSP to halt) \[0xBFC00024\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \[0xBFC00028\]\[0x2408000A\]\[ADDIU t0, zr, 0x000A\] \# t0 = 0x0000000A \[0xBFC0002C\]\[0x3C01A404\]\[LUI at, 0xA404\] \# at = 0xA4040000 \[0xBFC00030\]\[0xAC280010\]\[SW t0, at, 0x0010\] \# write t0 (0x0000000A) into 0xA4040010 (RSP\_STATUS register: sets 'halt' and clears 'rsp interrupt' bits) \[0xBFC00034\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \[0xBFC00038\]\[0x8D080018\]\[LW t0, t0, 0x0018\] \# t0 = value stored at 0xA4040018 (RSP\_DMA\_BUSY register) \[0xBFC0003C\]\[0x31080001\]\[ANDI t0, t0, 0x0001\] \# t0 = t0 & 0x0001 (isolates the 'halt' bit) \[0xBFC00040\]\[0x5500FFFD\]\[BNEL t0, zr, 0xFFFD\] \# if t0 != 0, branch to 0xBFC00038 (this is a spin loop, waiting for the RSP to not be halted) \[0xBFC00044\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \# 2b: Reset PI \[0xBFC00048\]\[0x24080003\]\[ADDIU t0, zr, 0x0003\] \# t0 = 0x00000003 \[0xBFC0004C\]\[0x3C01A460\]\[LUI at, 0xA460\] \# at = 0xA4600000 \[0xBFC00050\]\[0xAC280010\]\[SW t0, at, 0x0010\] \# write t0 (0x00000003) into 0xA4600010 (PI\_STATUS register: clears PI interrupt and resets PI controller) \# 2c: Clear video output \[0xBFC00054\]\[0x240803FF\]\[ADDIU t0, zr, 0x03FF\] \# t0 = 0x000003FF \[0xBFC00058\]\[0x3C01A440\]\[LUI at, 0xA440\] \# at = 0xA4400000 \[0xBFC0005C\]\[0xAC28000C\]\[SW t0, at, 0x000C\] \# write t0 (0x000003FF) into 0xA440000C (VI\_V\_INTR register: sets vertical interrupt trigger to half-line 0x3FF) \[0xBFC00060\]\[0x3C01A440\]\[LUI at, 0xA440\] \# at = 0xA4400000 \[0xBFC00064\]\[0xAC200024\]\[SW zr, at, 0x0024\] \# write zr (0x00000000) into 0xA4400024 (VI\_H\_VIDEO register: sets the start and end of active video image to zero) \[0xBFC00068\]\[0x3C01A440\]\[LUI at, 0xA440\] \# at = 0xA4400000 \[0xBFC0006C\]\[0xAC200010\]\[SW zr, at, 0x0010\] \# write zr (0x00000000) into 0xA4400010 (VI\_V\_CURRENT register: clears the VI interrupt) \# 2d: Stop audio \[0xBFC00070\]\[0x3C01A450\]\[LUI at, 0xA450\] \# at = 0xA4500000 \[0xBFC00074\]\[0xAC200000\]\[SW zr, at, 0x0000\] \# write zr (0x00000000) into 0xA4500000 (AI\_DRAM\_ADDR register: sets DMA RDRAM address to zero) \[0xBFC00078\]\[0x3C01A450\]\[LUI at, 0xA450\] \# at = 0xA4500000 \[0xBFC0007C\]\[0xAC200004\]\[SW zr, at, 0x0004\] \# write zr (0x00000000) into 0xA4500004 (AI\_LENGTH register: sets transfer length to zero) \# 2e: Wait for any RSP DMAs to complete \[0xBFC00080\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \[0xBFC00084\]\[0x8D080010\]\[LW t0, t0, 0x0010\] \# t0 = value stored at 0xA4040010 (RSP\_STATUS register) \[0xBFC00088\]\[0x31080004\]\[ANDI t0, t0, 0x0004\] \# t0 = t0 & 0x0004 (isolates the 'dma busy' bit) \[0xBFC0008C\]\[0x5500FFFD\]\[BNEL t0, zr, 0xFFFD\] \# if t0 != 0, branch to 0xBFC00084 (this is a spin loop, waiting for any RSP DMAs to complete) \[0xBFC00090\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \# SEGMENT 3 # Copy IPL2 from PIF to RSP IMEM \# 3a: Initialize start/end addresses \[0xBFC00094\]\[0x3C0BA400\]\[LUI t3, 0xA400\] \# t3 = 0xA4000000 \[0xBFC00098\]\[0x3C0CBFC0\]\[LUI t4, 0xBFC0\] \# t4 = 0xBFC00000 \[0xBFC0009C\]\[0x3C0DBFC0\]\[LUI t5, 0xBFC0\] \# t5 = 0xBFC00000 \[0xBFC000A0\]\[0x256B1000\]\[ADDIU t3, t3, 0x1000\] \# t3 = 0xA4001000 (start of RSP IMEM) \[0xBFC000A4\]\[0x258C00D4\]\[ADDIU t4, t4, 0x00D4\] \# t4 = 0xBFC000D4 (start of IPL2 in PIF ROM) \[0xBFC000A8\]\[0x25AD071C\]\[ADDIU t5, t5, 0x071C\] \# t5 = 0xBFC0071C (end of IPL2 in PIF ROM) \# 3b: Load/Store 1 word (4 bytes) at a time, moving instruction data from \[0xBFC000D4 - 0xBFC0071B\], into \[0xA4001000 - 0xA4001647\] \[0xBFC000AC\]\[0x8D890000\]\[LW t1, t4, 0x0000\] \# t1 = value stored at address t4 \[0xBFC000B0\]\[0x258C0004\]\[ADDIU t4, t4, 0x0004\] \# increment t4 (+4) \[0xBFC000B4\]\[0x256B0004\]\[ADDIU t3, t3, 0x0004\] \# increment t3 (+4) \[0xBFC000B8\]\[0x158DFFFC\]\[BNE t4, t5, 0xFFFC\] \# if t4 != t5, branch to 0xBFC000AC (repeat the loop) \[0xBFC000BC\]\[0xAD69FFFC\]\[SW t1, t3, 0xFFFC\] \# write t1 into address (t3 - 4) \# SEGMENT 4 # Jump to IPL2 in IMEM \[0xBFC000C0\]\[0x3C0BA400\]\[LUI t3, 0xA400\] \# t3 = 0xA4000000 \[0xBFC000C4\]\[0x3C1DA400\]\[LUI sp, 0xA400\] \# sp = 0xA4000000 \[0xBFC000C8\]\[0x256B1000\]\[ADDIU t3, t3, 0x1000\] \# t3 = 0xA4001000 (beginning of IMEM and IPL2) \[0xBFC000CC\]\[0x01600008\]\[JR t3\] \# jump to IMEM/IPL2 (and executes delay slot) \[0xBFC000D0\]\[0x37BD1FF0\]\[ORI sp, sp, 0x1FF0\] \# sp = 0xA4001FF0 (this prepares sp for use in IPL2) IPL2 ==== After IPL1 finishes moving the instructions of this stage to RSP IMEM, it jumps to `0x04001000` to start this second stage. IPL2 has just a very simple task: it loads IPL3 from the ROM (offsets 0x40-0x1000) and verifies its checksum. To do so, it actually sends the checksum to the PIF, which verifies if it's correct by separately fetching the expected one from the CIC. If the checksum doesn't match, PIF halts execution of the CPU (by asserting the NMI pin). IPL2 instead just proceeds to calling IPL3, hoping for the best. To access the ROM and be able to read the IPL3, IPL2 needs to configure the PI bus access timings. To do first, first it configures PI to its minimum speed, and reads the [first 4 bytes of the header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . Byte 1-3 in fact are expected to contain the (fastest) PI timings that the ROM supports. It then configures those timings, and proceeds reading the IPL3 into DMEM, one word at a time (notice that DMA wouldn't be possible here, because PI DMA only reads/writes to RDRAM, not DMEM). IPL3 ---- This stage is executed out of RSP DMEM, which was loaded with the first `0x1000` bytes of the game cartridge (or alternatively, whatever may be inserted in the cartridge or expansion slot on the bottom of the console). IPL2 will jump to `0x04000040` (`0x40` bytes after the start of DMEM, to account for the ROM header). The goal of IPL3 is to initialize RDRAM, which requires a complex [initialization process](https://n64brew.dev/wiki/RDRAM#Initialization_Sequence "RDRAM") that includes current calibration. During this stage, IPL3 also discovers whether there is an Expansion Pak installed and thus whether the additional 4 MiB of RDRAM are available. After RDRAM is initialized, it proceeds to booting the game. ### Nintendo proprietary IPL3 Over the course of the commercial life of the console, Nintendo released six known variants of the IPL3 stage, each one with its own matching CIC chip. In fact, as discussed previously for IPL2, each IPL3 bootcode has its checksum verified against the hardcoded value written in the CIC chip. This basically means that you can't easily change a physical CIC chip on the cartridge, without also replacing the IPL3 code. A seventh variant can be seen when analyzing the dumps of the GameBooster 64, Action Replay Pro 64, and GameShark Pro (v3.3): where the `0x0FC0` bytes after the header, are all zeros, and are dynamically loaded by the cartridge. For each of the six variants, there is a matching CIC chip found in each cartridge. There are 10 official CIC chips: | NTSC | PAL | % of ROMs (Qty) | | --- | --- | --- | | CIC-NUS-6102 | CIC-NUS-7101 | 87.6% (826) | | CIC-NUS-6103 | CIC-NUS-7103 | 6.5% (61) | | CIC-NUS-6105 | CIC-NUS-7105 | 4.5% (43) | | CIC-NUS-6106 | CIC-NUS-7106 | 0.09% (8) | | CIC-NUS-6101 | CIC-NUS-7102 | 0.05% (5) | Each of these NTSC/PAL pairs share an IPL3 variant, except **6101** and **7102**. After Nintendo IPL3 has initialized the RDRAM, it proceeds loading 1 MiB of game code i(ROM addresses 0x10001000 - 0x10101000), into RDRAM starting from the boot address specified in the [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") (offset 0x8), and the jumping to it. This actually finishes the boot sequence and hands off the execution to the actual game. The amount of code being loaded (1 MiB) is hardcoded and cannot be changed. Games are expected to cope with it. In most cases, this is more than needed, and extra loaded data is basically ignored (and maybe reloaded as part of the game asset loading code, to different addresses). If the game requires more than 1 MiB of code, it is expected to load it manually (eg: via dynamically loadable code segments, sometimes called "overlays"). Details to be added here... Details to be added here... Details to be added here... Details to be added here... Details to be added here... Details to be added here... ### Libdragon open-source IPL3 The open source homebrew SDK [Libdragon](https://libdragon.dev/) ships with an [open source IPL3](https://github.com/DragonMinded/libdragon/tree/trunk/boot) , that is embedded by default in all the ROMs made with libdragon. Compared to the proprietary one, it is much faster (boots in ~100ms vs ~500ms for an average ROM) and expects the game in ELF format, so that it is able to properly read text and data segments of any size (not just a single hardcoded one) and clear BSS. It is unencumbered and was implemented with a clean room approach, so it is supposedly free of licensing concerns. To allow booting on a real console in a true hardware boot scenario (eg: replica cartridges), each release binary is bruteforced (with GPUs) so that its checksum matches the expected one for CIC 6102. IPL2 will then successfully recognizes it as a valid IPL3 for that CIC, and allows boot to proceed. IPL4 ==== **TODO** Retrieved from "[https://n64brew.dev/wiki/Initial\_Program\_Load?oldid=5596](https://n64brew.dev/wiki/Initial_Program_Load?oldid=5596) " --- # Parallel Interface - N64brew Wiki [](https://n64brew.dev/wiki/PI#) Parallel Interface ================== (Redirected from [PI](https://n64brew.dev/wiki/PI?redirect=no "PI") ) The Parallel Interface (commonly referred to as the **PI**, or Peripheral Interface) is one of multiple I/O interfaces in the RCP, which is used to communicate with [game cartridges](https://n64brew.dev/wiki/Game_Pak "Game Pak") or other devices connected to either the cartridge port or expansion port on the bottom of the console. (e.g. 64DD) The PI is not to be confused with the [PIF](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") (or PIF-NUS) which is an entirely separate IC. Memory mapped registers are used to configure the Parallel Interface and initiate DMA reads and writes. The base address for these registers is `0x0460 0000`, 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 `0xA000 0000` to the address. As an example, to directly write to the PI\_DRAM\_ADDR register, use address `0xA460 0000`. Contents -------- * [1 The PI Bus](https://n64brew.dev/wiki/PI#The_PI_Bus) * [1.1 Domains](https://n64brew.dev/wiki/PI#Domains) * [1.2 Open bus behavior](https://n64brew.dev/wiki/PI#Open_bus_behavior) * [2 Registers](https://n64brew.dev/wiki/PI#Registers) * [2.1 0x0460 0000 - PI\_DRAM\_ADDR](https://n64brew.dev/wiki/PI#0x0460_0000_-_PI_DRAM_ADDR) * [2.2 0x0460 0004 - PI\_CART\_ADDR](https://n64brew.dev/wiki/PI#0x0460_0004_-_PI_CART_ADDR) * [2.3 0x0460 0008 - PI\_RD\_LEN](https://n64brew.dev/wiki/PI#0x0460_0008_-_PI_RD_LEN) * [2.4 0x0460 000C - PI\_WR\_LEN](https://n64brew.dev/wiki/PI#0x0460_000C_-_PI_WR_LEN) * [2.5 0x0460 0010 - PI\_STATUS](https://n64brew.dev/wiki/PI#0x0460_0010_-_PI_STATUS) * [2.6 0x0460 00n4 - PI\_BSD\_DOMn\_LAT](https://n64brew.dev/wiki/PI#0x0460_00n4_-_PI_BSD_DOMn_LAT) * [2.7 0x0460 00n8 - PI\_BSD\_DOMn\_PWD](https://n64brew.dev/wiki/PI#0x0460_00n8_-_PI_BSD_DOMn_PWD) * [2.8 0x0460 00nC - PI\_BSD\_DOMn\_PGS](https://n64brew.dev/wiki/PI#0x0460_00nC_-_PI_BSD_DOMn_PGS) * [2.9 0x0460 00n0 - PI\_BSD\_DOMn\_RLS](https://n64brew.dev/wiki/PI#0x0460_00n0_-_PI_BSD_DOMn_RLS) * [3 iQue Player-specific registers](https://n64brew.dev/wiki/PI#iQue_Player-specific_registers) * [3.1 0x0460 0040 - PI\_BB\_ATB\_UPPER](https://n64brew.dev/wiki/PI#0x0460_0040_-_PI_BB_ATB_UPPER) * [3.2 0x0460 0048 - PI\_BB\_NAND\_CTRL](https://n64brew.dev/wiki/PI#0x0460_0048_-_PI_BB_NAND_CTRL) * [3.3 0x0460 004C - PI\_BB\_NAND\_CFG](https://n64brew.dev/wiki/PI#0x0460_004C_-_PI_BB_NAND_CFG) * [3.4 0x0460 0058 - PI\_BB\_RD\_LEN](https://n64brew.dev/wiki/PI#0x0460_0058_-_PI_BB_RD_LEN) * [3.5 0x0460 005C - PI\_BB\_WR\_LEN](https://n64brew.dev/wiki/PI#0x0460_005C_-_PI_BB_WR_LEN) * [3.6 0x0460 0060 - PI\_BB\_GPIO](https://n64brew.dev/wiki/PI#0x0460_0060_-_PI_BB_GPIO) * [3.7 0x0460 0070 - PI\_BB\_NAND\_ADDR](https://n64brew.dev/wiki/PI#0x0460_0070_-_PI_BB_NAND_ADDR) * [3.8 0x0461 0500 to 0x0461 0800 - PI\_BB\_ATB\_LOWER](https://n64brew.dev/wiki/PI#0x0461_0500_to_0x0461_0800_-_PI_BB_ATB_LOWER) * [4 iQue Player-specific memory](https://n64brew.dev/wiki/PI#iQue_Player-specific_memory) * [5 Physical Bus Pinout](https://n64brew.dev/wiki/PI#Physical_Bus_Pinout) * [5.1 PI Interface Process](https://n64brew.dev/wiki/PI#PI_Interface_Process) * [5.1.1 Address output](https://n64brew.dev/wiki/PI#Address_output) * [5.1.2 Data Read](https://n64brew.dev/wiki/PI#Data_Read) * [5.1.3 Constant Read](https://n64brew.dev/wiki/PI#Constant_Read) * [6 DMA Transfers](https://n64brew.dev/wiki/PI#DMA_Transfers) * [6.1 Internal process](https://n64brew.dev/wiki/PI#Internal_process) * [6.2 Internal process: first block](https://n64brew.dev/wiki/PI#Internal_process:_first_block) * [6.3 Followup transfers](https://n64brew.dev/wiki/PI#Followup_transfers) * [6.4 PI\_WR\_LEN readbacks after a transfer](https://n64brew.dev/wiki/PI#PI_WR_LEN_readbacks_after_a_transfer) * [6.5 DMA data dumps](https://n64brew.dev/wiki/PI#DMA_data_dumps) 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](https://n64brew.dev/wiki/Memory_map "Memory map") 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 `0x0500_0000 - 0x1FBF_FFFF` and `0x1FD0_0000 - 0x7FFF_FFFF`. 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#Ranges 0x0500'0000 - 0x1FBF'FFFF and 0x1FD0'0000 - 0x7FFF'FFFF (PI external bus)](https://n64brew.dev/wiki/Memory_map#Ranges_0x0500'0000_-_0x1FBF'FFFF_and_0x1FD0'0000_-_0x7FFF'FFFF_(PI_external_bus) "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: | | | | | --- | --- | --- | | PI address range | Domain | Device | | 0x0000\_0000 - 0x04FF\_FFFF | Domain 1 | No known device exists that operates in this range | | 0x0500\_0000 - 0x05FF\_FFFF | Domain 2 | 64DD registers | | 0x0600\_0000 - 0x07FF\_FFFF | Domain 1 | 64DD ROM | | 0x0800\_0000 - 0x0FFF\_FFFF | Domain 2 | SRAM | | 0x1000\_0000 - 0xFFFF\_FFFF | Domain 1 | ROM (though this address range is huge, and ROM only typically occupied a small portion of it) | 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 `0x6666_DCBA` will return the value `0xDCBA_DCBA`. 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 * * * | PI\_DRAM\_ADDR `0x0460 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | | DRAM\_ADDR\[7:1\] | | | | | | | 0 | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-1 | **DRAM\_ADDR\[23:1\]:** Base address of RDRAM for PI DMAs; notice that bit 0 cannot be written and is fixed to zero. | **Extra Details:** Note that DMA transfers are buggy if DRAM\_ADDR\[2:0\] are not all zero, see [below](https://n64brew.dev/wiki/PI#Unaligned_DMA_transfer) . #### 0x0460 0004 - PI\_CART\_ADDR * * * | PI\_CART\_ADDR `0x0460 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[31:24\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | | CART\_ADDR\[7:1\] | | | | | | | 0 | | | | | --- | --- | | bit 31-1 | **CART\_ADDR\[31:1\]:** Base address of the PI bus (e.g. cartridge) for PI DMAs; notice that bit 0 cannot be written and is fixed to 0. | **Extra Details:** This register is automatically updated by PI after any PI transfer (both DMA and direct I/O). In both cases, it will contain the first address _after_ the last transferred word. DMA transfers are a bit complex in this regard, so see below in this page where the mechanics of the transfers are detailed. #### 0x0460 0008 - PI\_RD\_LEN * * * | PI\_RD\_LEN `0x0460 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **RD\_LEN\[23:0\]:** Number of bytes, minus one, to be transferred from RDRAM, to the PI bus | **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 * * * | PI\_WR\_LEN `0x0460 000C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **WR\_LEN\[23:0\]:** Number of bytes, minus one, to be transferred from the PI bus, into RDRAM | **Extra Details:** Writing to this register will start the DMA transfer. Reading appears to almost always return \`0x7F\` (see [below](https://n64brew.dev/wiki/Peripheral_Interface#PI_WR_LEN_readbacks_after_a_transfer "Peripheral Interface") for exceptions). #### 0x0460 0010 - PI\_STATUS * * * | PI\_STATUS `0x0460 0010` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | R-0 | R-0 | RW-0 | RW-0 | | — | — | — | — | 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 * * * | PI\_BSD\_DOM1\_LAT `0x0460 0014`

PI\_BSD\_DOM2\_LAT `0x0460 0024` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | LAT\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-8 | **Undefined:** Initialized to `0` | | bit 7-0 | **LAT\[7:0\]:** The "LATch" value is the number of RCP cycles, minus one, after the address has been sent (falling edge of ALE\_L) and before the first read or write may start (falling edge of /RD or /WR) | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's LAT using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set LAT = 64 (meaning (64+1)\*16 = 1040ns). #### 0x0460 00n8 - PI\_BSD\_DOMn\_PWD * * * | PI\_BSD\_DOM1\_PWD `0x0460 0018`

PI\_BSD\_DOM2\_PWD `0x0460 0028` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | PWD\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-8 | **Undefined:** Initialized to `0` | | bit 7-0 | **PWD\[7:0\]:** The "Pulse WiDth" value is the number of RCP cycles, minus one, the /RD or /WR signals are held low | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's PWD using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set PWD = 18 (meaning (18+1)\*16 = 304ns). #### 0x0460 00nC - PI\_BSD\_DOMn\_PGS * * * | PI\_BSD\_DOM1\_PGS `0x0460 001C`

PI\_BSD\_DOM2\_PGS `0x0460 002C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | PGS\[3:0\] | | | | | | | | --- | --- | | bit 31-4 | **Undefined:** Initialized to `0` | | bit 3-0 | **PGS\[3:0\]:** The "PaGe Size" value configures how many bytes can be sequentially read/written on the bus before sending the next base address (Size = 2^(PGS+2) bytes) | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's PGS using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set PGS = 7 (meaning 2^(7+2) = 512 bytes). The smallest possible value, 0, means 2^(0+2) = 4 bytes; the largest means 2^(15+2) = 128KiB. Page Size only matters for DMA transfers; all direct accesses via the PI are only ever 32 bits wide. Notice that Page Size refers to aligned pages. For instance, with the default setting of 512 bytes, the PI will never allow a single burst to cross a 512 byte boundary. For instance, requesting 16 bytes from address 508 will actually generate two different burst transfers on the PI bus: the first of 4 bytes from offset 508, and the second of 12 bytes from offset 512. #### 0x0460 00n0 - PI\_BSD\_DOMn\_RLS * * * | PI\_BSD\_DOM1\_RLS `0x0460 0020`

PI\_BSD\_DOM2\_RLS `0x0460 0030` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | RLS\[1:0\] | | | | | | --- | --- | | bit 31-2 | **Undefined:** Initialized to `0` | | bit 1-0 | **RLS\[1:0\]:** The "ReLeaSe" value is the number of RCP cycles, minus one, that the /RD or /WR signals are held high between each 16-bits of data | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's RLS using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set RLS = 3 (meaning (3+1)\*16 = 64ns). iQue Player-specific 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 0040 - PI\_BB\_ATB\_UPPER * * * | PI\_BB\_ATB\_UPPER `0x0460 0040` (Read) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | IV Source | | 7:0 | U-? | U-? | U-? | U-? | U-? | | | | | — | — | CpuEn | DmaEn | log2(Num Blocks) | | | | | | | | --- | --- | | bit 8 | **IV Source:** Where to source the Initialization Vector from for AES decryption. See below. | | bit 5 | **CpuEn:** If set to 1, the mapping will be enabled for CPU reads | | bit 4 | **DmaEn:** If set to 1, the mapping will be enabled for DMA reads | | bit 3-0 | **log2(Num Blocks):** log2 of the number of contiguous NAND blocks to map. This is applied to an ATB entry when **ATB\_LOWER** registers are written. | **Extra Details** This register supplies only half of the configuration for an ATB entry, also see the **PI\_BB\_ATB\_LOWER** array of registers where PI addresses and the starting NAND block number are specified. Mappings work with sequences of blocks, whose length is a power of two. The register here contains the logarithm of the length so for instance writing "0" causes 1 block to be mapped; writing 4 causes 16 consecutive blocks to be mapped. ATB is the N64 PI address space emulator that translates PI DMAs into NAND flash accesses. Data stored on the NAND is encrypted with AES, ATB must transparently decrypt the data when a PI DMA requests it. To decrypt AES at an 0x10-aligned position **P** the data at **P-0x10** is also required, or if **P=0** then the Initialization Vector (IV) is required. At the start of a DMA, ATB will try to find the entry that maps the PI address for **P-0x10** into the NAND to fetch the needed prior data; for all cases but **P=0** this should resolve correctly with a contiguous PI address space mapping. To handle the **P=0** case an additional dummy mapping must precede the base address of the desired mapping, with the IV Source bit set to 1. When the IV Source bit is 1 the IV will be pulled from the memory at **0x046104D0** rather than reading any data off the NAND. For example if the mapping begins at PI address 0x10000000 as for Cartridge ROM, a dummy mapping for PI address 0x0FFFC000 with IV Source set to 1 should be programmed. #### 0x0460 0048 - PI\_BB\_NAND\_CTRL * * * | PI\_BB\_NAND\_CTRL `0x0460 0048` (Read) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | Busy | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | R-0 | R-0 | U-? | U-? | | — | — | — | — | Single-bit Error | Double-bit Error | — | — | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31 | **Busy:** Indicates that a command is currently executing. | | bit 11 | **Single-bit Error:** Indicates that a single-bit error was detected by ECC. These are automatically corrected so generally no action is required. | | bit 10 | **Double-bit Error:** Indicates that a double-bit error was detected by ECC. Unlike single-bit errors, these are not automatically recoverable. | | PI\_BB\_NAND\_CTRL `0x0460 0048` (Write) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | Execute | Interrupt | — | — | — | — | — | — | | 23:16 | W-0 | | | | | | | | | NAND Command | | | | | | | | | 15:8 | W-0 | W-0 | W-0 | | W-0 | W-0 | W-0 | | | — | Buffer Select | Device Select | | Do ECC | Multicycle | Data Length \[9:8\] | | | 7:0 | W-0 | | | | | | | | | Data Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit 31 | **Execute:** Setting this bit when writing will cause the last written command to begin execution. | | bit 30 | **Interrupt:** Whether the FLASH interrupt should be raised when the command finishes execution. | | bit 29 | **?:** Unknown. Set when issuing Page Program (first cycle) | | bit 28 | **?:** Unknown. Set when issuing Read 1, Read Status and Read ID | | bit 27 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 26 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 25 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 24 | **?:** Unknown. Set when issuing Read 1, Read ID and Page Program (first cycle) | | bit 23-16 | **NAND Command:** NAND Command to execute. Corresponds directly to commands for the K9F1208U0M flash. | | bit 15 | **?:** Unknown. Set when issuing Read 1, Block Erase (second cycle) and Page Program (second cycle) | | bit 14 | **Buffer Select:** Selects which half of the 0x400-byte PI Buffer mapped at 0x04610000 should be used for DMA operations. See **iQue Player-specific Memory** for details on this buffer | | bit 13-12 | **Device Select:** Corresponds to Chip Enable signals on the card connector. Typically 0. | | bit 11 | **Do ECC:** Whether to do ECC | | bit 10 | **Multicycle:** Set to 1 if the command issued was not the last command in a multi-cycle sequence. | | bit 9-0 | **Data Length:** Data transfer length in bytes. Unlike most other lengths this is not length minus one, a length of 0 can be specified. | **Extra Details:** Writing 0 to this register will clear any pending FLASH interrupt. #### 0x0460 004C - PI\_BB\_NAND\_CFG * * * | PI\_BB\_NAND\_CFG `0x0460 004C` | | | | | | | | | | --- | --- | | 31:24 | U-? | | | | | | | | | Configuration | | | | | | | | | 23:16 | U-? | | | | | | | | | Configuration | | | | | | | | | 15:8 | U-? | | | | | | | | | Configuration | | | | | | | | | 7:0 | U-? | | | | | | | | | Configuration | | | | | | | | | | | | --- | --- | | bit 31-0 | **Configuration:** Likely specifies timing configurations for different NAND flash chips. It is currently unknown how to relate values programmed into this register and timing information found in datasheets. | **Extra Details** System software programs `0x753E3EFF` into this register to execute a Read ID command, then selects an appropriate configuration based on the ID: | ID \[31:16\] | NAND Size in Blocks | NAND Size in MiB | Configuration Value | Part Number | | --- | --- | --- | --- | --- | | 0xEC76 | 0x1000 | 64 | 0x441F1F3F | K9F1208U0M | | 0xEC79 | 0x2000 | 128 | 0x441F1F3F | K9K1G08U0A or K9K1G08U0B | | 0x9876 | 0x1000 | 64 | 0x753E1F3F | TC58512FT | | 0x2076 | 0x1000 | 64 | 0x441F1F3F | NAND512W3A | #### 0x0460 0058 - PI\_BB\_RD\_LEN * * * | PI\_BB\_RD\_LEN `0x0460 0058` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | | | | | — | | | | | | | | | 23:16 | U-? | | | | | | | | | — | | | | | | | | | 15:8 | U-? | | | | | | | W-? | | — | | | | | | | Length \[8\] | | 7:0 | W-? | | | | | | | | | Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Length:** DMA Transfer Length (-1). Writes initiate a DMA from SDRAM starting at **PI\_DRAM\_ADDR** to the PI Buffer at **0x04610000 + PI\_CART\_ADDR**. Exact bit width unknown, it is at least long enough to transfer 0x200 bytes. | **Extra Details** It is currently unknown what the behavior is if a DMA extends out of the bounds of the target PI Buffer, and whether both buffers can be accessed in one transfer. Spare buffers can't be accessed via DMA though. The busy bits in **PI\_STATUS** also applies to these transfers. These transfers also trigger an interrupt upon completion. It is the same interrupt used for regular PI DMAs. #### 0x0460 005C - PI\_BB\_WR\_LEN * * * | PI\_BB\_WR\_LEN `0x0460 005C` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | | | | | — | | | | | | | | | 23:16 | U-? | | | | | | | | | — | | | | | | | | | 15:8 | U-? | | | | | | | W-? | | — | | | | | | | Length \[8\] | | 7:0 | W-? | | | | | | | | | Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Length:** DMA Transfer Length (-1). Writes initiate a DMA from the PI Buffer at **0x04610000 + PI\_CART\_ADDR** to SDRAM starting at **PI\_DRAM\_ADDR**. Exact bit width unknown, it is at least long enough to transfer 0x200 bytes. | **Extra Details** It is currently unknown what the behavior is if a DMA extends out of the bounds of the target PI Buffer, and whether both buffers can be accessed in one transfer. Spare buffers can't be accessed via DMA though. The busy bits in **PI\_STATUS** also applies to these transfers. These transfers also trigger an interrupt upon completion. It is the same interrupt used for regular PI DMAs. #### 0x0460 0060 - PI\_BB\_GPIO * * * | PI\_BB\_GPIO `0x0460 0060` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-? | | U-? | U-? | U-? | R-? | | R-? | | Box ID \[15:14\] | | — | — | — | Box ID \[10:9\] | | Box ID \[8:6\] \[2\] | | 23:16 | R-? | | U-? | U-? | U-? | U-? | U-? | U-? | | Box ID \[8:6\] \[1:0\] | | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | RW-? | | RW-? | RW-? | RW-? | | RW-? | RW-? | | RTC Output Enable | | LED Output Enable | Power Output Enable | RTC Control | | LED Control | Power Control | | | | | --- | --- | | bit 31-16 | **Box ID \[15:0\]:** System software calls this area the "Box ID". Various sub-fields are read out of this area separately for varying purposes. | | bit 31-30 | **Box ID \[15:14\]:** System software reads this as some sort of model identifier. Precise meaning unknown. Whether all players have the same value here is unknown. | | bit 26-25 | **Box ID \[10:9\]:** System clock speed identifier? System software reads this to determine delay intervals for some operations. | | bit 24-22 | **Box ID \[8:6\]:** The Boot ROM checks this against bits \[10:8\] in a register at MI+0x10, if they don't match this value is copied there and the system is rebooted? | | bit 7-6 | **RTC Output Enable:** Output enables for the RTC bit lines. If off, the bit lines will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit lines can be driven to logic low or logic high by writing 0 or 1 respectively to the RTC Control bits. | | bit 5 | **LED Output Enable:** Output enable for the LED bit line. If off, the bit line will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit line can be driven to logic low or logic high by writing 0 or 1 respectively to the LED Control bit. | | bit 4 | **Power Output Enable:** Output enable for the Power bit line. If off, the bit line will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit line can be driven to logic low or logic high by writing 0 or 1 respectively to the Power Control bit. | | bit 3-2 | **RTC Control:** RTC communication happens through these bits. The communication protocol is described in the [ST M41T0 Serial RTC datasheet](https://www.st.com/content/ccc/resource/technical/document/datasheet/19/24/95/e2/85/6a/47/30/CD00003139.pdf/files/CD00003139.pdf/jcr:content/translations/en.CD00003139.pdf)
; the lower bit is the clock line while the upper bit is the data line. When RTC Output Enable bits are 0 the RTC can drive the bus, in which case reading the RTC Control bits will read the values sent by the RTC. | | bit 1 | **LED Control:** If the LED Output Enable is 1, writing 0 or 1 to this bit will drive the LED bit line low or high. If 0, the LED on the front of the player will light up. If 1, the LED will switch off. | | bit 0 | **Power Control:** If the Power Output Enable is 1, writing 0 or 1 to this bit will drive the Power bit line low or high. If 1, the power will remain on. If 0, the device will power off. | **Extra Details:** Whenever a GPIO control bit (with its corresponding output enable bit set) is set to 1, the corresponding bit line will be set to logic high (3.3v). If set to 0 (with output enable set) the bit line is set to logic low (0v). The LED lights up when the LED GPIO is 0 as the LED requires a voltage difference across it to light up. One side of the LED is fixed to 3.3v while the other side is connected to the LED GPIO port; when LED Control is 1 there is no voltage difference across the LED (3.3 - 3.3 = 0v) so it does not light up, while an LED Control of 0 creates a voltage difference (3.3 - 0 = 3.3v) so the LED lights up. #### 0x0460 0070 - PI\_BB\_NAND\_ADDR * * * | PI\_BB\_NAND\_ADDR `0x0460 0070` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | W-? | | | | — | | | | | Address \[26:24\] | | | | 23:16 | W-? | | | | | | | | | Address \[23:16\] | | | | | | | | | 15:8 | W-? | | | | | | | | | Address \[15:8\] | | | | | | | | | 7:0 | W-? | | | | | | | | | Address \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Address:** Set the NAND flash address that commands issued by **PI\_BB\_NAND\_CTRL** will target. Exact bit width is unknown, however it is at least enough to address 128MiB (27 bits) | **Extra Details:** To convert a page number to an address, multiply it by 512. To convert a block number to an address, multiply it by 0x4000. #### 0x0461 0500 to 0x0461 0800 - PI\_BB\_ATB\_LOWER * * * | PI\_BB\_ATB\_LOWER `0x0461 0500 - 0x0461 0800` | | | | | | | | | | --- | --- | | 31:24 | U-? | | | | | | | | | NAND Block Number \[15:8\] | | | | | | | | | 23:16 | U-? | | | | | | | | | NAND Block Number \[7:0\] | | | | | | | | | 15:8 | U-? | | | | | | | | | PI Physical Address \[29:14\] \[15:8\] | | | | | | | | | 7:0 | U-? | | | | | | | | | PI Physical Address \[29:14\] \[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-16 | **NAND Block Number:** Starting block number to map to the provided PI address. | | bit 15-0 | **PI Physical Address \[29:14\]:** PI address to begin the mapping at, divided by 0x4000 the NAND block size. | **Extra Details** There are 192 **ATB\_LOWER** registers. Issuing a write to a particular register will program that ATB entry with a mapping, also using the current contents of **ATB\_UPPER** to complete the entry configuration. The number of blocks to map comes from **ATB\_UPPER**. Mappings involving non-contiguous or unsorted NAND blocks must occupy multiple ATB entries. These ATB entries should be sorted by PI address, from lowest to highest. It is not possible to write addresses that are not aligned to the NAND block size (0x4000) It is not possible to map more contiguous blocks than the PI address alignment allows in a single entry. For example it is not possible to map 2 contiguous blocks in the same ATB entry if the base address is 0x10004000. The maximum number of blocks you can map for given `(pi_addr, nblocks)` in a single ATB entry is `1 << min(ctz(pi_addr/0x4000), ceil(log2(nblocks)))` where `ctz(x)` counts the number of trailing zeros in the binary representation of `x`. iQue Player-specific memory =========================== In addition to extra registers, the iQue Player maps additional memory into the PI registers address space for use in various PI operations. | Address Range | | Name | Description | | --- | --- | --- | --- | | 0x04610000 | 0x046101FF | PI Buffer 0 | Holds intermediate data between SDRAM and the NAND. NAND commands transfer data between this buffer and the flash; transfers between this buffer and SDRAM is done via DMAs triggered by **PI\_BB\_RD\_LEN** and **PI\_BB\_WR\_LEN**. AES decryptions happen in this buffer. | | 0x04610200 | 0x046103FF | PI Buffer 1 | Same as Buffer 0 in operation. | | 0x04610400 | 0x0461040F | PI Spare Data 0 | Holds "spare data" for buffer 0 contents. | | 0x04610410 | 0x0461041F | PI Spare Data 1 | Holds "spare data" for buffer 1 contents. | | 0x04610420 | 0x046104CF | AES Expanded Key | Holds the AES expanded key for AES decryption operations. | | 0x046104D0 | 0x046104DF | AES Initialization Vector | Holds the AES IV for AES decryption operations. | Access by the CPU to these buffers must be performed as if the buffers were memory mapped from PI address space: that is, it is important that the PI status register reports that the PI unit is idle before attempting a read or a write. Physical Bus Pinout =================== The PI Bus is a Bi-directional and multiplexed interface with a 16bit data path to the ROM, 64DD, [Flash](https://n64brew.dev/wiki/Flash "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. | | | | | --- | --- | --- | | Pin Name | Cart pins | Description | | AD0 | 28 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[16\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[0\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[0\] | | AD1 | 29 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[17\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[1\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[1\] | | AD2 | 30 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[18\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[2\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[2\] | | AD3 | 32 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[19\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[3\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[3\] | | AD4 | 36 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[20\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[4\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[4\] | | AD5 | 37 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[21\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[5\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[5\] | | AD6 | 40 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[22\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[6\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[6\] | | AD7 | 41 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[23\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[7\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[7\] | | AD8 | 16 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[24\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[8\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[8\] | | AD9 | 15 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[25\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[9\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[9\] | | AD10 | 12 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[26\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[10\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[10\] | | AD11 | 11 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[27\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[11\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[11\] | | AD12 | 7 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[28\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[12\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[12\] | | AD13 | 5 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[29\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[13\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[13\] | | AD14 | 4 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[30\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[14\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[14\] | | AD15 | 3 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[31\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[15\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[15\] | | /ALEH | 35 | Parts on the PI bus are expected to latch the high address (Bits\[31:16\]) when this goes from HIGH to LOW.

When this signal goes from LOW to HIGH it resets the internal address system so it can await for a new address request.

This stays HIGH when in idle and LOW when processing data. Commercial ROMs also use this as /CE, entering a low-power state while this signal is high.

This signal will be high for at least 7 FSB cycles, each time a new address is loaded. | | /ALEL | 33 | Parts on the PI bus are expected to latch the low address (Bits\[15:0\]) when this goes from HIGH to LOW.

No action has been seen when this goes from LOW to HIGH.

This stays HIGH when in idle and LOW when processing data.

This signal will be high for at least 14 FSB cycles, each time a new address is loaded, ending 7 FSB cycles after ALEH falls. | | /WR | 8 | This is the signal that sends a write command to the FLASH ram, SRAM or 64DD

While this signal is low, the RCP drives the PI bus with the current word of data.

When this signal goes from LOW to HIGH external parts are expected to record the value at that moment, if they need it. The RCP and external parts are also expected to increase the internal address counter in preparation for the next word transferred.

The RCP will not change this signal from HIGH to LOW until either the Latency (PI\_BSD\_DOMn\_LAT) or Release (PI\_BSD\_DOMn\_RLS) registers have counted the required number of FSB clocks.

This stays HIGH when idle. | | /RD | 10 | This is the signal that sends a read command to the ROM, FLASH ram, SRAM or 64DD.

While this signal is low, the RCP expects that some device will drive the PI Bus.

When this signal goes from LOW to HIGH the RCP will record the value at that moment. The RCP and external parts are also expected to increase the internal address counter in preparation for the next word transferred.

The /RD signal has the same timing constraints as the /WR signal above.

This stays HIGH when idle. | PI Interface Process -------------------- ### Address output  [](https://n64brew.dev/wiki/File:Rom_address_output.png "Rom Address Output") ### Data Read  [](https://n64brew.dev/wiki/File:Rom_Read_Data.png "Rom Read Data") ### Constant Read  [](https://n64brew.dev/wiki/File:Constant_ROM_Access.png "Constant ROM Access") DMA Transfers ============= PI DMA is well defined for so-called "aligned transfers", which are defined by the following constraints: 1. RDRAM address must be 8 bytes aligned 2. PI address must be 2 bytes aligned 3. Length must be a multiple of 2 Notice that the second point might be considered redundant from a hardware point of view given that both registers holding addresses are fixed to be 2-byte aligned (LSB is fixed to 0), but from a software point of view, this has to be taken into account. The behavior of PI DMA when the first and third constraint are not respected is not well designed; it seems like the designers attempted to implement support for loosing these constraints but gave up in the middle, leaving the hardware in a state that can only be described as "buggy". This also leaks some internal details on how the transfers are performed. To implement PI DMA, the RCP uses an internal 128 byte buffer. The following section attempts to describe the exact process (though the \*actual\* process implemented in the hardware is unknown; the following does match in observable behavior). NOTE: only DMA write transfers (PI -> RDRAM) have been analyzed in detail, using default PI DOM1 settings. It is expected that read transfers (RDRAM -> PI) behave in a similar way, though it's not been fully tested yet. We also expect PI DOM1 page size setting to somehow affect the transfer, though this has also not been explored yet. #### Internal process The transfer is split in blocks of maximum 128 bytes each one. Within each block, the PI first fills the internal buffer fetching data from the PI bus, and then write backs the buffer contents to RDRAM. This can be observed by monitoring PI\_DRAM\_ADDR and PI\_CART\_ADDR: during the transfer, it can be first seen PI\_CART\_ADDR moving forward, and then PI\_DRAM\_ADDR catching up with a leap (writing to RDRAM is much faster than reading PI). In general for all blocks of the transfer (excluding the first one, see below), the logic appears to be as follows: * Compute the block size. This is the smallest between the remaining length, the end of the current RDRAM page, and 128 bytes (which is the maximum size of the internal buffer). RDRAM pages are 2 KiB (0x800) long, so for instance if the current RDRAM address (at the beginning of the block) is 0x147e0, the block size will be 0x20 because the RDRAM page ends at 0x147ff. * Fill the page using PI reads from the bus. All PI accesses are always 16-bit long, so if the block size was odd (which happens on the last block, if the remaining length is odd), one extra byte will be fetched from PI into the internal buffer. * Write back into RDRAM. The exact format of RDRAM writes is unknown at the moment; since PI DMA transfers are well-defined for 8-byte aligned RDRAM addresses, it is assumed that 64-bit writes are used (a burst like that used for D/I cache writebacks would require 16-byte alignment or more to be performed). If an extra byte was fetched in the previous step, that byte is also written to RDRAM. So in general odd-length PI DMA transfers will transfer one byte more than requested. The above logic applies for all blocks of the transfer, excluding the first one. The first block in fact is treated specially by PI. It appears that the goal of the designers was to use the first block to realign transfers to 8-byte in RDRAM, which possibly causes the first block to use smaller, masked writes to RDRAM. So, even if the RDRAM starting address is misaligned, all blocks besides the first one will begin from a 8-byte aligned RDRAM address, and behave with the logic described above. #### Internal process: first block These are the differences in logic while processing the first block, which mostly concerns how to handle the initial RDRAM misalignment. In this description, we refer to _RDRAM misalignment_ as the amount of bytes that the RDRAM address is distant from the previous 8-byte aligned word (that is, the misalignment is the value of the last 3 bits of the RDRAM address). Notice that the RDRAM address hardware register has the LSB fixed 0, so misalignment can be either 2, 4, or 6. * The internal 128 byte buffer is filled starting from the index matching the misalignment. This might affect the maximum size of the first block: for instance, if misalignment is 6, the maximum size is not 128 but 122, because the first 6 bytes are skipped. * Writes to RDRAM seems to use some kind of masking, so they are correctly done at the byte granularity. This means that odd length transfers in the first block appear to work correctly. Notice that this applies only to the first block whatever its size is; the size (as described above) might be limited by the end of the RDRAM page, in which case only odd transfers up to there are working correctly. * As an exception to the above exception, if the first block reaches the end of the 128 byte buffer, the last 2 bytes of the buffer are always written back in full to RDRAM, even though one less byte was requested. * Example: PI DMA transfer with misalignment 0 and RDRAM page end far away. Odd lengths up to 125 (included) work correctly; odd transfers of exactly 127 bytes are rounded up to 128 (since they reach the last 16-bit word of the buffer). Also odd transfers of 129 or more, since they need two blocks to be performed, fall back into the general rule where one more byte is transferred. * Example: PI DMA transfer with misalignment 6 and RDRAM page end far away. Odd lengths up to 119 (included) work correctly; odd transfers of exactly 121 bytes are rounded up to 122 (since they reach the last 16-bit word of the buffer). Also odd transfers of 123 or more, since they need two blocks to be performed, fall back into the general rule where one more byte is transferred. * There seems to be a hardware bug related how RDRAM writes are performed, in case of misaligned addresses. It seems like the hardware is counting the block length starting from index 0 of the buffer, even though the first byte was actually placed at the index matching the misalignment, and even though masking is performed correctly. This means that for instance, if misalignment is 6 and the length of 8, the following happens: * First, 8 bytes are fetched from the PI bus and put at index 6..13 in the internal buffer. * Then, RDRAM writes are performed but the hardware believes the block ends at index 8, so only bytes 6..8 are written back to RDRAM. * Symmetrically, if the buffer is full (128 bytes), the last 6 bytes will not be transferred because of the same bug (even if those bytes were fetched by the PI bus). So there will be a "hole" of 6 bytes in the RDRAM output buffer. For instance, if misalignment is 6 and the length is 1024, and the RDRAM page end is far away, the following happens on the first block: * Block size is computed as 122 bytes. * 122 bytes are fetched from the PI bus, and put at index 6..127 in the internal buffer. * RDRAM writes are performed but the hardware believes that the block ends at index 121, so only bytes 6..121 are written back to RDRAM. * Notice that, this notwithstanding, RDRAM address is correctly rounded up to 8 byte at the end of the block (see below), so the second block will behave correctly. There will be a hole in RDRAM as bytes 122.127 in the first block are never written back to RDRAM, so the content of RDRAM for those bytes is not affected by DMA. * RDRAM address register is always rounded up to the next 8 byte alignment at the end of the first block. In most normal cases, the logic above already ensures that the address ends up being aligned at the end of the block, but the rounding up happens even in cases like short transfers that ends with the first block at ends at an arbitrary byte. #### Followup transfers After a DMA transfer is finished, it is possible to trigger a "followup transfer", that is a transfer that sequentially continues the previous one, by simply writing a new length to the PI\_WR\_LEN register. In this case, the current values of PI\_DRAM\_ADDR and PI\_CART\_ADDR are used at the beginning of the transfers. Those values will match the last addresses as updated by the first transfer. The above section describes in details how PI reads and RDRAM writes are done, and registers are updated, so they also implicitly describe how a followup transfer behaves in various edge cases (short transfers, misaligned transfers, etc.) #### PI\_WR\_LEN readbacks after a transfer Reading back PI\_WR\_LEN after a transfer is done, appears to always be fixed at 0x7F. The only exception that has been noticed is when the transfer was smaller than 8 bytes: in that case, the value is 0x7F minus the initial RDRAM misalignment. For instance, if the RDRAM misalignment was 4, the value found in the register at the end of the transfer will be 0x7B. #### DMA data dumps To further investigate and understand how PI DMA is performed, the repo [n64\_pi\_dma\_test](https://github.com/rasky/n64_pi_dma_test) can be used. The repo contains data dumps acquires on real hardware of DMA transfers with all possible misalignments (0, 2, 4, 6), all lengths from 1 to 384 bytes, and all distances from RDRAM page end from 0 to 128 bytes. It also contains timing information on all those transfers. The repo can be used as a testsuite for emulators, but also to further investigate other side cases. Retrieved from "[https://n64brew.dev/wiki/Parallel\_Interface?oldid=5761](https://n64brew.dev/wiki/Parallel_Interface?oldid=5761) " --- # File:VR4300-Users-Manual.pdf - N64brew Wiki [](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf#) File:VR4300-Users-Manual.pdf ============================ * [File](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf#file) * [File history](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf#filehistory) * [File usage](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf#filelinks) [![](https://n64brew.dev/1.45/resources/assets/file-type-icons/fileicon-pdf.png)](https://static.wikitide.net/n64wiki/5/55/VR4300-Users-Manual.pdf) [VR4300-Users-Manual.pdf](https://static.wikitide.net/n64wiki/5/55/VR4300-Users-Manual.pdf "VR4300-Users-Manual.pdf") (file size: 2.1 MB, MIME type: application/pdf) Summary ------- The User's Manual (datasheet) for the VR4300, VR4305, and VR4310, 64-bit microprocessors developed by NEC. Primarily used to understand the VR4300 utilized in the Nintendo 64 console. File history ------------ Click on a date/time to view the file as it appeared at that time. | | Date/Time | Dimensions | User | Comment | | --- | --- | --- | --- | --- | | current | [21:16, 12 November 2020](https://static.wikitide.net/n64wiki/5/55/VR4300-Users-Manual.pdf) | (2.1 MB) | [Bigbass](https://n64brew.dev/wiki/User:Bigbass "User:Bigbass")
([talk](https://n64brew.dev/wiki/User_talk:Bigbass?action=edit&redlink=1 "User talk:Bigbass (page does not exist)")
\| [contribs](https://n64brew.dev/wiki/Special:Contributions/Bigbass "Special:Contributions/Bigbass")
) | The User's Manual (datasheet) for the VR4300, VR4305, and VR4310, 64-bit microprocessors developed by NEC. Primarily used to understand the VR4300 utilized in the Nintendo 64 console. | You cannot overwrite this file. File usage ---------- There are no pages that use this file. Retrieved from "[https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf?oldid=1594](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf?oldid=1594) " --- # File:Cncrntug.pdf - N64brew Wiki [](https://n64brew.dev/wiki/File:Cncrntug.pdf#) File:Cncrntug.pdf ================= * [File](https://n64brew.dev/wiki/File:Cncrntug.pdf#file) * [File history](https://n64brew.dev/wiki/File:Cncrntug.pdf#filehistory) * [File usage](https://n64brew.dev/wiki/File:Cncrntug.pdf#filelinks) [![](https://n64brew.dev/1.45/resources/assets/file-type-icons/fileicon-pdf.png)](https://static.wikitide.net/n64wiki/a/ae/Cncrntug.pdf) [Cncrntug.pdf](https://static.wikitide.net/n64wiki/a/ae/Cncrntug.pdf "Cncrntug.pdf") (file size: 1.48 MB, MIME type: application/pdf) Summary ------- RDRAM - Concurrent RDRAM® User Guide File history ------------ Click on a date/time to view the file as it appeared at that time. | | Date/Time | Dimensions | User | Comment | | --- | --- | --- | --- | --- | | current | [00:27, 15 June 2021](https://static.wikitide.net/n64wiki/a/ae/Cncrntug.pdf) | (1.48 MB) | [Mazamars312](https://n64brew.dev/wiki/User:Mazamars312?action=edit&redlink=1 "User:Mazamars312 (page does not exist)")
([talk](https://n64brew.dev/wiki/User_talk:Mazamars312?action=edit&redlink=1 "User talk:Mazamars312 (page does not exist)")
\| [contribs](https://n64brew.dev/wiki/Special:Contributions/Mazamars312 "Special:Contributions/Mazamars312")
) | RDRAM - Concurrent RDRAM® User Guide | You cannot overwrite this file. File usage ---------- There are no pages that use this file. Retrieved from "[https://n64brew.dev/wiki/File:Cncrntug.pdf?oldid=3967](https://n64brew.dev/wiki/File:Cncrntug.pdf?oldid=3967) " --- # Flash - N64brew Wiki [](https://n64brew.dev/wiki/Flash#) Flash ===== Flash memory provides games with 1 Mebibit (= 128 kibibyte) of non volatile memory to keep game progress saved after console shutdown. This is the biggest capacity offered among save types. Contrary to SRAM this kind of memory doesn't require an external battery to keep data saved. Similar to SRAM, it is accessed using the PI bus at address 0x0800\_0000 (eg. with Domain 2 timings). **Warning: The information contained in this page is still WIP and may not completely reflect actual hardware behavior, especially when "corner case" behavior differs between the various chips.** **Trivia:** the Ultra SDK offered a way to access multiple Flash chips and mapped them at address 0x0800\_0000 | (chip\_num << 17). But no known game used more than 1 Flash chip. **Note:** The flash chip is a custom design (manufactured by Macronix and Matsushita), but it seems at least similar in part to the MX29L1611 model which has a public [datasheet](https://pdf1.alldatasheet.com/datasheet-pdf/download/74490/MCNIX/MX29L1611.html) . Contents -------- * [1 TLDR for programmer](https://n64brew.dev/wiki/Flash#TLDR_for_programmer) * [2 Chip layout: Sectors, Pages, Byte & Words](https://n64brew.dev/wiki/Flash#Chip_layout:_Sectors,_Pages,_Byte_&_Words) * [3 Command Internal Register (CIR)](https://n64brew.dev/wiki/Flash#Command_Internal_Register_(CIR)) * [4 Silicon ID Mode](https://n64brew.dev/wiki/Flash#Silicon_ID_Mode) * [5 Status Mode](https://n64brew.dev/wiki/Flash#Status_Mode) * [5.1 Clear status](https://n64brew.dev/wiki/Flash#Clear_status) * [6 Chip Erase Setup](https://n64brew.dev/wiki/Flash#Chip_Erase_Setup) * [7 Sector Erase Setup](https://n64brew.dev/wiki/Flash#Sector_Erase_Setup) * [8 Erase](https://n64brew.dev/wiki/Flash#Erase) * [9 Load Byte Page Mode](https://n64brew.dev/wiki/Flash#Load_Byte_Page_Mode) * [10 Page Program](https://n64brew.dev/wiki/Flash#Page_Program) * [11 Read Array Mode](https://n64brew.dev/wiki/Flash#Read_Array_Mode) * [12 PI Configuration](https://n64brew.dev/wiki/Flash#PI_Configuration) ### TLDR for programmer * Flash chip PI base address is 0x08000000. It exposes a 32bit Command Internal Register (CIR) at offset 0x10000, that is at PI address 0x08010000. This CIR is write-only and determine in what mode the flash chip is and what commands to execute. * Depending on the mode it's in, it will expose different content at its base address. In ReadArray mode it will expose the data array (eg. 1Mib), in Status mode it will expose its 8bit status register, in SiliconID mode it will expose its 64bit SiliconID register, in LoadBytePage mode it will expose a 128 byte internal page buffer. * On poweron flash chip is in ReadArray mode. So reading its content can be done without touching the CIR and no write operation can corrupt its content (except maybe if you trigger a program page command). But otherwise, if you want to read flash data array content, you first have to write the ReadArray command (0xF0000000) to CIR (eg. at PI address 0x08010000). * There were different models of flash memory which have different behaviors regarding read operations. "Older" models are "word-indexed" and therefore interpret addresses as 16bit-word addresses instead of 8bit-byte addresses like "byte-indexed" model would. For "word-indexed" chip the offset should be divided by 2 to effectively read the expected data: to read at byte offset 16 you should use an offset of 16/2=8 eg. PI address 0x08000008 instead of 0x08000010. "byte-indexed" models don't need such "divide by 2" adjustment. To know if your model is word-indexed or byte-indexed you have to know which flash model you interact with (use SiliconID for that). * On top of that, for DMA transfers, there is a "256x128 byte" boundary that cannot be crossed by a single DMA: eg. to read the whole flash content you have to split into at least 4 DMA because DMA can't cross offsets 0x8000, 0x10000 and 0x18000 for "byte-indexed" chips (resp. 0x4000, 0x8000 and 0xC000 for "word-indexed" chips). Note that flash is not unique with this DMA crossing restriction, other PI devices have similar restrictions and that's why the PI controller can split automatically DMA using the PGS setting: ROM have a "512 byte" boundary (PGS=7 : 2^(2+7)=512), and SRAM have a "32768 byte" boundary (PGS=0xD 2^(2+13)=32768). Unfortunately for flash, due to the existence of "word-indexed" flash we can't just set PGS to 0xD because PI don't know about the "divide by 2" adjustment needed for them. Instead we set PGS to a big value (0xF) and manually split (and divide by 2 if required by the model of flash) all DMAs. * Regarding write operations, flash works with erase & program operations on a sector and page layout. This means that you first need to erase a full sector (or the whole chip if it makes more sense for your use case) and then program each page individually. If you need to "only" change some page and not the whole sector you have to manually read them first and program them back after the erase sector operation. * Flash chip has 8 sectors, each consisting of 128 pages, a page being 128 byte (or 64 word for word-indexed flash). * Erasing is done in 2 steps: 1. Erase Setup: write either the Sector Erase Setup (0x4B000XXX, with XXX being an index of a page in the sector you want to erase) or the Chip Erase Setup (0x3C000000) command to CIR 2. Erase: write Erase (0x78000000) command to CIR. This will start the erase operation and automatically switch the flash into status mode (eg. reading at base address will return the WSM register content) so that you can poll for ERASE\_BUSY bit to know when this operation is done. When done, you should also check ERASE\_OK bit to know if erasure was successful. The erase operation can take between 85ms and 300ms depending on model and if it's the whole chip or just a single sector. * Programming is done in 3 steps: 1. Load Byte Page: write LoadBytePage (0xB4000000) command into CIR so that the Internal Page buffer can be accessed at flash base address. 2. Write the content you want in that buffer. Usually this is done using a single 128-byte DMA at flash base address (0x08000000). If you write less than 128byte, default content will be 0xff. 3. Program Page: write Program (0xA5000XXX, with XXX being the index of the page you want to program). This will start the page programming operation and automatically switch the flash into status mode so you can poll for PROGRAM\_BUSY bit to know when this operation is done. When done, you should also check PROGRAM\_OK bit to know if programming was successful. The program operation can take between 300µs and 3.5ms depending on chip model. * To identify the flash model programmatically, first switch to SiliconID mode (eg. write SiliconID command (0xE1000000) to CIR \[some models may need multiple consecutive writes to properly switch to SiliconID mode\]) and then issue an 8 byte DMA at PI address 0x08000000 to read SiliconID register. It is important to use DMA here, and not IO otherwise you'll get only the first 32bit repeated twice. ### Chip layout: Sectors, Pages, Byte & Words The 1Mebibit Flash memory is organized as 8 sectors, each composed of 128 pages, with each pages being 128 x 8-bit bytes (for byte addressed chips) or 64 x 16-bit words (for word addressed chips). Erase operations (eg. resetting bits to '1') can only operate either on the full chip or on a full sector. Programming operations (eg. clearing bits to '0') can only operate on a full page. When reading chip array, the distinction between word addressed chip and byte addressed chip matters. For instance, reading at address "4" means for byte addressed chip reading starting at the 4th byte, while for a word addressed chip it means reading starting at the 4th 16bit word which would correspond to the 8th byte : converting from a byte address to a word address require dividing by 2 the byte address. The table below summarize the chip layout: | | | | | | --- | --- | --- | --- |Chip layout | Sector | Pages | 8-bit byte Addresses | 16-bit word Addresses | | 0 | 0x000 - 0x07F | 0x0000\_0000 - 0x0000\_3FFF | 0x0000\_0000 - 0x0000\_1FFF | | 1 | 0x080 - 0x0FF | 0x0000\_4000 - 0x0000\_7FFF | 0x0000\_2000 - 0x0000\_3FFF | | 2 | 0x100 - 0x17F | 0x0000\_8000 - 0x0000\_BFFF | 0x0000\_4000 - 0x0000\_5FFF | | 3 | 0x180 - 0x1FF | 0x0000\_C000 - 0x0000\_FFFF | 0x0000\_6000 - 0x0000\_7FFF | | 4 | 0x200 - 0x27F | 0x0001\_0000 - 0x0001\_3FFF | 0x0000\_8000 - 0x0000\_9FFF | | 5 | 0x280 - 0x2FF | 0x0001\_4000 - 0x0001\_7FFF | 0x0000\_A000 - 0x0000\_BFFF | | 6 | 0x300 - 0x37F | 0x0001\_8000 - 0x0001\_BFFF | 0x0000\_C000 - 0x0000\_DFFF | | 7 | 0x380 - 0x3FF | 0x0001\_C000 - 0x0001\_FFFF | 0x0000\_E000 - 0x0000\_FFFF | ### Command Internal Register (CIR) Flash chip exposes a Command Internal Register (32bits, write only), mapped at flash address 0x0001\_0000 to serve as an interface between the CPU and internal Flash operations: to execute an operation or transition to a given mode, the corresponding command should be written to CIR. **Quirks:** Some flash chips may need multiple writes to CIR to fully transition to the new mode. For instance, flash chip MX29L1100 (and possibly other Macronix variants) may need 2 writes to CIR to effectively switch to Status or SiliconID mode. Other modes / commands don't seem to need multiple write to CIR under normal conditions. Flash chip MN63F8MPN (from Matsushita) is not affected by such quirks. Note that, CIR is write only and cannot be read. Otherwise its address would conflict with "regular" data addresses when reading. Commands recognized by CIR are given below. Further research is needed to determine if other command exists (like suspend, protect, unprotect, ...). **Trivia**: For all known commands bits 31-28 are the inverse of bits 27-24. | | | | | --- | --- | --- | | Command | Value | Comment | | Chip Erase Setup | 0x3C00\_0000 | | | Sector Erase Setup | 0x4B00\_0XXX | XXX denotes a page number belonging to the sector to erase.

example: XXX = 123, will erase sector 2 (eg. pages \[0x100 - 0x17F\]) Note: bits \[23:10\] of command are ignored (and maybe also bits \[6:0\] as they don't contribute to sector determination). | | Erase | 0x7800\_0000 | | | Program Page | 0xA500\_0XXX | XXX denotes the page to program

Note : bits \[23:10\] of command are ignored. | | Load Byte Page | 0xB400\_0000 | | | Status Mode | 0xD200\_0000 | | | Silicon ID Mode | 0xE100\_0000 | | | Read Array Mode | 0xF000\_0000 | | ### Silicon ID Mode When in Silicon ID Mode, the read-only SILICON\_ID register is mapped into flash address space. In this mode, the lower 16 bits of addresses are ignored and only the size of the read burst matters : for read burst of length N <= 8, only the first N bytes of SILICON\_ID are read (eg. bits 63 to 64-8\*N). For burst length of N > 8, the behavior is chip dependent. For the MN63F8MPN, the last 16-bit word of SILICON\_ID (eg. DEVICE\_ID) is repeated after the first 8 byte of SILICON\_ID. For the MX29L1100, the 8 byte of SILICON\_ID is repeated. The behavior is chip dependent when bit 17 of address is set (eg. flash address range \[0x10000 - 0x1FFFF\]). For the MN63F8MPN, an "open bus" value is returned (eg. lowest 16 bits of address is repeated for the whole read burst). After such an open bus read all subsequent reads will also return an "open bus" value until a new command is written to CIR. For the MX29L1100, the SILICON\_ID is repeated (same behavior as when bit 17 is not set). | | | | --- | --- | | | SILICON\_ID | | 63:32 | FLASH\_TYPE\_ID (0x1111\_8001) | | 31:16 | MANUFACTURER\_ID | | 15:0 | DEVICE\_ID | **Extra Details:** FLASH\_TYPE\_ID is expected to be equal to 0x1111\_8001, but further research is needed to determine its proper purpose. Known combination of Manufacturer / Device ID is given in the table below. Given that current understanding of byte / word addressing indicates that it depends on chip model we also summarize in this table if the given chip operates in byte mode or word mode. Further research is needed to verify this claim. | | | | | | --- | --- | --- | --- | | Name | Manufacturer ID | Device ID | Byte / Word addressing | | MX29L0000 | 0x00C2 (Macronix) | 0x0000 | word | | MX29L0001 | 0x00C2 | 0x0001 | word | | MX29L1100 | 0x00C2 | 0x001E | word | | MX29L1101\_A | 0x00C2 | 0x001D | byte | | MX29L1101\_B | 0x00C2 | 0x0084 | byte | | MX29L1101\_C | 0x00C2 | 0x008E | byte | | MN63F8MPN | 0x0032 (Matsushita) | 0x00F1 | byte | ### Status Mode When in Status Mode, the STATUS register is mapped into flash address space. In this mode, the lower 16 bits of addresses are ignored and only the size of the read burst matters : the pattern 00 (with being the STATUS register bits) is repeated. **Quirk:** On MX29L1100 the upper 16bits of the first read come from the data of the previous mode. If bit 17 of address is set (eg. flash address range \[0x10000 - 0x1FFFF\]) an "open bus" value is returned (eg. lowest 16 bits of address is repeated for the whole read burst). After such an open bus read all subsequent reads will also return an "open bus" value until a new command is written to CIR. #### Clear status After an Erase or Program operation is finished, the software is responsible for clearing the Status by writing 0 at flash address 0 while in Status Mode. MN63F8MPN only: Writing any value anywhere while is Status Mode will clear the ERASE\_OK or PROGRAM\_OK bits if they were set. MX29L1100: ERASE\_OK and PROGRAM\_OK are by default set, and will be unset after corresponding operation in case of failure. To reset them to their default state after a failure, one should write 0 at flash address 0 while in Status mode. | | | | --- | --- | | | STATUS | | 7 | WSM\_READY | | 6 | unknown (maybe some kind of suspend) | | 5-4 | unknown (always 0) | | 3 | ERASE\_OK | | 2 | PROGRAM\_OK | | 1 | ERASE\_BUSY | | 0 | PROGRAM\_BUSY | **Extra Details:** WSM\_READY : When set, Write State Machine is ready to accept Erase or Program commands. ERASE\_OK : After an erase operation, it will reflect the success of the operation 1: OK, 0: NOK. PROGRAM\_OK : After a program operation, it will reflect the success of the operation 1: OK, 0:NOK. ERASE\_BUSY: When set, an Erase operation is currently being executed. PROGRAM\_BUSY : When set, a Program operation is currently being executed. Further research is needed to determine purpose of bits 4-6. They may be related to Suspend operation / Sector Protection. ### Chip Erase Setup First phase of erase operation. Should be followed by Erase command to perform full chip erasure. ### Sector Erase Setup First phase of sector erase operation. Should be followed by Erase command to perform sector erasure. ### Erase Second phase of sector/chip erase operation. This will start the erase operation (eg. reset bits to "1"). During operation, WSM\_READY bit is cleared, ERASE\_BUSY is set At the end of operation, WSM\_READY is set, ERASE\_BUSY is cleared and ERASE\_OK reflects success of operation. Also, to ease status polling by application, the chip automatically transition to Status Mode at the beginning of operation and will stay in this mode until a new valid command is written to CIR. Sector and Chip Erase operation takes around <280ms and <300ms respectively for MN63F8MPN, and ~85ms for both sector and chip erase for MX29L1100. Writing an Erase command without first writing a Chip or Sector Erase Setup command will not work and do nothing : Erase command will be ignored. ### Load Byte Page Mode First phase of page programming operation. When in Load Byte Page mode, the Internal Page Buffer is mapped into flash address space at flash addresses 0x00 - 0x7F. Initial content of Internal Page is all bits set (eg. 0xFF x 128). Writing to Internal Page result in a chip dependent behavior: * MN63F8MPN can only clear bits : eg. assuming an initial value of 0xFF at a given location, writing 0xA5 and then 0x5A there will result in value (0xFF & 0xA5 & 0x5A) = 0x00 being programmed into Internal Page. * MX29L1100 writes behaves normally (they can set and clear bits). Internal Page can be read. After a Program operation, the content on Internal Page is reset to all bits set. The content of Internal Page is persisted until either a Page is programmed, or chip power off. (Tested only on MX9L1100) ### Page Program Second phase of page programming operation. This will start the programming operation (eg. clearing bits to "0") of specified page, using content of previously loaded bytes into Internal Page Buffer. During operation, WSM\_READY bit is cleared, PROGRAM\_BUSY is set At the end of operation, WSM\_READY is set, PROGRAM\_BUSY is cleared and PROGRAM\_OK reflects success of operation. Also, to ease status polling by application, the chip automatically transition to Status Mode at the beginning of operation and will stay in this mode until a new valid command is written to CIR. Program Page operation takes around <300µs (MN63F8MPN) / ~3.5ms (MX29L1100) It is recommended to only program pages that have all bits set to "1" (eg. the page should be erased before being programmed). But further research is required to document behavior in such a case. ### Read Array Mode When in Read Array mode, the Array Data is mapped for reading into flash address space at flash addresses 0x00000 - 0x1FFFF (for byte addressed chip) and 0x00000 - 0x0FFFF (for word addressed chip). Array Data can be read using IO or DMA. Note however that for DMA, any read burst crossing a 256-page boundary (eg. end of an odd sector) will "wrap around" and return value from the beginning of the previous even sector. ### PI Configuration **Note:** In this section when we talk about "Page Size" we refer to the _PI_ Page Size setting (eg. when to split DMA bursts and emit an address on the PI bus), not the _Flash_ Page Size (eg. the minimum size of "programmable" data). PI BSD Dom2 should be configured with the following values to access Flash memory : | | | | | --- | --- | --- |PI BSD Configuration | | Value | Meaning | | Latency | 0x5 | (5+1)\*16 = 96ns | | Pulse | 0xC | (12+1)\*16 = 208ns | | Page Size | 0xF | 2^(15+2) = 128KiB | | Release | 0x2 | (2+1)\*16 = 48ns | Regarding the choice of Page Size, we recommend using the maximum value so that all meaningful DMA will be done in a single burst (eg. send address only once at beginning of DMA) and manually split DMA. This is to accommodate the 3 main constrains : * to read all bits of SiliconID a burst must be at least 8 byte (eg. PGS >= 1) * a single burst can't cross a 256-page boundary without exhibiting a wrap around * word addressed chips can't directly use regular byte addresses If there is no intent of supporting word addressed chip, maybe using PGS = 5 (128B) or PGS = 0xD (32KiB) could work as they would properly split DMA at the page (resp. 256-page) boundary. Retrieved from "[https://n64brew.dev/wiki/Flash?oldid=5773](https://n64brew.dev/wiki/Flash?oldid=5773) " --- # Sharp SM5 Microcontroller - N64brew Wiki [](https://n64brew.dev/wiki/Sharp_SM5_Microcontroller#) Sharp SM5 Microcontroller ========================= The [PIF](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") and CIC (inside the cartridge) are both custom versions of Sharp SM5 4-bit microcontrollers. These microcontrollers were also used in the Game & Watch handheld games, so Nintendo already had developers that were familiar with them. While the core functionality of the PIF and CIC are generally understood, the microcontroller model is custom and therefore not well known. There has been some effort to reverse engineer the PIF and CIC communication to ease the process for creating compatible flash carts. At least 2 projects went through the time effort and money to decap the chips and view the internals of to better understand what they are doing. Retrieved from "[https://n64brew.dev/wiki/Sharp\_SM5\_Microcontroller?oldid=4454](https://n64brew.dev/wiki/Sharp_SM5_Microcontroller?oldid=4454) " --- # CIC-NUS - N64brew Wiki [](https://n64brew.dev/wiki/CIC-NUS#) CIC-NUS ======= The CIC-NUS (usually called "CIC") is a protection chip that is present on all N64 cartridges and implements the required security measures to allow the game to boot on an unmodified console. Once the cartridge is inserted into the slot, the CIC is electrically connected to the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") , the peripheral and protection chip within the N64 itself, via two lines (roughly, clock and data), and communicates with it. The PIF is in charge of securing the boot sequence and is able to halt the CPU if the protection fails, preventing the game from booting. Since the PIF can be regarded as the "master" of the communication between the CIC and itself, the whole boot sequence is documented in the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") page in the wiki. Please refer to it for more details about how the secure boot works. The 64DD (which plugs to the bottom of the console) comes with its own CIC. The N64DD games on the magnetic disk support do not have a CIC themselves, so the secure boot is completed by the 64DD firmware, which then loads the games from the disk and boot it. Contents -------- * [1 Variants](https://n64brew.dev/wiki/CIC-NUS#Variants) * [1.1 UltraCIC](https://n64brew.dev/wiki/CIC-NUS#UltraCIC) * [2 Pinout](https://n64brew.dev/wiki/CIC-NUS#Pinout) * [3 Description of operation](https://n64brew.dev/wiki/CIC-NUS#Description_of_operation) * [3.1 **Physical connection with PIF**](https://n64brew.dev/wiki/CIC-NUS#Physical_connection_with_PIF) * [3.2 Boot sequence](https://n64brew.dev/wiki/CIC-NUS#Boot_sequence) * [3.2.1 1\. ID](https://n64brew.dev/wiki/CIC-NUS#1._ID) * [3.2.2 2\. Seeds](https://n64brew.dev/wiki/CIC-NUS#2._Seeds) * [3.2.3 3\. Random entropy](https://n64brew.dev/wiki/CIC-NUS#3._Random_entropy) * [3.2.4 4\. Checksum](https://n64brew.dev/wiki/CIC-NUS#4._Checksum) * [3.3 Main loop](https://n64brew.dev/wiki/CIC-NUS#Main_loop) * [3.3.1 1\. Command "Compare" (bits: 00)](https://n64brew.dev/wiki/CIC-NUS#1._Command_%22Compare%22_(bits:_00)) * [3.3.2 2\. Command "Die" (bits: 01)](https://n64brew.dev/wiki/CIC-NUS#2._Command_%22Die%22_(bits:_01)) * [3.3.3 3\. Command "Challenge" (bits: 10)](https://n64brew.dev/wiki/CIC-NUS#3._Command_%22Challenge%22_(bits:_10)) * [3.3.4 4\. Command "Reset" (bits: 10)](https://n64brew.dev/wiki/CIC-NUS#4._Command_%22Reset%22_(bits:_10)) * [4 Hacking the CIC](https://n64brew.dev/wiki/CIC-NUS#Hacking_the_CIC) * [4.1 To Enable Test Mode(s)](https://n64brew.dev/wiki/CIC-NUS#To_Enable_Test_Mode(s)) * [4.2 In Test Mode](https://n64brew.dev/wiki/CIC-NUS#In_Test_Mode) * [4.2.1 Arbitrary Code Execution](https://n64brew.dev/wiki/CIC-NUS#Arbitrary_Code_Execution) * [4.2.2 Halt Instruction](https://n64brew.dev/wiki/CIC-NUS#Halt_Instruction) * [4.2.3 Stop Instruction](https://n64brew.dev/wiki/CIC-NUS#Stop_Instruction) * [4.2.4 Output Data](https://n64brew.dev/wiki/CIC-NUS#Output_Data) * [4.2.5 Load Constant into Accumulator LDX](https://n64brew.dev/wiki/CIC-NUS#Load_Constant_into_Accumulator_LDX) * [4.2.6 Dumping the CIC code](https://n64brew.dev/wiki/CIC-NUS#Dumping_the_CIC_code) * [4.3 References](https://n64brew.dev/wiki/CIC-NUS#References) Variants -------- There are different models of CIC, which normally differentiates themselves for small details in the firmware and different "secret keys" used to secure the boot. As explained in the PIF-NUS page, each variant of the CIC comes in pair with a different boot software (called IPL3), which is part of the secure boot, and is embedded in the cartridge itself (in a special area of the ROM: offset 0x40 - 0x1000). A mismatch of IPL3 with CIC would be detected by the previous secure boot stage (IPL2) which is hardwired in the console itself because of a failed checksum. The following table lists all known CIC variants, with some overview of the main differences between them. Notice that the security keys are instead listed in the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") page, in the section related to the secure boot. | | | | | | --- | --- | --- | --- |CIC/IPL3 variants | Variant | Used in | Game Entrypoint\[1\] | Comment | | 6102 / 7101 | Most titles (~ 88% of commercial games) | u32@0x08 | * Identical IPL between NTSC (6102) and PAL (7101) | | 5101 | Aleck 64 titles | u32@0x08 - 0x100000 | | | 6101 | Starfox 64 | u32@0x08 | * Similar to 6102 with minor code differences. | | 7102 | Lylat wars | 0x80000480 | * Entrypoint hardcoded to 0x80000480; otherwise identical to 6102. | | 6103 / 7103 | Banjo-Kazooie, Diddy Kong Racing, ... | u32@0x08 - 0x100000 | * Identical IPL between NTSC (6103) and PAL (7103) | | 6105 / 7105 | Banjo-Tooie, Perfect Dark, Zelda OOT, Zelda MM, ... | u32@0x08 | * Identical IPL between NTSC (6105) and PAL (7105)
* More complex protection scheme which involves the RSP at boot, plus a special challenge/response security protocol that is invoked by the CPU during gameplay. | | 6106 / 7106 | F-ZeroX, Yoshi's Story, ... | u32@0x08 - 0x200000 | * Identical IPL between NTSC (6106) and PAL (7106)
* The second part of IPL3 is ciphered
* No junk byte at the end
* It should have been called 6104, but "4" is the "unlucky" number, so it was renamed to 6106. | | 5167 | 64DD ROM conversion | u32@0x08 if u16@0x16 != 0

u32@0x101c if u16@0x16 == 0 | * This is an "imaginary" CIC that does not exist in physical shape. It was used as part of the home-brew effort to convert 64DD games from the disk format to the cartridge format, to facilitate playing pirated versions on emulators and flashcarts. | | 8303 | 64DD IPL Retail (J) | u32@0x08 | * Longer game checksum (6xu32) | | 8401 | 64DD IPL Dev (J) | | | | 8501 ? | 64DD IPL Retail (U) | | | \[1\] IPL3 is in charge for loading the game into RDRAM and jumping to its entrypoint. Normally, for instance in the case of the IPL3 code for the vastly popular CIC 6102, the entrypoint is stored in the ROM header at a fixed offset and is thus readily available. Some IPL3s somehow "mangle" the entrypoint, possibly in an effort to obfuscate it. This table reports where the entrypoint is. ### UltraCIC The name "UltraCIC" normally refers to a physical chip that can acts as a CIC clone. There have been a few projects sharing this name, based on several different MCUs. Normally, the project can be found on GitHub as open source, complete with the full source code. These clones are normally found in "reproduction cartridges" (aka physical cartridges that can be bough and programmed to eg. distribute physical copies of a homebrew game) to allow the cartridge to boot correctly on a real N64. UltraCICs are normally "universal", that is, they can act as any CIC variant. To switch variant, it is necessary to either flash a modified firmware, or send a custom command typically through a different bus available to the MCU (eg: SIPO). Programmable flashcarts such as Everdrive 64 or 64drive also features some sort of "UltraCIC" to allow games to boot. Sometimes the CIC emulation functionality is provided by the main FPGA, while in other cases it is a real separate chip. These are a few links to explore: * [https://github.com/jago85/UltraCIC\_C](https://github.com/jago85/UltraCIC_C) * [https://github.com/perkinsb1024/UltraCIC-II](https://github.com/perkinsb1024/UltraCIC-II) * [https://github.com/ManCloud/UltraCIC-III](https://github.com/ManCloud/UltraCIC-III) Pinout ------  [](https://n64brew.dev/wiki/File:CIC_decap_pinslabeled.png) **CIC decap pins labeled** | | | | | | | | | --- | --- | --- | --- | --- | --- | --- |CIC Pinout (16 Pin DIP Package) | N64 Function | SM5 Function | Number | | Number | SM5 Function | N64 Function | | VDD | VDD | Pin 1 | | Pin 16 | VDD | VDD | | | P5:0 | Pin 2 | | Pin 15 | P2:0 | DIO (to PIF) | | | P5:1 | Pin 3 | | Pin 14 | P2:1 | DCLK (to PIF) | | | P5:2 | Pin 4 | | Pin 13 | P2:2 | GND | | | P5:3 | Pin 5 | | Pin 12 | P2:3 | | | GND | TS:0 | Pin 6 | | Pin 11 | CLK | CLK | | GND | TS:1 | Pin 7 | | Pin 10 | TIO | | | GND | GND | Pin 8 | | Pin 9 | Reset | !RESET | Description of operation ------------------------ This section tries to detail how a CIC works. The differences between CICs themselves are actually minimal and mostly related to data (region, security keys) rather than code (functionality). The CIC is powered by a Sharp SM5 core, running a custom firmware that is burnt within the chip and cannot be modified. Most of the CIC firmwares have been dumped via a trick explained below in the section "Hacking the CIC", and the firmwares have been analyzed. This link [https://github.com/jago85/UltraCIC\_C/blob/master/cic\_c.c](https://github.com/jago85/UltraCIC_C/blob/master/cic_c.c) contains a C implementation very faithful to the original firmware that can be regarded as a pseudo-code reference of the workings of CIC. Everything described in this section can be cross-referenced to that C source code. ### **Physical connection with PIF** Each CIC is connected to PIF via two lines: Pin 14 (aka DCLK) and pin 15 (aka DIO). In this simple serial protocol, the PIF acts as the master, and the CIC as the slave. Whenever the PIF firmware is ready to send or receive data, it will pulse the DCLK line and then either move the DIO line (in case PIF is writing), or read the line status as moved by CIC (in case of reading). The protocol makes it clear beforehand whether a write or a read should happen. Any time the CIC needs to either read a data from the PIF or write a data for it, it spins waiting to see the front on the DCLK line. ### Boot sequence #### 1\. ID  [](https://n64brew.dev/wiki/File:ID_nibble.png) ID nibble with value 0101 (yellow = DCLK, cyan = DIO) The CIC writes 1 nibble (4 bits) to the PIF called "ID". The bits are as follow (in order of transmission, so what we call here "bit 0" would actually be "bit 3" in a reconstructed 4 bit register). * Bit 0: "type". This is 0 for normal CICs, and 1 if the CIC is part of a 64DD unit. * Bit 1: "region". This is 0 for NTSC CICs, and 1 for PAL CICs. This value is hardcoded in the firmware, which makes the CIC region-locked. * Bit 2: always 0 * Bit 3: always 1 #### 2\. Seeds The CIC writes 6 nibbles which contains one of the boot secrets: the checksum seeds. These are 2 seeds of 1 byte each one, called IPL2 seed and IPL3 seed (please refer to the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") page for a description of how these seeds are then used by PIF). In all known PIF variants, the two seeds happen to be exactly the same byte, even though the protocol would allow for them to differ. A table in the PIF-NUS page lists the seeds for all known CIC variants. In addition these 2 bytes, a third byte is transmitted first, which is a fixed value (0xB5).  [](https://n64brew.dev/wiki/File:Seed.png) Scrambled seed (yellow = DCLK, cyan = DIO) For instance, the transmitted sequence for a CIC 6102 which uses 0x3F as seed is `B5 3F 3F`. Before putting the data on the wire, the 3 bytes are scrambled via a very light obfuscation algorithm to avoid to directly leak the secrets on the wire. The algorithm is very simple: starting from the second byte, each byte is added to the previous one, and the result is further incremented by 1; the first byte is not changed. For instance, the sequence `B5 3F 3F`is scrambled as follows: * The second byte `3F` is added to the first byte `B5`, producing `F4`, which is further incremented becoming `F5`. * The third byte `3F` is added to the scrambled second byte `F5`, producing `34`, which is further incremented before `35`. The resulting scrambled sequence is then `B5 F5 35`, which is transmitted as 6 nibbles on the wire. #### 3\. Random entropy The CIC now waits for the Data CLK line to go low. While the pin stays high, the CIC keeps incrementing an internal memory location. On the other side of the line, the PIF is now executing a part of the boot process in sync with the CPU, and at some point it is also using a hardware time-based random number generator (it's waiting for a capacitor to charge in a RC connected to one of its pin). Once this is done, the PIF puts the DCLK line low. The amount of time it takes to get this is subject to some entropy (mostly because of the time-based RNG), and thus the CIC will increment the memory location a number of times that changes across boots, which in turns produces something akin to a random number. #### 4\. Checksum  [](https://n64brew.dev/wiki/File:Checksum.png) A checksum transmitted to PIF (yellow: DCLK, cyan: DIO) The CIC writes 16 nibbles which contain the other boot secret: the IPL2 checksum. This is a 6 byte checksum that is used by the PIF to verify that the IPL3 found in the cartridge is the correct one for this CIC. The exact process (that also involves the CPU at its IPL2 stage) is detailed in the [PIF-NUS page](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") . As a prefix to the 6 byte (12 nibble) checksum, 2 bytes (4 nibbles) are prefixed. These bytes come from the random number generated in the previous step and are thus different at each boot. The resulting 16 nibbles (4: random prefix + 12: checksum) are then scrambled using the same algorithm detailed in the section above related to seeds. As an example, if the random prefix is `3F DF` and the checksum is `A5 36 C0 F1 D8 59` (which is the actual CIC 6102 checksum secret), the final sequence transmitted is `3F DF A5 36 C0 F1 D8 59`which is scrambled and put on the wire as `3F 57 29 3E 54 7C F5 90`. After the checksum has been transmitted, the boot sequence is finished, and the CIC enters its main operation loop. ### Main loop During the main loop, the CIC waits until the PIF transmits a 2-bit command. After a command is received, the CIC executes the command, which might involve receiving additional bits from PIF and/or transmitting bits to it. After this a command is processed, the CIC starts wiring for the next command. The main loop never exits. Notice that the CIC is not affected by the soft reset (presses of the RESET physical button on the console), as those are handled by PIF resetting the CPU, but do not directly affect the CIC; the boot sequence in fact is not repeated. The follow paragraphs detail the 4 different commands that can be sent by PIF. #### 1\. Command "Compare" (bits: 00) #### 2\. Command "Die" (bits: 01) When the CIC receives this command, it enters an infinite loop in which it does nothing, thus stopping any communication with PIF, until powered off. #### 3\. Command "Challenge" (bits: 10) This command is fully implemented only on CIC 6105 / 7105. All other CICs contain a dummy version of this command that does almost nothing (see below). When the CIC receives this command, it begins a challenge / response security protocol: the protocol receives some bytes from the PIF (actually, coming from the CPU via the PIF), runs a "secret" security algorithm that also uses some tables in ROM, and sends back some other bytes. The CPU can check whether the answer is what it was expected and if so, it can confirm that the CIC 6105 is legit. This was based on the fact that counterfeiting the security protocol was deemed to be hard (and in fact, it required some time to be reverse engineered even before the CIC firmwares were dumped). After receiving this command, the CI first writes 2 nibbles with the fixed value `0x0A 0x0A`. These represent a timeout for the PIF (interpreted as `0x0A0A` = 2570 iterations of a wait loop, so it was probably hand-tuned and tested): they given an indication of how much time the PIF will have to wait before being able to read the response. Remember that PIF is the master of the communication, so it has no way to "wait for the CIC to be ready to send data": when the PIF pulses the clock line, it expects the CIC to be ready to immediately send the data. So for this algorithm, the designers decided to let the CIC send the expected duration to PIF, probably to be able to change the algorithm in newer CIC versions without having to change the PIF as well. After sending the timeout, the CIC receives 30 nibbles (15 bytes) which is the challenge string. The challenge string is transformed through a security algorithm that we will not try to cover here as it is pretty convoluted, but can be studied in [one of its C implementations](https://github.com/jago85/UltraCIC_C/blob/3450b4403a1df190b9abb2dbe071ce07a546179b/cic_c.c#L292-L322) . Then, the CIC sends a 0 bit to PIF (used such as a "start" bit), followed by the 30 transformed nibbles. After this, it goes back to the main loop. It is important to notice that most CIC variants (all excluding 6105 and 7105) do not implement the full algorithm. Instead, the challenge string is simply bit-inverted and sent back to PIF. In fact, no known software relies on this dummy challenge algorithm. #### 4\. Command "Reset" (bits: 10) Hacking the CIC --------------- This section describes a way that can be used to hack the CIC, that is take control of it up to the point of dumping its internal ROM. The actions described below have been done successfully as part of a University project. Some of the details are missing whether it was to avoid encouraging piracy, simply not required for the core of the paper or lost in the language translation (authors live in Germany) is unknown. The main entry point is to use the "test mode", a feature of the SM5 core that is available on known CIC variants. ##### To Enable Test Mode(s) Pulling TS:0 and/or TS:1 high before power on will place the SM5 controller in one of 3 test modes. (**Which test modes and which pin states are unknown**) It's also unclear if you can change between test modes while the unit is powered on. The fourth state is standard usage with TS:0 and TS:1 tied to ground. It's unknown how slowly you can clock the CIC. ##### In Test Mode Which mode is still unclear but the following functionality is available. ###### Arbitrary Code Execution Instructions can be set 1 nibble at a time on Port 5 pins, most instructions are 1 byte long so they must be entered 1 nibble at a time then toggle the clock line. ###### Halt Instruction The Halt instruction is encoded as 0x77 which is nice because it doesn't matter which nibble you send first. This instruction also has a nice benefit of causing a clear external change, the TIO line defaults to the Clock signal but after the Halt instruction it stops outputting a clock signal. ###### Stop Instruction The Stop instruction is encoded as 0x76 which will assist in determining if the high or low nibble should be input first. This instruction also stops the TIO clock upon execution, so we have a clear external indication of success. ###### Output Data Port 2 of the CIC can be used to output either the AREG register or the Program Counter (PC), it's unclear at this time if the difference is achieved with different test modes or by modifying internal configuration registers. NOTE: On power up Port 2 is configured for Input, an internal configuration register must be modified to make it output. ###### Load Constant into Accumulator LDX The LDX instuction can be used to populate the AREG with a known value that can be checked on Port 2 ###### Dumping the CIC code This process is very confusing so it may take some experimentation to work out the exact steps and details, the original document tries to explain but it feels like some details are missing. Jump Instructions are 2 bytes, the first nibble is the instruction and the following 12 bits are the destination address. Being in test mode seems to have a side effect on this instruction, inputting only the Jump instruction followed by a zero nibble, the second byte is loaded from the ROM, which is an instruction but is treated as data. The Jump instruction is then executed and the PC can be viewed on Port 2, as well ### References [https://sites.google.com/site/consoleprotocols/home/techinfo/lowlevel/pif12](https://sites.google.com/site/consoleprotocols/home/techinfo/lowlevel/pif12) [https://code.google.com/archive/p/mupen64plus/wikis/SoftResetNotes.wiki](https://code.google.com/archive/p/mupen64plus/wikis/SoftResetNotes.wiki) [https://github.com/jago85/UltraCIC\_C/blob/master/cic\_c.c](https://github.com/jago85/UltraCIC_C/blob/master/cic_c.c) [https://github.com/hcs64/pif\_rom\_dumper](https://github.com/hcs64/pif_rom_dumper) Retrieved from "[https://n64brew.dev/wiki/CIC-NUS?oldid=5619](https://n64brew.dev/wiki/CIC-NUS?oldid=5619) " --- # Reality Display Processor/Commands - N64brew Wiki [](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#) Reality Display Processor/Commands ================================== < [Reality Display Processor](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor") | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 0x00 | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | | 0x08 | Fill Triangle | Fill Triangle (Z) | Fill Triangle (T) | Fill Triangle (TZ) | Fill Triangle (S) | Fill Triangle (SZ) | Fill Triangle (ST) | Fill Triangle (STZ) | | 0x10 | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | | 0x18 | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | No Operation | | 0x20 | No Operation | No Operation | No Operation | No Operation | Texture Rectangle | Texture Rectangle Flip | Sync Load | Sync Pipe | | 0x28 | Sync Tile | Sync Full | Set Key GB | Set Key R | Set Convert | Set Scissor | Set Primitive Depth | Set Other Modes | | 0x30 | Load TLUT | No Operation | Set Tile Size | Load Block | Load Tile | Set Tile | Fill Rectangle | Set Fill Color | | 0x38 | Set Fog Color | Set Blend Color | Set Primitive Color | Set Environment Color | Set Combine Mode | Set Texture Image | Set Depth Image | Set Color Image | Contents -------- * [1 0x00 through 0x07, 0x10 through 0x23, 0x31 - No Operation](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x00_through_0x07,_0x10_through_0x23,_0x31_-_No_Operation) * [2 0x08 through 0x0F - Fill Triangle](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x08_through_0x0F_-_Fill_Triangle) * [2.1 Base Command](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#Base_Command) * [2.2 Optional Shading Properties](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#Optional_Shading_Properties) * [2.3 Optional Texturing Properties](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#Optional_Texturing_Properties) * [2.4 Optional Depth Properties](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#Optional_Depth_Properties) * [3 0x24 and 0x25 - Texture Rectangle](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x24_and_0x25_-_Texture_Rectangle) * [4 0x26 - Sync Load](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x26_-_Sync_Load) * [5 0x27 - Sync Pipe](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x27_-_Sync_Pipe) * [6 0x28 - Sync Tile](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x28_-_Sync_Tile) * [7 0x29 - Sync Full](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x29_-_Sync_Full) * [8 0x2A - Set Key GB](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x2A_-_Set_Key_GB) * [9 0x2B - Set Key R](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x2B_-_Set_Key_R) * [10 0x2C - Set Convert](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x2C_-_Set_Convert) * [11 0x2D - Set Scissor](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x2D_-_Set_Scissor) * [12 0x2E - Set Primitive Depth](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x2E_-_Set_Primitive_Depth) * [13 0x2F - Set Other Modes](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x2F_-_Set_Other_Modes) * [14 0x30 - Load TLUT](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x30_-_Load_TLUT) * [15 0x32 - Set Tile Size](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x32_-_Set_Tile_Size) * [16 0x33 - Load Block](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x33_-_Load_Block) * [17 0x34 - Load Tile](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x34_-_Load_Tile) * [18 0x35 - Set Tile](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x35_-_Set_Tile) * [19 0x36 - Fill Rectangle](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x36_-_Fill_Rectangle) * [20 0x37 - Set Fill Color](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x37_-_Set_Fill_Color) * [21 0x38 - Set Fog Color](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x38_-_Set_Fog_Color) * [22 0x39 - Set Blend Color](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x39_-_Set_Blend_Color) * [23 0x3A - Set Primitive Color](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x3A_-_Set_Primitive_Color) * [24 0x3B - Set Environment Color](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x3B_-_Set_Environment_Color) * [25 0x3C - Set Combine Mode](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x3C_-_Set_Combine_Mode) * [26 0x3D - Set Texture Image](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x3D_-_Set_Texture_Image) * [27 0x3E - Set Depth Image](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x3E_-_Set_Depth_Image) * [28 0x3F - Set Color Image](https://n64brew.dev/wiki/Reality_Display_Processor/Commands#0x3F_-_Set_Color_Image) ### 0x00 through 0x07, 0x10 through 0x23, 0x31 - No Operation * * * | No Operation `0x00-0x07`,`0x10-0x23`,`0x31` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x00-0x07,0x10-0x23,0x31 | Stalls the RDP pipeline for 1 cycle. (TOVERIFY all of these command ids appear to behave as no-ops and take only 1 cycle to execute in testing so far, however this should be checked more extensively) ### 0x08 through 0x0F - Fill Triangle * * * The Fill Triangle command varies in length depending on whether shade, texture or depth is enabled in the first word. For any of these properties that are enabled, additional words are appended to the base command words in order of shading, texturing, z-buffering. #### Base Command Every triangle type begins with a common base that describes the type, shape and other small properties of the triangle. | Fill Triangle `0x08` through `0x0F` (base) | | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Word 0 | 63:48 | — | — | 0 | 0 | 1 | shade | texture | zbuffer | lmajor | — | level\[2:0\] | | | tile\[2:0\] | | | | 47:32 | — | — | yl\[13:0\] | | | | | | | | | | | | | | | 31:16 | — | — | ym\[13:0\] | | | | | | | | | | | | | | | 15:0 | — | — | yh\[13:0\] | | | | | | | | | | | | | | | Word 1 | 63:48 | — | — | — | — | xl.i\[11:0\] | | | | | | | | | | | | | 47:32 | xl.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | — | — | dxldy.i\[13:0\] | | | | | | | | | | | | | | | 15:0 | dxldy.f\[15:0\] | | | | | | | | | | | | | | | | | Word 2 | 63:48 | — | — | — | — | xh.i\[15:0\] | | | | | | | | | | | | | 47:32 | xh.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | — | — | dxhdy.i\[15:0\] | | | | | | | | | | | | | | | 15:0 | dxhdy.f\[15:0\] | | | | | | | | | | | | | | | | | Word 3 | 63:48 | — | — | — | — | xm.i\[15:0\] | | | | | | | | | | | | | 47:32 | xm.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | — | — | dxmdy.i\[15:0\] | | | | | | | | | | | | | | | 15:0 | dxmdy.f\[15:0\] | | | | | | | | | | | | | | | | **Word 0** | | | | --- | --- | | bit 61:56 | **command:** 0x08 through 0x0F depending on features specified | | bit 58 | **shade:** If 1, command is followed by 8 words specifying shading coefficients | | bit 57 | **texture:** If 1, command is followed by 8 words specifying texturing coefficients | | bit 56 | **zbuffer:** If 1, command is followed by 2 words specifying z-buffering coefficients | | bit 55 | **lmajor:** Left-major flag, if enabled rendering occurs from left-to-right, otherwise rendering occurs from right-to-left | | bit 53:51 | **level\[2:0\]:** Maximum LOD level | | bit 50:48 | **tile\[2:0\]:** Base tile descriptor index | | bit 45:32 | **yl\[13:0\]:** Lowest y coordinate (largest value), rasterization ends at this height (s11.2 format) | | bit 29:16 | **ym\[13:0\]:** Middle y coordinate (middle value), rasterization swaps dxmdy for dxldy (s11.2 format) | | bit 13:0 | **yh\[13:0\]:** Highest y coordinate (highest value), rasterization begins at this height (s11.2 format) | **Word 1** | | | | --- | --- | | bit 59:48 | **xl.i\[11:0\]:** Integer part of x coordinate on dxldy line at height ym (s11.16 format) | | bit 47:32 | **xl.f\[15:0\]:** Fractional part of x coordinate on dxldy line at height ym (s11.16 format) | | bit 29:16 | **dxldy.i\[13:0\]:** Integer part of change in x per change in y of line connecting middle and lowest vertices (s13.16 format) | | bit 15:0 | **dxldy.f\[15:0\]:** Fractional part of change in x per change in y of line connecting middle and lowest vertices (s13.16 format) | **Word 2** | | | | --- | --- | | bit 59:48 | **xh.i\[11:0\]:** Integer part of x coordinate on dxhdy line at height floor(yh) (s11.16 format) | | bit 47:32 | **xh.f\[15:0\]:** Fractional part of x coordinate on dxhdy line at height floor(yh) (s11.16 format) | | bit 29:16 | **dxhdy.i\[13:0\]:** Integer part of change in x per change in y of line connecting highest and lowest vertices (s13.16 format) | | bit 15:0 | **dxhdy.f\[15:0\]:** Fractional part of change in x per change in y of line connecting highest and lowest vertices (s13.16 format) | **Word 3** | | | | --- | --- | | bit 59:48 | **xm.i\[11:0\]:** Integer part of x coordinate on dxmdy line at height floor(yh) (s11.16 format) | | bit 47:32 | **xm.f\[15:0\]:** Fractional part of x coordinate on dxmdy line at height floor(yh) (s11.16 format) | | bit 29:16 | **dxmdy.i\[13:0\]:** Integer part of change in x per change in y of line connecting highest and middle vertices (s13.16 format) | | bit 15:0 | **dxmdy.f\[15:0\]:** Fractional part of change in x per change in y of line connecting highest and middle vertices (s13.16 format) | * * * #### Optional Shading Properties If the **shade** bit was set in the base triangle description, the above words will be followed by these 8 words describing triangle shading properties: | Fill Shaded Triangle (suffix) | | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Word 0 | 63:48 | \- | \- | \- | \- | \- | \- | \- | r.i\[8:0\] | | | | | | | | | | 47:32 | \- | \- | \- | \- | \- | \- | \- | g.i\[8:0\] | | | | | | | | | | 31:16 | \- | \- | \- | \- | \- | \- | \- | b.i\[8:0\] | | | | | | | | | | 15:0 | \- | \- | \- | \- | \- | \- | \- | a.i\[8:0\] | | | | | | | | | | Word 1 | 63:48 | drdx.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dgdx.i\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dbdx.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dadx.i\[15:0\] | | | | | | | | | | | | | | | | | Word 2 | 63:48 | r.f\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | g.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | b.f\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | a.f\[15:0\] | | | | | | | | | | | | | | | | | Word 3 | 63:48 | drdx.f\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dgdx.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dbdx.f\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dadx.f\[15:0\] | | | | | | | | | | | | | | | | | Word 4 | 63:48 | drde.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dgde.i\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dbde.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dade.i\[15:0\] | | | | | | | | | | | | | | | | | Word 5 | 63:48 | drdy.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dgdy.i\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dbdy.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dady.i\[15:0\] | | | | | | | | | | | | | | | | | Word 6 | 63:48 | drde.f\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dgde.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dbde.f\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dade.f\[15:0\] | | | | | | | | | | | | | | | | | Word 7 | 63:48 | drdy.f\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dgdy.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dbdy.f\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dady.f\[15:0\] | | | | | | | | | | | | | | | | **Word 0** | | | | --- | --- | | bit 56:48 | **r.i\[8:0\]:** Integer part of red channel shade color at **(xh, floor(yh))** (s8.16 format) | | bit 40:32 | **g.i\[8:0\]:** Integer part of green channel shade color at **(xh, floor(yh))** (s8.16 format) | | bit 24:16 | **b.i\[8:0\]:** Integer part of blue channel shade color at **(xh, floor(yh))** (s8.16 format) | | bit 8:0 | **a.i\[8:0\]:** Integer part of alpha channel shade color at **(xh, floor(yh))** (s8.16 format) | **Word 1** | | | | --- | --- | | bit 63:48 | **drdx.i\[15:0\]:** Integer part of change in red channel shade color horizontally along a scanline (s15.16 format) | | bit 47:32 | **dgdx.i\[15:0\]:** Integer part of change in green channel shade color horizontally along a scanline (s15.16 format) | | bit 31:16 | **dbdx.i\[15:0\]:** Integer part of change in blue channel shade color horizontally along a scanline (s15.16 format) | | bit 15:0 | **dadx.i\[15:0\]:** Integer part of change in alpha channel shade color horizontally along a scanline (s15.16 format) | **Word 2** | | | | --- | --- | | bit 63:48 | **r.f\[15:0\]:** Fractional part of red channel shade color at **(xh, floor(yh))** (s8.16 format) | | bit 47:32 | **g.f\[15:0\]:** Fractional part of green channel shade color at **(xh, floor(yh))** (s8.16 format) | | bit 31:16 | **b.f\[15:0\]:** Fractional part of blue channel shade color at **(xh, floor(yh))** (s8.16 format) | | bit 15:0 | **a.f\[15:0\]:** Fractional part of alpha channel shade color at **(xh, floor(yh))** (s8.16 format) | **Word 3** | | | | --- | --- | | bit 63:48 | **drdx.f\[15:0\]:** Fractional part of change in red channel shade color horizontally along a scanline (s15.16 format) | | bit 47:32 | **dgdx.f\[15:0\]:** Fractional part of change in green channel shade color horizontally along a scanline (s15.16 format) | | bit 31:16 | **dbdx.f\[15:0\]:** Fractional part of change in blue channel shade color horizontally along a scanline (s15.16 format) | | bit 15:0 | **dadx.f\[15:0\]:** Fractional part of change in alpha channel shade color horizontally along a scanline (s15.16 format) | **Word 4** | | | | --- | --- | | bit 63:48 | **drde.i\[15:0\]:** Integer part of change in red channel shade color along the major edge (s15.16 format) | | bit 47:32 | **dgde.i\[15:0\]:** Integer part of change in green channel shade color along the major edge (s15.16 format) | | bit 31:16 | **dbde.i\[15:0\]:** Integer part of change in blue channel shade color along the major edge (s15.16 format) | | bit 15:0 | **dade.i\[15:0\]:** Integer part of change in alpha channel shade color along the major edge (s15.16 format) | **Word 5** | | | | --- | --- | | bit 63:48 | **drdy.i\[15:0\]:** Integer part of change in red channel shade color for each scanline (s15.16 format) | | bit 47:32 | **dgdy.i\[15:0\]:** Integer part of change in green channel shade color for each scanline (s15.16 format) | | bit 31:16 | **dbdy.i\[15:0\]:** Integer part of change in blue channel shade color for each scanline (s15.16 format) | | bit 15:0 | **dady.i\[15:0\]:** Integer part of change in alpha channel shade color for each scanline (s15.16 format) | **Word 6** | | | | --- | --- | | bit 63:48 | **drde.f\[15:0\]:** Fractional part of change in red channel shade color along the major edge (s15.16 format) | | bit 47:32 | **dgde.f\[15:0\]:** Fractional part of change in green channel shade color along the major edge (s15.16 format) | | bit 31:16 | **dbde.f\[15:0\]:** Fractional part of change in blue channel shade color along the major edge (s15.16 format) | | bit 15:0 | **dade.f\[15:0\]:** Fractional part of change in alpha channel shade color along the major edge (s15.16 format) | **Word 7** | | | | --- | --- | | bit 63:48 | **drdy.f\[15:0\]:** Fractional part of change in red channel shade color for each scanline (s15.16 format) | | bit 47:32 | **dgdy.f\[15:0\]:** Fractional part of change in green channel shade color for each scanline (s15.16 format) | | bit 31:16 | **dbdy.f\[15:0\]:** Fractional part of change in blue channel shade color for each scanline (s15.16 format) | | bit 15:0 | **dady.f\[15:0\]:** Fractional part of change in alpha channel shade color for each scanline (s15.16 format) | * * * #### Optional Texturing Properties If the **texture** bit was set in the base triangle description, the above words will be followed by these 8 words describing triangle texturing properties: | Fill Textured Triangle (suffix) | | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Word 0 | 63:48 | s.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | t.i\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | w.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Word 1 | 63:48 | dsdx.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dtdx.i\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dwdx.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Word 2 | 63:48 | s.f\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | t.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | w.f\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Word 3 | 63:48 | dsdx.f\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dtdx.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dwdx.f\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Word 4 | 63:48 | dsde.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dtde.i\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dwde.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Word 5 | 63:48 | dsdy.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dtdy.i\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dwdy.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Word 6 | 63:48 | dsde.f\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dtde.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dwde.f\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Word 7 | 63:48 | dsdy.f\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dtdy.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dwdy.f\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | **Word 0** | | | | --- | --- | | bit 63:48 | **s.i\[15:0\]:** Integer part of s-axis texture coordinate at **(xh, floor(yh))** | | bit 47:32 | **t.i\[15:0\]:** Integer part of t-axis texture coordinate at **(xh, floor(yh))** | | bit 31:16 | **w.i\[15:0\]:** Integer part of perspective scale at **(xh, floor(yh))** | **Word 1** | | | | --- | --- | | bit 63:48 | **dsdx.i\[15:0\]:** Integer part of change in s-axis texture coordinate horizontally along a scanline | | bit 47:32 | **dtdx.i\[15:0\]:** Integer part of change in t-axis texture coordinate horizontally along a scanline | | bit 31:16 | **dwdx.i\[15:0\]:** Integer part of change in perspective scale horizontally along a scanline | **Word 2** | | | | --- | --- | | bit 63:48 | **s.f\[15:0\]:** Fractional part of s-axis texture coordinate at **(xh, floor(yh))** | | bit 47:32 | **t.f\[15:0\]:** Fractional part of t-axis texture coordinate at **(xh, floor(yh))** | | bit 31:16 | **w.f\[15:0\]:** Fractional part of perspective scale at **(xh, floor(yh))** | **Word 3** | | | | --- | --- | | bit 63:48 | **dsdx.f\[15:0\]:** Fractional part of change in s-axis texture coordinate horizontally along a scanline | | bit 47:32 | **dtdx.f\[15:0\]:** Fractional part of change in t-axis texture coordinate horizontally along a scanline | | bit 31:16 | **dwdx.f\[15:0\]:** Fractional part of change in perspective scale horizontally along a scanline | **Word 4** | | | | --- | --- | | bit 63:48 | **dsde.i\[15:0\]:** Integer part of change in s-axis texture coordinate along the major edge | | bit 47:32 | **dtde.i\[15:0\]:** Integer part of change in t-axis texture coordinate along the major edge | | bit 31:16 | **dwde.i\[15:0\]:** Integer part of change in perspective scale along the major edge | **Word 5** | | | | --- | --- | | bit 63:48 | **dsdy.i\[15:0\]:** Integer part of change in s-axis texture coordinate for each scanline | | bit 47:32 | **dtdy.i\[15:0\]:** Integer part of change in t-axis texture coordinate for each scanline | | bit 31:16 | **dwdy.i\[15:0\]:** Integer part of change in perspective scale for each scanline | **Word 6** | | | | --- | --- | | bit 63:48 | **dsde.f\[15:0\]:** Fractional part of change in s-axis texture coordinate along the major edge | | bit 47:32 | **dtde.f\[15:0\]:** Fractional part of change in t-axis texture coordinate along the major edge | | bit 31:16 | **dwde.f\[15:0\]:** Fractional part of change in perspective scale along the major edge | **Word 7** | | | | --- | --- | | bit 63:48 | **dsdy.f\[15:0\]:** Fractional part of change in s-axis texture coordinate for each scanline | | bit 47:32 | **dtdy.f\[15:0\]:** Fractional part of change in t-axis texture coordinate for each scanline | | bit 31:16 | **dwdy.f\[15:0\]:** Fractional part of change in perspective scale for each scanline | * * * #### Optional Depth Properties If the **zbuffer** bit was set in the base triangle description, the above words will be followed by these 2 words describing triangle depth properties: | Fill Z-Buffered Triangle (suffix) | | | | | | | | | | | | | | | | | | | --- | --- | --- | | Word 0 | 63:48 | z.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | z.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dzdx.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dzdx.f\[15:0\] | | | | | | | | | | | | | | | | | Word 1 | 63:48 | dzde.i\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | dzde.f\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dzdy.i\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dzdy.f\[15:0\] | | | | | | | | | | | | | | | | **Word 0** | | | | --- | --- | | bit 63:48 | **z.i\[15:0\]:** Integer part of depth at **(xh, floor(yh))** | | bit 47:32 | **z.f\[15:0\]:** Fractional part of depth at **(xh, floor(yh))** | | bit 31:16 | **dzdx.i\[15:0\]:** Integer part of change in depth horizontally along a scanline | | bit 15:0 | **dzdx.f\[15:0\]:** Fractional part of change in depth horizontally along a scanline | **Word 1** | | | | --- | --- | | bit 63:48 | **dzde.i\[15:0\]:** Integer part of change in depth along the major edge | | bit 47:32 | **dzde.f\[15:0\]:** Fractional part of change in depth along the major edge | | bit 31:16 | **dzdy.i\[15:0\]:** Integer part of change in depth for each scanline | | bit 15:0 | **dzdy.f\[15:0\]:** Fractional part of change in depth for each scanline | ### 0x24 and 0x25 - Texture Rectangle * * * | Texture Rectangle `0x24` and Texture Rectangle Flip `0x25` | | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | | Word 0 | 63:48 | — | — | command\[5:0\] = 0x24 or 0x25 | | | | | | lrx\[11:4\] | | | | | | | | | 47:32 | lrx\[3:0\] | | | | lry\[11:0\] | | | | | | | | | | | | | 31:16 | — | — | — | — | — | tile\[2:0\] | | | ulx\[11:4\] | | | | | | | | | 15:0 | ulx\[3:0\] | | | | uly\[11:0\] | | | | | | | | | | | | | Word 1 | 63:48 | s\[15:0\] | | | | | | | | | | | | | | | | | 47:32 | t\[15:0\] | | | | | | | | | | | | | | | | | 31:16 | dsdx\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dtdy\[15:0\] | | | | | | | | | | | | | | | | **Word 0** | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x24 (Texture\_Rectangle) or 0x25 (Texture\_Rectangle\_Flip) | | bit 55:44 | **lrx\[11:0\]:** Lower-right x screen coordinate (u10.2 format) | | bit 43:32 | **lry\[11:0\]:** Lower-right y screen coordinate (u10.2 format) | | bit 26:24 | **tile\[2:0\]:** Tile descriptor index | | bit 23:12 | **ulx\[11:0\]:** Upper-left x screen coordinate (u10.2 format) | | bit 11:0 | **uly\[11:0\]:** Upper-left y screen coordinate (u10.2 format) | **Word 1** | | | | --- | --- | | bit 63:48 | **s\[15:0\]:** Upper-left s coordinate (s10.5 format) | | bit 47:32 | **t\[15:0\]:** Upper-left t coordinate (s10.5 format) | | bit 31:16 | **dsdx\[15:0\]:** Change in s per x (s5.10 format) | | bit 15:0 | **dtdy\[15:0\]:** Change in t per y (s5.10 format) | Renders a textured rectangle between upper-left coordinate **(ulx,uly)** and lower-right coordinate **(lrx,lry)**, sampling texels using the tile descriptor indexed by **tile**. The coordinates **(s,t)** are the texture coordinates for the pixel at **(ulx,uly)**, the derivatives **dsdx** and **dtdy** are added to these initial coordinates to determine the texture coordinates for every other pixel. The screen coordinates are subpixel precise, integer values correspond to single pixels. In COPY and FILL mode the fractional bits are ignored and the lower-right bounds are inclusive, while in 1-Cycle and 2-Cycle modes the lower-right bounds are exclusive. In COPY mode, since several pixels are written per cycle the **dsdx** parameter must account for this. It should be set such that it steps 64-bits worth of texels at a time, for example for a 16-bit color image it should be set to 4.0(s10.5) so that the s coordinate is incremented by 4 pixels (64 bits). In FILL mode this behaves identically to **Fill Rectangle**, the texturing properties are ignored. Internally, this command is equivalent to a left-major triangle where `dxhdy = dxmdy = dxldy = 0, yl = ym = lry, yh = uly, xl = xm = lrx, xh = ulx` **Hazards** * In 1-Cycle and 2-Cycle mode using attributes such as shade color and per-pixel depth may be ill-defined. (TOVERIFY: It's either always 0 or uses the last value from previous primitives, check which) ### 0x26 - Sync Load * * * | Sync Load `0x26` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x26\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x26 | Stalls the RDP pipeline for exactly 25 GCLK cycles. This guarantees that the loading pipeline will be available for use following any prior operation. The stall is always 25 cycles and does not wait on any particular internal signal(s), if a Sync Load is queued when it is not needed it simply wastes the full length of time. ### 0x27 - Sync Pipe * * * | Sync Pipe `0x27` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x27\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x27 | Stalls the RDP pipeline for exactly 50 GCLK cycles. This guarantees that any preceding primitives will be fully rendered and it is safe to modify rendering attributes such as color registers, othermodes and combine mode. The stall is always 50 cycles and does not wait on any particular internal signal(s), if a Sync Pipe is queued when it is not needed it simply wastes the full length of time. (TOVERIFY/Speculation: There is no known attribute that takes more than 33 cycles to sync. Can a tile sync account for all attribute changes?) ### 0x28 - Sync Tile * * * | Sync Tile `0x28` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x28\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x28 | Stalls the RDP pipeline for exactly 33 GCLK cycles. This guarantees that any preceding primitives will have finished using tile information and that it is now safe to modify tile descriptors without affecting prior primitives. The stall is always 33 cycles and does not wait on any particular internal signal(s), if a Sync Tile is queued when it is not needed it simply wastes the full length of time. ### 0x29 - Sync Full * * * | Sync Full `0x29` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x29\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 15:0 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x29 | Waits for all currently staged pipeline and memory operations to complete before halting the RDP pipeline counter and raising the DP interrupt in the [MIPS Interface](https://n64brew.dev/wiki/MIPS_Interface "MIPS Interface") . **Hazards** * Ensure this is the final command consumed before hitting DP\_END, otherwise the RDP may hang. * Ensure no other commands are sent to RDP (via DP\_START/DP\_END) while a Sync Full is in progress, otherwise the RDP may hang. ### 0x2A - Set Key GB * * * | Set Key GB `0x2a` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x2a\[5:0\] | | | | | | width\_G\[11:4\] | | | | | | | | | 47:32 | width\_G\[3:0\] | | | | width\_B\[11:0\] | | | | | | | | | | | | | 31:16 | center\_G\[7:0\] | | | | | | | | scale\_G\[7:0\] | | | | | | | | | 15:0 | center\_B\[7:0\] | | | | | | | | scale\_B\[7:0\] | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x2a | | bit 55:44 | **width\_G\[11:0\]:** Keying window half-width for green channel (4.8 format) | | bit 43:32 | **width\_B\[11:0\]:** Keying window half-width for blue channel (4.8 format) | | bit 31:24 | **center\_G\[7:0\]:** Center Color Combiner input for green channel | | bit 23:16 | **scale\_G\[7:0\]:** Scale Color Combiner input for green channel | | bit 15:8 | **center\_B\[7:0\]:** Center Color Combiner input for blue channel | | bit 7:0 | **scale\_B\[7:0\]:** Scale Color Combiner input for blue channel | Sets chroma Key parameters for the green and blue color channels. Key width is used exclusively in the chroma key pipeline stage following the Color Combiner (if enabled in othermodes) k e y \_ a l p h a \= c l a m p ( − a b s ( C o m b i n e d ) + W i d t h , 0.0 , 1.0 ) {\\displaystyle {\\displaystyle \\mathrm {key\\\_alpha} =\\mathrm {clamp} (-\\mathrm {abs} (\\mathrm {Combined} )+\\mathrm {Width} ,0.0,1.0)}}   Key center and scale are Color Combiner inputs, the expectation is that the Color Combiner is configured to ( X − C e n t e r ) ⋅ S c a l e {\\displaystyle {\\displaystyle (X-\\mathrm {Center} )\\cdot \\mathrm {Scale} }}   when chroma keying, where X {\\displaystyle {\\displaystyle X}}   is any color source. If not chroma keying, these may be used as general-purpose Color Combiner inputs. ### 0x2B - Set Key R * * * | Set Key R `0x2b` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x2b\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | — | — | — | — | width\_R\[11:0\] | | | | | | | | | | | | | 15:0 | center\_R\[7:0\] | | | | | | | | scale\_R\[7:0\] | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x2b | | bit 27:16 | **width\_R\[11:0\]:** Keying window half-width for red channel (4.8 format) | | bit 15:8 | **center\_R\[7:0\]:** Center Color Combiner input for red channel | | bit 7:0 | **scale\_R\[7:0\]:** Scale Color Combiner input for red channel | Sets Chroma Key parameters for the red color channel. See **Set Key GB** for discussion. ### 0x2C - Set Convert * * * | Set Convert `0x2c` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x2c\[5:0\] | | | | | | — | — | K0\[8:3\] | | | | | | | 47:32 | K0\[2:0\] | | | K1\[8:0\] | | | | | | | | | K2\[8:5\] | | | | | 31:16 | K2\[4:0\] | | | | | K3\[8:0\] | | | | | | | | | K4\[8:7\] | | | 15:0 | K4\[6:0\] | | | | | | | K5\[8:0\] | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x2c | | bit 53:45 | **K0\[8:0\]:** Color conversion constant K0 | | bit 44:36 | **K1\[8:0\]:** Color conversion constant K1 | | bit 35:27 | **K2\[8:0\]:** Color conversion constant K2 | | bit 26:18 | **K3\[8:0\]:** Color conversion constant K3 | | bit 17:9 | **K4\[8:0\]:** Color conversion constant K4 | | bit 8:0 | **K5\[8:0\]:** Color conversion constant K5 | Sets colorspace conversion coefficients intended for YUV -> RGB conversion. K0 through K3 are used in the Texture Filter color conversion step: ( R G B A ) \= ( 1 0 K 0 1 K 1 K 2 1 K 3 0 1 0 0 ) ( Y U V ) {\\displaystyle {\\displaystyle {\\begin{pmatrix}R\\\\G\\\\B\\\\A\\\\\\end{pmatrix}}={\\begin{pmatrix}1&0&K\_{0}\\\\1&K\_{1}&K\_{2}\\\\1&K\_{3}&0\\\\1&0&0\\\\\\end{pmatrix}}{\\begin{pmatrix}Y\\\\U\\\\V\\\\\\end{pmatrix}}}}   K4 and K5 are made available as Color Combiner inputs, with the intention that the CC complete the colorspace conversion process after receiving values from the Texture Filter. K4 and K5 may also simply be used as additional general-purpose CC inputs. ### 0x2D - Set Scissor * * * | Set Scissor `0x2d` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x2d\[5:0\] | | | | | | upper\_left.x\[11:4\] | | | | | | | | | 47:32 | upper\_left.x\[3:0\] | | | | upper\_left.y\[11:0\] | | | | | | | | | | | | | 31:16 | — | — | — | — | — | — | field | odd | lower\_right.x\[11:4\] | | | | | | | | | 15:0 | scissor.x.lo\[3:0\] | | | | lower\_right.y\[11:0\] | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x2d | | bit 55:44 | **upper\_left.x\[11:0\]:** Upper left x coordinate (u10.2 format) | | bit 43:32 | **upper\_left.y\[11:0\]:** Upper left y coordinate (u10.2 format) | | bit 25 | **field:** Whether to perform interlaced rendering, only even or odd lines will be processed | | bit 24 | **odd:** Whether to keep even (0) or odd (1) lines when interlaced rendering is enabled | | bit 23:12 | **lower\_right.x\[11:0\]:** Lower right x coordinate (u10.2 format) | | bit 11:0 | **lower\_right.y\[11:0\]:** Lower right y coordinate (u10.2 format) | Sets the scissor region, pixels that are outside of this region will not be processed in the pixel pipeline and will not be written to memory. Note that only the scissor determines this, the color image width has no effect. Typically the scissor width should not be larger than the color image width. The RDP is capable of efficiently rejecting pixels that lie outside the left, right and lower scissor boundaries at no cost in time. The same cannot be said of pixels lying above the scissor region: for every line for which a primitive extends above the scissor region the RDP spends one cycle advancing each line, since it must update the attribute start values for each line. **Hazards** * The scissor region is **inclusive** on the right edge and **exclusive** on the lower edge in FILL and COPY modes, while it is **exclusive** on both the lower and right edges in 1-Cycle and 2-Cycle modes. * The scissor region must be configured before rendering anything. There is no way to disable the scissor, if it is not configured it will use whatever configuration was last in the registers. * In COPY mode, upper\_left.x should be 0 (TOVERIFY Is this always true? It appears to be an unavoidable crash, but needs further investigation) * In COPY mode, lower\_right.x should be a multiple of 4 pixels (-1) to prevent possible memory corruption * In FILL mode, upper\_left.x and lower\_right.x should be multiples of 4 pixels (-1 for lower\_right.x) to prevent possible memory corruption * FILL mode triangles interact bizarrely with the scissor bounds, avoid drawing such triangles that extend off-screen (TODO understand this better) * In FILL mode sometimes scissor works at the level of individual pixels while sometimes it only works at 4-pixel boundaries. (TODO understand this better, probably to do with memory alignment) ### 0x2E - Set Primitive Depth * * * | Set Primitive Depth `0x2e` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x2e\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | z\[15:0\] | | | | | | | | | | | | | | | | | 15:0 | dz\[15:0\] | | | | | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x2e | | bit 31:16 | **z\[15:0\]:** Primitive depth value | | bit 15:0 | **dz\[15:0\]:** Primitive dz value | Sets the primitive depth registers to the provided values. Primitive depth may be selected for depth compare instead of using per-pixel depth in the othermodes configuration. This is the only way to set a depth value for rectangle commands. When used, the primitive depth value is taken to be the integer part of the s15.3 fixed-point z value, the fractional bits will be zero. Notably, due to not having control over the fractional bits it is impossible to configure a primitive depth value that writes the maximum possible depth value into the z-buffer. Unlike the majority of other attribute-setters, primitive depth operates correctly even in the absence of pipeline synchronizations. Primitive depth may be changed between primitive rendering without corrupting prior primitives. **Hazards** * The dz value provided should be a power of 2 to ensure correct operation. RDP hardware uses a cheap integer log2 algorithm for computing log2(dz) that is only guaranteed to produce correct results when the input is a power of 2. Notably, the value 0xFFFF happens to work correctly. ### 0x2F - Set Other Modes * * * | Set Other Modes `0x2f` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x2f\[5:0\] | | | | | | atomic\_prim | \- | cycle\_type\[1:0\] | | persp\_tex\_en | detail\_tex\_en | sharpen\_tex\_en | tex\_lod\_en | | 47:32 | tlut\_en | tlut\_type | sample\_type | mid\_texel | bi\_lerp\_0 | bi\_lerp\_1 | convert\_one | key\_en | rgb\_dither\_sel\[1:0\] | | alpha\_dither\_sel\[1:0\] | | \- | | | | | 31:16 | bl\_m1a\_0\[1:0\] | | bl\_m1a\_1\[1:0\] | | bl\_m1b\_0\[1:0\] | | bl\_m1b\_1\[1:0\] | | bl\_m2a\_0\[1:0\] | | bl\_m2a\_1\[1:0\] | | bl\_m2b\_0\[1:0\] | | bl\_m2b\_1\[1:0\] | | | 15:0 | \- | force\_blend | alpha\_cvg\_select | cvg\_x\_alpha | z\_mode\[1:0\] | | cvg\_dest\[1:0\] | | color\_on\_cvg | image\_read\_en | z\_update\_en | z\_compare\_en | antialias\_en | z\_source\_sel | dither\_alpha\_en | alpha\_compare\_en | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x2f | | bit 55 | **atomic\_prim:** Enables span buffer coherency, forces active span segments to be written to the frame buffer before reading new span segments | | bit 54 | **\-:** ? | | bit 53:52 | **cycle\_type\[1:0\]:** Determines pipeline mode. Either 1-Cycle (0), 2-Cycle (1), COPY (2), FILL (3) | | bit 51 | **persp\_tex\_en:** Enables perspective correction of texture coordinates | | bit 50 | **detail\_tex\_en:** Enables "detail texture" mode in Texture LOD | | bit 49 | **sharpen\_tex\_en:** Enables "sharpen texture" mode in Texture LOD | | bit 48 | **tex\_lod\_en:** Enables Texture Level of Detail (LOD) | | bit 47 | **tlut\_en:** Enables Texture Look-Up Table (TLUT) sampling. Texels are first fetched from low TMEM that are then used to index a palette in high TMEM to find the final color values. | | bit 46 | **tlut\_type:** Determines TLUT texel format. Either RGBA16 (0) or IA16 (1) | | bit 45 | **sample\_type:** Determines texel sampling mode. Either point-sampled (0) or 2x2 bilinear (1) | | bit 44 | **mid\_texel:** Determines bilinear filter mode. Either 3-point (0) or average mode (1) | | bit 43 | **bi\_lerp\_0:** Determines texture filter mode for the first cycle. Either YUV to RGB conversion (See **Set Convert**) (0) or bilinear filter (1) | | bit 42 | **bi\_lerp\_1:** Determines texture filter mode for the second cycle. Either YUV to RGB conversion (See **Set Convert**) (0) or bilinear filter (1) | | bit 41 | **convert\_one:** Determines the input to the second texture filter stage. Either the sample from the second stage of texture sampling (0) or the result from the first texture filter cycle (1) | | bit 40 | **key\_en:** Enables chroma keying following the Color Combiner stage | | bit 39:38 | **rgb\_dither\_sel\[1:0\]:** Set RGB dither mode | | bit 37:36 | **alpha\_dither\_sel\[1:0\]:** Set Alpha dither mode | | bit 35:32 | **\-:** ? | | bit 31:30 | **bl\_m1a\_0\[1:0\]:** Blender input P (first cycle) | | bit 29:28 | **bl\_m1a\_1\[1:0\]:** Blender input P (second cycle) | | bit 27:26 | **bl\_m1b\_0\[1:0\]:** Blender input A (first cycle) | | bit 25:24 | **bl\_m1b\_1\[1:0\]:** Blender input A (second cycle) | | bit 23:22 | **bl\_m2a\_0\[1:0\]:** Blender input M (first cycle) | | bit 21:20 | **bl\_m2a\_1\[1:0\]:** Blender input M (second cycle) | | bit 19:18 | **bl\_m2b\_0\[1:0\]:** Blender input B (first cycle) | | bit 17:16 | **bl\_m2b\_1\[1:0\]:** Blender input B (second cycle) | | bit 15 | **\-:** ? | | bit 14 | **force\_blend:** Enables blending for all pixels rather than only edge pixels | | bit 13 | **alpha\_cvg\_select:** Use coverage (or coverage multiplied by CC alpha) for alpha input to blender rather than alpha output from CC | | bit 12 | **cvg\_x\_alpha:** Multiply coverage and alpha from CC (used in conjunction with alpha\_cvg\_sel) | | bit 11:10 | **z\_mode\[1:0\]:** Determines z-buffer comparator mode | | bit 9:8 | **cvg\_dest\[1:0\]:** Determines coverage output mode | | bit 7 | **color\_on\_cvg:** If enabled, writes the blender output only if coverage overflowed, otherwise write the 2B input verbatim | | bit 6 | **image\_read\_en:** Enable color image reading | | bit 5 | **z\_update\_en:** Enable z-buffer writing | | bit 4 | **z\_compare\_en:** Enable z-buffer reading and depth comparison | | bit 3 | **antialias\_en:** Enable anti-aliasing, which may enable blending on edge pixels | | bit 2 | **z\_source\_sel:** Selects either per-pixel (0) or primitive (1) depth as depth source to compare against the z-buffer | | bit 1 | **dither\_alpha\_en:** Determines alpha compare threshold source. (0 blend color register alpha, 1 random) | | bit 0 | **alpha\_compare\_en:** Enables alpha compare, pixels below the alpha threshold (compared against CC alpha output) are not written | Configures the RDP "Other Modes", a collection of global settings. **RGB dither modes** | Value | Description | | --- | --- | | 0 | 4x4 Magic Square dither matrix | | 1 | 4x4 Bayer dither matrix | | 2 | Random noise. Note the random sample is different per color channel, grayscale images may not remain grayscale after noise dithering. | | 3 | Disabled, no dithering applied | The dither matrices are both 4x4 and are indexed by the (integer) screen coordinates mod 4, or bits \[1:0\]. If scissoring is used, the y coordinate bits \[2:1\] are used to index the matrix instead. The Bayer matrix is: ( 0 4 1 5 4 0 5 1 3 7 2 6 7 3 6 2 ) {\\displaystyle {\\displaystyle {\\begin{pmatrix}0&4&1&5\\\\4&0&5&1\\\\3&7&2&6\\\\7&3&6&2\\\\\\end{pmatrix}}}}   The Magic Square matrix is: ( 0 6 1 7 4 2 5 3 3 5 2 4 7 1 6 0 ) {\\displaystyle {\\displaystyle {\\begin{pmatrix}0&6&1&7\\\\4&2&5&3\\\\3&5&2&4\\\\7&1&6&0\\\\\\end{pmatrix}}}}   **Alpha dither modes** | Value | Description | | --- | --- | | 0 | Same pattern as chosen in RGB. If RGB dither was set to noise, use magic square. If RGB dither was disabled, use bayer. | | 1 | Inverse of the same pattern as chosen in RGB. Same rules as above if RGB dither was set to noise or disabled. | | 2 | Random noise | | 3 | Disabled, no dithering applied | **Z modes** | Value | Description | | --- | --- | | 0 | Opaque surface mode. | | 1 | Interpenetrating surface mode. | | 2 | Transparent surface mode. | | 3 | Decal surface mode. | **Coverage destination modes** | Value | Description | | --- | --- | | 0 | Clamp. Sums new and old coverage, clamps to full if there is an overflow. | | 1 | Wrap. Sums new and old coverage, writes this sum modulo 8. | | 2 | Full. Always write full coverage. | | 3 | Save. Always write old coverage, discard new coverage. Requires image\_read\_en, otherwise it will behave like Full. | **Blender Configuration** Other modes include the blender input configuration. The blender computes either p ⋅ a + m ⋅ b {\\displaystyle {\\displaystyle p\\cdot a+m\\cdot b}}   or p ⋅ a + m ⋅ b a + b {\\displaystyle {\\displaystyle {\\frac {p\\cdot a+m\\cdot b}{a+b}}}}   The latter is performed only when force\_blend is disabled and anti-aliasing is enabled, where it is performed only on edge pixels. **Blender P and M Inputs** | Value | Description | | --- | --- | | 0 | First cycle: output color from Color Combiner final stage; Second cycle: output color from first blender cycle | | 1 | Memory color from framebuffer | | 2 | Blend color register RGB | | 3 | Fog color register RGB | **Blender A Inputs** | Value | Description | | --- | --- | | 0 | Output alpha from Color Combiner final stage | | 1 | Fog color register Alpha | | 2 | Shade Alpha (interpolated per-pixel) | | 3 | Fixed 0.0 | **Blender B Inputs** | Value | Description | | --- | --- | | 0 | 1.0 - A, where A is the other alpha input | | 1 | Memory coverage from framebuffer | | 2 | Fixed 1.0 | | 3 | Fixed 0.0 | **Hazards** * This is a pipeline configuration command that requires pipeline synchronization before use, otherwise currently rendering primitives may be partially rendered using both the old other modes configuration and the new other modes configuration. * In 1-Cycle mode only the blender's first cycle configuration is used, the second cycle configuration is ignored. * Alpha compare in COPY mode targeting 16-bit framebuffers is only valid for RGBA5551 textures, it only considers the least-significant bit in the sampled texel for determining whether to write it. * Blender hazards: * If memory color was set as a blender input, **image\_read\_en** should be enabled for proper operation. * If memory coverage was set as a blender input, **image\_read\_en** should be enabled for proper operation. If it is left disabled, memory coverage is set to full. * If **cvg\_dest** was set to _Save_ (3), **image\_read\_en** should be enabled for proper operation. If it is left disabled, _Save_ (3) will act like _Full_ (2). * 2-Cycle mode pipeline bug: In the first cycle of 2-cycle mode, memory color is read from the previous pixel. * 2-Cycle mode pipeline bug: In the first cycle of 2-cycle mode, memory coverage is read from the previous pixel. * 2-Cycle mode pipeline bug: In the second cycle of 2-cycle mode, shade alpha is read from the next pixel. * 2-Cycle mode pipeline bug: Alpha compare uses the output of the first combiner cycle of the next pixel as the value to compare to the threshold. ### 0x30 - Load TLUT * * * | Load TLUT `0x30` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x34\[5:0\] | | | | | | upper\_left.s\[11:4\] | | | | | | | | | 47:32 | upper\_left.s\[3:0\] | | | | upper\_left.t\[11:0\] | | | | | | | | | | | | | 31:16 | — | — | — | — | — | tile\[2:0\] | | | lower\_right.s\[11:4\] | | | | | | | | | 15:0 | lower\_right.s\[3:0\] | | | | lower\_right.t\[11:0\] | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x30 | | bit 55:44 | **upper\_left.s\[11:0\]:** Upper-left s coordinate (u10.2) | | bit 43:32 | **upper\_left.t\[11:0\]:** Upper-left t coordinate (u10.2) | | bit 26:24 | **tile\[2:0\]:** Tile descriptor index | | bit 23:12 | **lower\_right.s\[11:0\]:** Lower-right s coordinate (u10.2) | | bit 11:0 | **lower\_right.t\[11:0\]:** Lower-right t coordinate (u10.2) | Loads a Texture Look-Up Table (TLUT) into TMEM. When loading with Load TLUT, every texel loaded from RDRAM is quadrupled and placed adjacently in TMEM. A 256-color TLUT therefore takes up 256 × 2 × 4 \= 0 x 800 {\\displaystyle {\\displaystyle \\mathrm {256} \\times \\mathrm {2} \\times \\mathrm {4} =\\mathrm {0x800} }}   bytes of TMEM, which is the maximum size of a TLUT as it must fit in high TMEM. A 16-color TLUT takes up only 16 × 2 × 4 \= 0 x 80 {\\displaystyle {\\displaystyle \\mathrm {16} \\times \\mathrm {2} \\times \\mathrm {4} =\\mathrm {0x80} }}   bytes of TMEM, 16 of these may reside in high TMEM at the same time. Load TLUT updates the tile size for the selected tile descriptor to `(upper_left.s, upper_left.t, lower_right.s, lower_right.t)`. This is generally undesirable for rendering; ensure that, if using the same tile for subsequent rendering, the tile size is reconfigured following this command with appropriate synchronization. Typical usage of Load TLUT may resemble `(uls=0, ult=0, lrs=count-1, lrt=0)` where `count` is the number of texels making up the TLUT, e.g. 255 for a 256-color palette. **Hazards** * To ensure data is loaded correctly the associated tile descriptor should be configured to use a 4-bit texel size, and should not be configured to use YUV or RGBA texel formats. Note however that the texture image should still be configured to use a 16-bit texel size. * For correct sampling, the base TMEM address for a TLUT must reside in the upper half of TMEM, that is at TMEM word address 0x100 (byte address 0x800) or greater. * For correct sampling, the base TMEM address for a TLUT must be aligned to 16 TMEM words or 128 bytes. ### 0x32 - Set Tile Size * * * | Set Tile Size `0x32` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x32\[5:0\] | | | | | | upper\_left.s\[11:4\] | | | | | | | | | 47:32 | upper\_left.s\[3:0\] | | | | upper\_left.t\[11:0\] | | | | | | | | | | | | | 31:16 | — | — | — | — | — | index\[2:0\] | | | lower\_right.s\[11:4\] | | | | | | | | | 15:0 | lower\_right.s\[3:0\] | | | | lower\_right.t\[11:0\] | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x32 | | bit 55:44 | **upper\_left.s\[11:0\]:** Upper-left s coordinate (u10.2 format) | | bit 43:32 | **upper\_left.t\[11:0\]:** Upper-left t coordinate (u10.2 format) | | bit 26:24 | **index\[2:0\]:** Tile descriptor index | | bit 23:12 | **lower\_right.s\[11:0\]:** Lower-right s coordinate (u10.2 format) | | bit 11:0 | **lower\_right.t\[11:0\]:** Lower-right t coordinate (u10.2 format) | Updates the tile extents for the tile descriptor specified by the index parameter. The upper-left coordinate specifies the origin of the tile, where texture coordinates (0,0) would sample from, and sets the lower extents for clamp/mask/mirror in texture sampling. The lower-right coordinate specifies the upper extents for clamp/mask/mirror in texture sampling. ### 0x33 - Load Block * * * | Load Block `0x33` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x33\[5:0\] | | | | | | upper\_left.s\[11:4\] | | | | | | | | | 47:32 | upper\_left.s\[3:0\] | | | | upper\_left.t\[11:0\] | | | | | | | | | | | | | 31:16 | — | — | — | — | — | tile\[2:0\] | | | lower\_right.s\[11:4\] | | | | | | | | | 15:0 | lower\_right.s\[3:0\] | | | | dxt\[11:0\] | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x33 | | bit 55:44 | **upper\_left.s\[11:0\]:** Upper-left s coordinate (u12.0) | | bit 43:32 | **upper\_left.t\[11:0\]:** Upper-left t coordinate (u12.0) | | bit 26:24 | **tile\[2:0\]:** Tile descriptor index | | bit 23:12 | **lower\_right.s\[11:0\]:** Lower-right s coordinate (u12.0) | | bit 11:0 | **dxt\[11:0\]:** Change in x per t (u1.11) | Load Block loads texels to the TMEM address specified by the selected tile descriptor from the RDRAM location pointed to by the current texture image (see **Set Texture Image**). Only 2048 texels of any size can be loaded at once, loads configured to read more than 2048 texels will fail resulting in nothing being written into TMEM. For 4 and 8 bit types the texture image texel size can be set to 16-bit to facilitate loading more than 2048 texels, it is not possible to use the 32-bit texel size in this way without advance preparation as 32-bit texels have a different TMEM layout. `lower_right.s - upper_left.s` determines the number of texels to load. Load Block is the fastest way to move data from RDRAM into TMEM, however not all width/height combinations are valid. Texture lines loaded with Load Block must be a multiple of 64 bits in size, either naturally or by explicitly padding each line in RDRAM. Further, for a given width there is a maximum height based on imprecision in the dxt value. For each 64-bit TMEM word loaded with Load Block, an internal counter is incremented by dxt. This counter can be thought of as a 1.11 fixed-point value; when the counter holds a value greater than one the current word being loaded belongs to an odd-numbered line, otherwise it belongs to an even-numbered line. For texture sampling reasons odd lines must have their 32-bit words swapped when loaded from RDRAM, this is done automatically by the loading hardware hence why it tracks which lines are even or odd. However if dxt is sufficiently imprecise the counter may miss a word at the end of a line, leading to corruptions as that word will not be swapped correctly. Like **Load Tile**, both `upper_left` parameters offset the RDRAM base address of the data to be loaded into the tile TMEM address. **Hazards** * Load Block sets the tile size for the associated tile descriptor to (upper\_left.s, upper\_left.t, lower\_right.s, dxt). This is generally not a useful configuration for rendering the image. * Loads that extend past the end of TMEM will wrap back to the start. * The texel size specified in **Set Texture Image** should match the texel size in the tile descriptor used for loading, otherwise operation is not guaranteed. * Some texture image addresses may hang the RDP if an attempt is made to load from it. See **Set Texture Image** hazards. ### 0x34 - Load Tile * * * | Load Tile `0x34` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x34\[5:0\] | | | | | | upper\_left.s\[11:4\] | | | | | | | | | 47:32 | upper\_left.s\[3:0\] | | | | upper\_left.t\[11:0\] | | | | | | | | | | | | | 31:16 | — | — | — | — | — | tile\[2:0\] | | | lower\_right.s\[11:4\] | | | | | | | | | 15:0 | lower\_right.s\[3:0\] | | | | lower\_right.t\[11:0\] | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x34 | | bit 55:44 | **upper\_left.s\[11:0\]:** Upper-left s coordinate (u10.2) | | bit 43:32 | **upper\_left.t\[11:0\]:** Upper-left t coordinate (u10.2) | | bit 26:24 | **tile\[2:0\]:** Tile descriptor index | | bit 23:12 | **lower\_right.s\[11:0\]:** Lower-right s coordinate (u10.2) | | bit 11:0 | **lower\_right.t\[11:0\]:** Lower-right t coordinate (u10.2) | Load Tile loads an arbitrary rectangle of texels to the TMEM address specified by the selected tile descriptor from the RDRAM location pointed to by the current texture image (see **Set Texture Image**). The rectangle in RDRAM to load from is specified by the provided upper\_left and lower\_right coordinates and the line parameter in the tile descriptor, the RDRAM width is `lower_right.s - upper_left.s` while the TMEM width is the tile line parameter. The upper\_left location in RDRAM is mapped to the tile TMEM address. Load Tile is slower than Load Block, however it is more flexible as there is no need to explicitly pad texture lines in RDRAM to 8-byte boundaries, and there are no issues with word-swapping. Load Tile updates the tile size for the selected tile descriptor to `(upper_left.s, upper_left.t, lower_right.s, lower_right.t)` which may be used for subsequent rendering. **Hazards** * The width configured in **Set Texture Image** should match the RDRAM width configured in the tile descriptor, otherwise operation is not guaranteed. * Loads that extend past the end of TMEM will wrap back to the start. * The texel size specified in **Set Texture Image** should match the texel size in the tile descriptor used for loading, otherwise operation is not guaranteed. * Some texture image addresses may hang the RDP if an attempt is made to load from it. See **Set Texture Image** hazards. ### 0x35 - Set Tile * * * | Set Tile `0x35` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x35\[5:0\] | | | | | | format\[2:0\] | | | size\[1:0\] | | — | line\[8:7\] | | | 47:32 | line\[6:0\] | | | | | | | address\[8:0\] | | | | | | | | | | 31:16 | — | — | — | — | — | index\[2:0\] | | | palette\[3:0\] | | | | clamp\_T | mirror\_T | mask\_T\[3:2\] | | | 15:0 | mask\_T\[1:0\] | | shift\_T\[3:0\] | | | | clamp\_S | mirror\_S | mask\_S\[3:0\] | | | | shift\_S\[3:0\] | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x35 | | bit 55:53 | **format\[2:0\]:** Tile texel format | | bit 52:51 | **size\[1:0\]:** Tile texel size | | bit 49:41 | **line\[8:0\]:** Tile line length in TMEM words | | bit 40:32 | **address\[8:0\]:** TMEM address in TMEM words | | bit 26:24 | **index\[2:0\]:** Tile index | | bit 23:20 | **palette\[3:0\]:** Palette index | | bit 19 | **clamp\_T:** Clamp enable (T-axis) | | bit 18 | **mirror\_T:** Mirror enable (T-axis) | | bit 17:14 | **mask\_T\[3:0\]:** Mask (T-axis) | | bit 13:10 | **shift\_T\[3:0\]:** Shift (T-axis) | | bit 9 | **clamp\_S:** Clamp enable (S-axis) | | bit 8 | **mirror\_S:** Mirror enable (S-axis) | | bit 7:4 | **mask\_S\[3:0\]:** Mask (S-axis) | | bit 3:0 | **shift\_S\[3:0\]:** Shift (S-axis) | Configures the tile descriptor specified by the index parameter. The texel **format** parameter is selected from | Format Name | Format Value | | --- | --- | | RGBA | 0 | | YUV | 1 | | Color-Indexed (CI) | 2 | | Intensity-Alpha (IA) | 3 | | Intensity (I) | 4+ | The texel **size** parameter is selected from | Bits per pixel | Size Value | | --- | --- | | 4 | 0 | | 8 | 1 | | 16 | 2 | | 32 | 3 | Officially supported format/size combinations | | RGBA | YUV | CI | IA | I | | --- | --- | --- | --- | --- | --- | | **4** | \- | \- | Y | Y | Y | | **8** | \- | \- | Y | Y | Y | | **16** | Y | Y | \- | Y | \- | | **32** | Y | \- | \- | \- | \- | The **line** parameter indicates the number of TMEM (64-bit) words in a single row of the tile. This quantity is typically calculated from the tile width in texels and texel size as `(width * NBITS(size) + 63) / 64` where `NBITS(size)` is the number of bits per texel. If a texture width is not an integral number of TMEM words, each row must be padded to begin on a new TMEM word; Load Tile performs this padding transparently when loading texture data from RDRAM into TMEM, for Load Block the data must already be padded in RDRAM. The **address** is a TMEM word (64-bit) address. For example an address of 0x100 resolves to byte address 0x800, half way through TMEM. The **palette** parameter is for CI4 formatted tiles, for CI formats of other sizes it is ignored altogether. It is the upper half of a TMEM address, indicating where the tile can find the associated color palette. The lower half of the TMEM address is populated by the color index in the CI4 texture data. The **clamp/mirror/mask/shift** parameters for each texture coordinate axis influence how texels are sampled from the tile during texture sampling. (TODO link texture sampling article) * **clamp**: Enables clamping. When the tile is sampled with coordinates that fall outside the tile boundaries, the nearest texel that is within the tile bounds will be used. If disabled, coordinates are brought into the tile range by wrapping based on the mask value, which operates on powers of 2. Therefore, if a tile extent is not a power of 2 clamping should typically be used. * **mirror**: Enables mirroring. When oversampling the tile, flip the axis. * **mask**: Determines how many bits of the integer part of the texture coordinate should be considered for sampling from this tile. A mask of 0 indicates all bits should be considered. * **shift**: Optional shift amount for texture coordinates. Values 1-10 shift right while 11-15 shift left. | Shift value | Operation | | --- | --- | | 0 | N/A | | 1 | \>> 1 | | 2 | \>> 2 | | ... | | | 10 | \>> 10 | | 11 | << 5 | | 12 | << 4 | | 13 | << 3 | | 14 | << 2 | | 15 | << 1 | ### 0x36 - Fill Rectangle * * * | Fill Rectangle `0x36` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x36\[5:0\] | | | | | | lower\_right.x\[11:4\] | | | | | | | | | 47:32 | lower\_right.x\[3:0\] | | | | lower\_right.y\[11:0\] | | | | | | | | | | | | | 31:16 | — | — | — | — | — | — | — | — | upper\_left.x\[11:4\] | | | | | | | | | 15:0 | upper\_left.x\[3:0\] | | | | upper\_left.y\[11:0\] | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x36 | | bit 55:44 | **lower\_right.x\[11:0\]:** Lower-right x coordinate (u10.2 format) | | bit 43:32 | **lower\_right.y\[11:0\]:** Lower-right y coordinate (u10.2 format) | | bit 23:12 | **upper\_left.x\[11:0\]:** Upper-left x coordinate (u10.2 format) | | bit 11:0 | **upper\_left.y\[11:0\]:** Upper-right y coordinate (u10.2 format) | Renders a solid-color rectangle. In FILL and COPY mode, the rectangle coordinates are inclusive on the right and lower edges while in 1-Cycle and 2-Cycle mode they are exclusive. In 1-Cycle or 2-Cycle mode, the rectangle is rendered with subpixel accuracy and can be anti-aliased if pixels are only partially covered. The color is determined by the blender output as with triangles and texture rectangles, not the fill color register. In FILL mode, the rectangle is rendered without subpixel accuracy (upper-left coordinates are rounded down, lower-right coordinates are rounded up) and the color is determined solely by the fill color register. Internally, this command is equivalent to a left-major triangle where `dxhdy = dxmdy = dxldy = 0, yl = ym = lry, yh = uly, xl = xm = lrx, xh = ulx` **Hazards** * In 1-Cycle and 2-Cycle mode using attributes such as shade color, texture coordinates and per-pixel depth may be ill-defined. (TOVERIFY: It's either always 0 or uses the last value from previous primitives, check which) * In COPY mode, fill rectangle behaves like texture rectangle with all texture attributes (tile, s, t, dsdx, dtdy) set to 0. (TOVERIFY: as above, is it always 0 or is it left over values from previous primitives?) ### 0x37 - Set Fill Color * * * | Set Fill Color `0x37` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x37\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | color\[31:16\] | | | | | | | | | | | | | | | | | 15:0 | color\[15:0\] | | | | | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x37 | | bit 31:0 | **color\[31:0\]:** Fill Color. Format varies based on color image pixel size, see below | Sets the fill color register, the color to use when rendering primitives in FILL mode. This is the only color sampled by FILL mode as all other pipeline stages are bypassed. This color is not made available to the Color Combiner or the Blender, and Fill Rectangle outside of FILL mode does not use it. Since FILL mode simply repeats the 32-bit value verbatim out to memory, proper usage of the color parameter will depend on the color image configuration: * 32-bit: A single RGBA32 color occupying all 32 bits. * 16-bit: Two RGBA16 colors occupying the upper and lower 16 bits respectively. Even pixels sample the upper half and odd pixels sample the lower half. For filling the color image with a solid color, simply let the upper and lower halves hold the same value. * 8-bit: Four 8-bit intensity values, repeating every four pixels. * 4-bit: N/A, crash **Hazards** * This is an attribute-setting command that requires pipeline synchronization before use, otherwise currently rendering primitives may be partially rendered using both the old fill color and the new fill color, if it was used. ### 0x38 - Set Fog Color * * * | Set Fog Color `0x38` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x38\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | red\[7:0\] | | | | | | | | green\[7:0\] | | | | | | | | | 15:0 | blue\[7:0\] | | | | | | | | alpha\[7:0\] | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x38 | | bit 31:24 | **red\[7:0\]:** Fog Red Value | | bit 23:16 | **green\[7:0\]:** Fog Green Value | | bit 15:8 | **blue\[7:0\]:** Fog Blue Value | | bit 7:0 | **alpha\[7:0\]:** Fog Alpha Value | Sets the "Fog" color register to the provided RGBA value. This color is accessible from the Blender as an input. The name "Fog" reflects only one particular usage of how the color may be used, the name does not relate to any particular meaning in the hardware. **Hazards** * This is an attribute-setting command that requires pipeline synchronization before use, otherwise currently rendering primitives may be partially rendered using both the old fog color and the new fog color, if it was used. ### 0x39 - Set Blend Color * * * | Set Blend Color `0x39` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x39\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | red\[7:0\] | | | | | | | | green\[7:0\] | | | | | | | | | 15:0 | blue\[7:0\] | | | | | | | | alpha\[7:0\] | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x39 | | bit 31:24 | **red\[7:0\]:** Blend Red Value | | bit 23:16 | **green\[7:0\]:** Blend Green Value | | bit 15:8 | **blue\[7:0\]:** Blend Blue Value | | bit 7:0 | **alpha\[7:0\]:** Blend Alpha Value | Sets the "Blend" color register to the provided RGBA value. This color is accessible from the Blender as an input. The name "Blend" reflects only one particular usage of how the color may be used, the name does not relate to any particular meaning in the hardware. **Hazards** * This is an attribute-setting command that requires pipeline synchronization before use, otherwise currently rendering primitives may be partially rendered using both the old blend color and the new blend color, if it was used. ### 0x3A - Set Primitive Color * * * | Set Primitive Color `0x3a` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x3a\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | min\_level\[7:0\] | | | | | | | | prim\_lod\_frac\[7:0\] | | | | | | | | | 31:16 | red\[7:0\] | | | | | | | | green\[7:0\] | | | | | | | | | 15:0 | blue\[7:0\] | | | | | | | | alpha\[7:0\] | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x3a | | bit 47:40 | **min\_level\[7:0\]:** Minimum LOD level | | bit 39:32 | **prim\_lod\_frac\[7:0\]:** Primitive LOD Fraction Color Combiner input | | bit 31:24 | **red\[7:0\]:** Primitive Red Value | | bit 23:16 | **green\[7:0\]:** Primitive Green Value | | bit 15:8 | **blue\[7:0\]:** Primitive Blue Value | | bit 7:0 | **alpha\[7:0\]:** Primitive Alpha Value | Sets the "Primitive" color register to the provided RGBA value, and some LOD parameters. The color is accessible from the Color Combiner as an input. The name "Primitive" reflects only one particular usage of how the color may be used, the name does not relate to any particular meaning in the hardware. **prim\_lod\_frac** is simply another Color Combiner input, despite the name it is not used by the texture LOD hardware feature. It may be used as a fixed interpolation factor. **min\_level** is used in the texture LOD hardware, it bounds the LOD tile selection from below. The LOD value selecting a particular tile **N** can be computed by L \= 32 ⋅ 2 ( N − T b a s e ) {\\displaystyle {\\displaystyle L=32\\cdot 2^{(N-T\_{\\mathrm {base} })}}}   where **Tbase** is the tile number set in the triangle command. For texture rectangles this is tile 0. If the LOD computed for a particular pixel during texture sampling is less than the minimum LOD level, tile 0 is used. Detail and sharpen modes may still apply. (TODO link to more detailed Texture LOD article) Unlike the majority of other attribute-setters, primitive color operates correctly even in the absence of pipeline synchronizations. The primitive color may be changed between primitive rendering without corrupting prior primitives. ### 0x3B - Set Environment Color * * * | Set Environment Color `0x3b` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x3b\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | red\[7:0\] | | | | | | | | green\[7:0\] | | | | | | | | | 15:0 | blue\[7:0\] | | | | | | | | alpha\[7:0\] | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x3b | | bit 31:24 | **red\[7:0\]:** Environment Red value | | bit 23:16 | **green\[7:0\]:** Environment Green value | | bit 15:8 | **blue\[7:0\]:** Environment Blue value | | bit 7:0 | **alpha\[7:0\]:** Environment Alpha value | Sets the "Environment" color register to the provided RGBA value. This color is accessible from the Color Combiner as an input. The name "Environment" reflects only one particular usage of how the color may be used, the name does not relate to any particular meaning in the hardware. **Hazards** * This is an attribute-setting command that requires pipeline synchronization before use, otherwise currently rendering primitives may be partially rendered using both the old environment color and the new environment color, if it was used. ### 0x3C - Set Combine Mode * * * | Set Combine Mode `0x3c` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x3c\[5:0\] | | | | | | RGB\_A\_0\[3:0\] | | | | RGB\_C\_0\[4:1\] | | | | | 47:32 | RGB\_C\_0\[0\] | Alpha\_A\_0\[2:0\] | | | Alpha\_C\_0\[2:0\] | | | RGB\_A\_1\[3:0\] | | | | RGB\_C\_1\[4:0\] | | | | | | 31:16 | RGB\_B\_0\[3:0\] | | | | RGB\_B\_1\[3:0\] | | | | Alpha\_A\_1\[2:0\] | | | Alpha\_C\_1\[2:0\] | | | RGB\_D\_0\[2:1\] | | | 15:0 | RGB\_D\_0\[0\] | Alpha\_B\_0\[2:0\] | | | Alpha\_D\_0\[2:0\] | | | RGB\_D\_1\[2:0\] | | | Alpha\_B\_1\[2:0\] | | | Alpha\_D\_1\[2:0\] | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x3c | | bit 55:52 | **RGB\_A\_0\[3:0\]:** RGB A Input (First Cycle) | | bit 51:47 | **RGB\_C\_0\[4:0\]:** RGB C Input (First Cycle) | | bit 46:44 | **Alpha\_A\_0\[2:0\]:** Alpha A Input (First Cycle) | | bit 43:41 | **Alpha\_C\_0\[2:0\]:** Alpha C Input (First Cycle) | | bit 40:37 | **RGB\_A\_1\[3:0\]:** RGB A Input (Second Cycle) | | bit 36:32 | **RGB\_C\_1\[4:0\]:** RGB C Input (Second Cycle) | | bit 31:28 | **RGB\_B\_0\[3:0\]:** RGB B Input (First Cycle) | | bit 27:24 | **RGB\_B\_1\[3:0\]:** RGB B Input (Second Cycle) | | bit 23:21 | **Alpha\_A\_1\[2:0\]:** Alpha A Input (Second Cycle) | | bit 20:18 | **Alpha\_C\_1\[2:0\]:** Alpha C Input (Second Cycle) | | bit 17:15 | **RGB\_D\_0\[2:0\]:** RGB D Input (First Cycle) | | bit 14:12 | **Alpha\_B\_0\[2:0\]:** Alpha B Input (First Cycle) | | bit 11:9 | **Alpha\_D\_0\[2:0\]:** Alpha D Input (First Cycle) | | bit 8:6 | **RGB\_D\_1\[2:0\]:** RGB D Input (Second Cycle) | | bit 5:3 | **Alpha\_B\_1\[2:0\]:** Alpha B Input (Second Cycle) | | bit 2:0 | **Alpha\_D\_1\[2:0\]:** Alpha D Input (Second Cycle) | Configures the inputs to the Color Combiner pipeline stage. The Color Combiner stage computes the following equation ( A − B ) ⋅ C + D {\\displaystyle {\\displaystyle (A-B)\\cdot C+D}}   This equation can be configured differently for * Cycle 1 RGB * Cycle 1 Alpha * Cycle 2 RGB * Cycle 2 Alpha In 1-Cycle mode only the Cycle 2 configuration is computed, while in 2-Cycle mode both are computed and the output from Cycle 1 can be used as an input to Cycle 2. Besides some inputs behaving differently the second cycle of 2-Cycle mode and the only cycle of 1-Cycle mode behave identically, the first cycle of 2-Cycle mode is greatly simplified with no clamping occurring between combiner cycles. **Inputs for A (RGB)** | Value | Name | Description | | --- | --- | --- | | 0 | COMBINED | Combined color from first cycle | | 1 | TEX0 | Texture color sampled from the tile set in the primitive command, after texture LOD if enabled | | 2 | TEX1 | Texture color sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) | | 3 | PRIMITIVE | Primitive color register (rgb) | | 4 | SHADE | Shade color interpolated per-pixel from shade coefficients | | 5 | ENVIRONMENT | Environment color register (rgb) | | 6 | 1 | Fixed 1 | | 7 | NOISE | Per-pixel noise. This is a 9-bit value whose top 3 bits are [random](https://github.com/Thar0/RDP-Noise)
, while the bottom 6 are fixed to 0b100000 (0x20). | | 8+ | 0 | Fixed 0 | **Inputs for A (Alpha)** | Value | Name | Description | | --- | --- | --- | | 0 | COMBINED | Combined alpha from first cycle | | 1 | TEX0 | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled | | 2 | TEX1 | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) | | 3 | PRIMITIVE | Primitive color register (alpha) | | 4 | SHADE | Shade alpha interpolated per-pixel from shade coefficients | | 5 | ENVIRONMENT | Environment color register (alpha) | | 6 | 1 | Fixed 1 | | 7 | 0 | Fixed 0 | **Inputs for B (RGB)** | Value | Name | Description | | --- | --- | --- | | 0 | COMBINED | Combined color from first cycle | | 1 | TEX0 | Texture color sampled from the tile set in the primitive command, after texture LOD if enabled | | 2 | TEX1 | Texture color sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) | | 3 | PRIMITIVE | Primitive color register (rgb) | | 4 | SHADE | Shade color interpolated per-pixel from shade coefficients | | 5 | ENVIRONMENT | Environment color register (rgb) | | 6 | CENTER | Chroma key center (see **Set Key R** and **Set Key GB**) | | 7 | K4 | K4 value (see **Set Convert**) | | 8+ | 0 | Fixed 0 | **Inputs for B (Alpha)** | Value | Name | Description | | --- | --- | --- | | 0 | COMBINED | Combined alpha from first cycle | | 1 | TEX0 | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled | | 2 | TEX1 | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) | | 3 | PRIMITIVE | Primitive color register (alpha) | | 4 | SHADE | Shade alpha interpolated per-pixel from shade coefficients | | 5 | ENVIRONMENT | Environment color register (alpha) | | 6 | 1 | Fixed 1 | | 7 | 0 | Fixed 0 | **Inputs for C (RGB)** | Value | Name | Description | | --- | --- | --- | | 0 | COMBINED | Combined color from first cycle | | 1 | TEX0 | Texture color sampled from the tile set in the primitive command, after texture LOD if enabled | | 2 | TEX1 | Texture color sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) | | 3 | PRIMITIVE | Primitive color register (rgb) | | 4 | SHADE | Shade color interpolated per-pixel from shade coefficients | | 5 | ENVIRONMENT | Environment color register (rgb) | | 6 | SCALE | Chroma key scale (see **Set Key R** and **Set Key GB**) | | 7 | COMBINED\_ALPHA | Combined alpha from first cycle | | 8 | TEX0\_ALPHA | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled | | 9 | TEX1\_ALPHA | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled, plus one | | 10 | PRIMITIVE\_ALPHA | Primitive color register (alpha) | | 11 | SHADE\_ALPHA | Shade alpha interpolated per-pixel from shade coefficients | | 12 | ENVIRONMENT\_ALPHA | Environment color register (alpha) | | 13 | LOD\_FRACTION | LOD Fraction computed as part of Texture LOD | | 14 | PRIM\_LOD\_FRAC | Primitive LOD Fraction (see **Set Primitive Color**) | | 15 | K5 | K5 value (see **Set Convert**) | | 16+ | 0 | Fixed 0 | **Inputs for C (Alpha)** | Value | Name | Description | | --- | --- | --- | | 0 | LOD\_FRACTION | LOD Fraction computed as part of Texture LOD | | 1 | TEX0 | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled | | 2 | TEX1 | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) | | 3 | PRIMITIVE | Primitive color register (alpha) | | 4 | SHADE | Shade alpha interpolated per-pixel from shade coefficients | | 5 | ENVIRONMENT | Environment color register (alpha) | | 6 | PRIM\_LOD\_FRAC | Primitive LOD Fraction (see **Set Primitive Color**) | | 7 | 0 | Fixed 0 | **Inputs for D (RGB)** | Value | Name | Description | | --- | --- | --- | | 0 | COMBINED | Combined color from first cycle | | 1 | TEX0 | Texture color sampled from the tile set in the primitive command, after texture LOD if enabled | | 2 | TEX1 | Texture color sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) | | 3 | PRIMITIVE | Primitive color register (rgb) | | 4 | SHADE | Shade color interpolated per-pixel from shade coefficients | | 5 | ENVIRONMENT | Environment color register (rgb) | | 6 | 1 | Fixed 1 | | 7 | 0 | Fixed 0 | **Inputs for D (Alpha)** | Value | Name | Description | | --- | --- | --- | | 0 | COMBINED | Combined alpha from first cycle | | 1 | TEX0 | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled | | 2 | TEX1 | Texture alpha sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) | | 3 | PRIMITIVE | Primitive color register (alpha) | | 4 | SHADE | Shade alpha interpolated per-pixel from shade coefficients | | 5 | ENVIRONMENT | Environment color register (alpha) | | 6 | 1 | Fixed 1 | | 7 | 0 | Fixed 0 | **Hazards** * This is a pipeline configuration command that requires pipeline synchronization before use, otherwise currently rendering primitives may be partially rendered using both the old combiner configuration and the new combiner configuration. * In 1-Cycle mode only the second cycle configuration is used, the first cycle configuration is ignored. * COMBINED in the first cycle (or in 1-Cycle mode) is ill-defined. It will (generally) read the previous pixel combiner output. ([RH#003](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#RH#003 "Reality Display Processor/Hazards") ) * If using the SHADE input the primitive must have provided shade coefficients. The SHADE input is therefore always invalid for rectangle commands as there is no option for providing shade coefficients for those. ([RH#004](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#RH#004 "Reality Display Processor/Hazards") ) * If using the TEX0/1 inputs the primitive must have provided texture coordinates and a tile number. Fill Rectangles cannot provide either of these so these options are always invalid for those. * If using the LOD\_FRACTION input, Texture LOD must have been enabled in othermodes. * In 1-Cycle mode, LOD\_FRACTION is not valid. (TOVERIFY in what way is it not valid? Does the 1-Cycle pipeline bypass LOD altogether, so does not update this input?) * In 1-Cycle mode, TEX1 reads next pixel TEX0 (with some ambiguity at scanline edges) ([RH#001](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#RH#001 "Reality Display Processor/Hazards") ) * In 2-Cycle mode there is a pipeline bug involving TEX0 and TEX1. In the second cycle of 2-Cycle mode, TEX0 refers to TEX1 and TEX1 refers to next pixel TEX0 (with some ambiguity at scanline edges) ([RH#002](https://n64brew.dev/wiki/Reality_Display_Processor/Hazards#RH#002 "Reality Display Processor/Hazards") ) * The color combiner is quick to overflow. Values between 256 and 383 saturate to 255 while 384 to 511 overflow to 0. Values greater than or equal to 512 overflow the 9-bit color completely. * Sign-extension properties differ between the A,B,D and C inputs. C is more prone to overflow, it is advisable to keep the COMBINED input out of the C input as much as possible. * The 1 input is valued at 256, not 255. This may contribute to overflow issues. ### 0x3D - Set Texture Image * * * | Set Texture Image `0x3d` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x3d\[5:0\] | | | | | | format\[2:0\] | | | size\[1:0\] | | — | — | — | | 47:32 | — | — | — | — | — | — | width\[9:0\] | | | | | | | | | | | 31:16 | — | — | — | — | — | — | — | — | dramAddress\[23:16\] | | | | | | | | | 15:0 | dramAddress\[15:0\] | | | | | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x3d | | bit 55:53 | **format\[2:0\]:** Texture image format | | bit 52:51 | **size\[1:0\]:** Texture image pixel size | | bit 41:32 | **width\[9:0\]:** Texture image width in pixels (-1) (possible range of 1 to 4096 pixels) | | bit 23:0 | **dramAddress\[23:0\]:** RDRAM physical address of the buffer containing texture data | Sets the texture image, the location in RDRAM where the texture loading pipeline should source image data, and related properties. The texture image format parameter seems to have no function, it has no effect on any operation unlike the tile format. | Format Name | Format Value | | --- | --- | | RGBA | 0 | | YUV | 1 | | Color-Indexed (CI) | 2 | | Intensity-Alpha (IA) | 3 | | Intensity (I) | 4+ | The size parameter is used to increment the RDRAM address during texture loading. | Bits per pixel | Size Value | | --- | --- | | 4 | 0 | | 8 | 1 | | 16 | 2 | | 32 | 3 | The width parameter is used to calculate the start address of each texture image row during loading. This is an attribute-setting command but is not used in the rendering pipeline so does not require a pipeline synchronization before changing it. (TOVERIFY is a sync required if changing it following a load command?) **Hazards** * Memory alignment should be 8-byte to guarantee operation. If the address falls into the range \[1,7\] mod 64, the RDP may hang when loading from it. ### 0x3E - Set Depth Image * * * | Set Depth Image `0x3e` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x3e\[5:0\] | | | | | | — | — | — | — | — | — | — | — | | 47:32 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | 31:16 | — | — | — | — | — | — | — | — | dramAddress\[23:16\] | | | | | | | | | 15:0 | dramAddress\[15:0\] | | | | | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x3e | | bit 23:0 | **dramAddress\[23:0\]:** RDRAM physical address of the buffer to store depth information | Sets the depth image, the region in RDRAM where depth information will be stored. The depth image width is the same as the image width for the current color image. The depth buffer format is a fixed 18-bit-per-pixel format irrespective of the color image format. **Hazards** * This attribute must be set before rendering any primitives that read or write depth, else it will read/write depth data at an unspecified location. * Memory alignment must be 64-byte for all rendering operations to behave as expected. ### 0x3F - Set Color Image * * * | Set Color Image `0x3f` | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 63:48 | — | — | command = 0x3f\[5:0\] | | | | | | format\[2:0\] | | | size\[1:0\] | | — | — | — | | 47:32 | — | — | — | — | — | — | width\[9:0\] | | | | | | | | | | | 31:16 | — | — | — | — | — | — | — | — | dramAddress\[23:16\] | | | | | | | | | 15:0 | dramAddress\[15:0\] | | | | | | | | | | | | | | | | | | | | --- | --- | | bit 61:56 | **command\[5:0\]:** 0x3f | | bit 55:53 | **format\[2:0\]:** Pixel format (same meaning as in texture format) | | bit 52:51 | **size\[1:0\]:** Pixel size (same meaning as in texture size) | | bit 41:32 | **width\[9:0\]:** Image width in pixels (-1) (possible range of 1 to 4096 pixels) | | bit 23:0 | **dramAddress\[23:0\]:** RDRAM physical address of the buffer where pixels will be written | Sets the color image address and properties, the region in RDRAM where primitives will be rendered to as pixels. The image is arrayed in row-order, that is rows are adjacent in memory. It is generally more efficient to draw wide triangles rather than horizontally thin triangles that overlap many rows; for single rows span buffers are employed to alleviate stalls due to waiting on memory read/write requests while there is no such buffering across multiple rows. Valid format/size combinations are RGBA32, RGBA16, I8. 4-bit modes only write 0s as bytes, all 8-bit modes behave identically, 16-bit and 32-bit RGBA modes differ from other modes. **Hazards** * This attribute must be set before rendering any primitives, else it will render to an unspecified location. * This is an attribute-setting command that requires pipeline synchronization before use, otherwise currently rendering primitives may be split partially between the old color image and new color image. (TOVERIFY there are conflicting accounts of whether Set Color Image requires a sync, the current consensus is that sync is required to avoid all edge cases) * Memory alignment must be 64-byte for all rendering operations to behave as expected. * Rendering any primitive in FILL mode to a 4-bit color image will crash the RDP. * Copying a 4-bit or 8-bit texture with COPY mode is only possible if the destination color image is 8-bit. * Copying a 16-bit texture with COPY mode is only possible if the destination color image is 16-bit. (Note that CI textures are treated as 16-bit in COPY mode as the TLUT is 16-bit) * COPY mode is unavailable when a 32-bit color image is configured. Retrieved from "[https://n64brew.dev/wiki/Reality\_Display\_Processor/Commands?oldid=5698](https://n64brew.dev/wiki/Reality_Display_Processor/Commands?oldid=5698) " --- # Serial Interface - N64brew Wiki [](https://n64brew.dev/wiki/SI#) Serial Interface ================ (Redirected from [SI](https://n64brew.dev/wiki/SI?redirect=no "SI") ) The Serial Interface (or **SI**) is one of multiple I/O interfaces in the RCP, which is used to communicate with the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") and in turn, [Joybus](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") devices. Memory mapped registers are used to configure the Serial Interface and initiate DMA reads and writes. The base address for these registers is `0x0480 0000`, also known as SI\_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 `0xA000 0000` to the address. As an example, to directly write to the SI\_DRAM\_ADDR register, use address `0xA480 0000`. In addition to these registers, SI is also in charge of handling the memory mapping of PIF-ROM and PIF-RAM to VR4300. These memories are mapped at physical address `0x1FC0 0000` (and normally accessed via the uncached segment at `0xBFC0 0000`). Contents -------- * [1 Mapped PIF-ROM and PIF-RAM](https://n64brew.dev/wiki/SI#Mapped_PIF-ROM_and_PIF-RAM) * [2 DMA transfers](https://n64brew.dev/wiki/SI#DMA_transfers) * [3 Communication protocol with PIF-NUS](https://n64brew.dev/wiki/SI#Communication_protocol_with_PIF-NUS) * [4 Registers](https://n64brew.dev/wiki/SI#Registers) * [4.1 0x0480 0000 - SI\_DRAM\_ADDR](https://n64brew.dev/wiki/SI#0x0480_0000_-_SI_DRAM_ADDR) * [4.2 0x0480 0004 - SI\_PIF\_AD\_RD64B](https://n64brew.dev/wiki/SI#0x0480_0004_-_SI_PIF_AD_RD64B) * [4.3 0x0480 0008 - SI\_PIF\_AD\_WR4B](https://n64brew.dev/wiki/SI#0x0480_0008_-_SI_PIF_AD_WR4B) * [4.4 0x0480 0010 - SI\_PIF\_AD\_WR64B](https://n64brew.dev/wiki/SI#0x0480_0010_-_SI_PIF_AD_WR64B) * [4.5 0x0480 0014 - SI\_PIF\_AD\_RD4B](https://n64brew.dev/wiki/SI#0x0480_0014_-_SI_PIF_AD_RD4B) * [4.6 0x0480 0018 - SI\_STATUS](https://n64brew.dev/wiki/SI#0x0480_0018_-_SI_STATUS) * [5 iQue Player](https://n64brew.dev/wiki/SI#iQue_Player) Mapped PIF-ROM and PIF-RAM -------------------------- When the VR4300 access the physical area at `0x1FC0 0000` - `0x1FCF FFFF`, RCP handles the request via SI; the memory access performed via standard MIPS opcode like `LW` or `SW` is converted into a I/O communication with PIF, using the serial bus. See [PIF-NUS#Internal ROMs and RAM](https://n64brew.dev/wiki/PIF-NUS#Internal_ROMs_and_RAM "PIF-NUS") for a description of the memories inside the PIF. The addresses are mapped as follows (and they mirror across the whole area): | | | | --- | --- | | 0x000 - 0x7BF | PIF-ROM. This area contains the IPL1/IPL2 boot code. VR4300 starts running from these addresses after a NMI. During the boot process, PIF-ROM is locked out for security reasons, and during normal runtime all reads from these addresses return 0. | | 0x7C0-0x7FF | PIF-RAM (64 bytes). This area is used to communicate with PIF, mostly to run the Joyous protocol to communicate with external controllers. | Notice that in general the SI is not aware of this memory map. For each access to the area, it will issue a read or write request (using the protocol detailed below) which includes the 11-bit address. It does not behave differently depending on the address (eg: writes to the ROM area are still issued). The SI serial protocol with PIF-NUS only allows to transfer 32-bit words (or 64-byte sequences, when a DMA transfer is requested), so it is advised for the VR4300 to access this memory mapped area only via 32-bit operations. The result obtained when using accesses of different size is detailed in [Memory map#Range 0x1FC0'0000 - 0x1FCF'FFFF (SI external bus)](https://n64brew.dev/wiki/Memory_map#Range_0x1FC0'0000_-_0x1FCF'FFFF_(SI_external_bus) "Memory map") . In general, read accesses are blocking, while write accesses are asynchronous. Read accesses while a write is in progress are correctly delayed and run at the end of the write. This is described in detail in [Memory map#Range 0x1FC0'0000 - 0x1FCF'FFFF (SI external bus)](https://n64brew.dev/wiki/Memory_map#Range_0x1FC0'0000_-_0x1FCF'FFFF_(SI_external_bus) "Memory map") . Direct writes to the memory mapped areas cause interrupts on the VR4300, exactly like DMA transfers. In fact, the two are mostly identical at the hardware level, including the fact that the flag DMA\_BUSY is also set. DMA transfers ------------- The SI allows to transfer the contains of the whole PIF-RAM (64 bytes) with a DMA transfer (both reads and writes). VR4300 can trigger these DMAs by writing to the registers `SI_PIF_AD_WR64B` and `SI_PIF_AD_RD64B` (see below). Notice that the 64-byte read transfer has a special: when the transfer is requested by the SI, the PIF firmware first runs the whole joyous handshake described in PIF-RAM, communicating with the attached device; then t updates the contents of PIF-RAM with the results, and only a this point the ACK is sent to the SI and the actual transfer is done with the updated values. This means that the 64-byte DMA read is usually much slower than expected because it does not just transfer the bytes, but must first wait for the PIF to communicates with all controllers as requested. Communication protocol with PIF-NUS -----------------------------------  [](https://n64brew.dev/wiki/File:SI_-_PIF_communication_protocol.gif) Visual representation of the protocol described in this paragraph The communication protocol with PIF-NUS is the low-level data encapsulation performed by the SI to communicate with PIF-NUS. There are 4 supported packets: * **RD4B** (Read 4 bytes): This packet is generated any time the VR4300 reads from the PIF mapped area. The SI sends on the bus the bits `11` to identify the packet, followed by bits 10..2 of the address to read (bits 1..0 are assumed to be always 0, that is the address is always 32-bit aligned). The PIF replies with an ACK followed by the 32-bit word that was contained in the memory (ROM or RAM) at the specified address. * **WR4B** (Write 4 bytes): This packet is generated any time the VR4300 writes to the PIF mapped area The SI sends on the bus the bits `10` to identify the packet, followed by bits 10..2 of the address to write (bits 1..0 are assumed to be always 0, that is the address is always 32-bit aligned). The PIF replies with an ACK, and at that point the SI sends the 32-bit word to be written to memory (RAM; writes to ROM are obviously ignored) at the specified address. * **RD64B** (Read 64 bytes): This packet is generated any time the VR4300 issues a DMA read transfer The SI sends on the bus the bits `01`to identify the packet, followed by bits 10..2 of the address to write (which would normally be `111110000`, which are bits 10..2 of `0x7C0`). When the PIF receives this packet, it does not immediately replies with the ACK: first, it runs the joybus handshake described in PIF-RAM, communicating with the various attached devices, and updates the PIF-RAM contents with the result. Only after this is done, the ACK is sent to the SI, followed by the 512 bits of PIF-RAM contents. * **WR64B** (Write 64 bytes). This packet is generated any time the VR4300 issues a DMA write transfer. The SI sends on the bus the bits `01`to identify the packet, followed by bits 10..2 of the address to write (which would normally be `111110000`, which are bits 10..2 of `0x7C0`). The PIF replies with an ACK, and at that point the SI sends the 512-bit sequence to be written to PIF-RAM. 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 #### 0x0480 0000 - SI\_DRAM\_ADDR * * * | SI\_DRAM\_ADDR `0x0480 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **DRAM\_ADDR\[23:0\]:** RDRAM address used in SI DMAs | #### 0x0480 0004 - SI\_PIF\_AD\_RD64B * * * | SI\_PIF\_AD\_RD64B `0x0480 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | — | PIF\_ADDR\[10:8\] | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | R-0 | | PIF\_ADDR\[7:2\] | | | | | | 0 | 0 | | | | | --- | --- | | bit 31-11 | **Undefined:** Initialized to `0` | | bit 10-0 | **PIF\_ADDR:** Offset in PIF\_RAM/PIF\_ROM where to fetch data | Writing to this register triggers a SI DMA transfer from PIF to RDRAM. The RDRAM address is the one stored in SI\_DRAM\_ADDR, while the address within PIF\_ROM/PIF\_RAM must be written to this register. Notice that the lowest two bits of the PIF address are fixed to zero, so only aligned transfers can be run. This transfer is done by sending a RD64B serial packet to PIF, waiting for acknowledge and then writing to RDRAM the data sent back via serial. Notice that this command has a special meaning for PIF: if the PIF\_RAM command byte has either bit 0 or bit 1 set, the respective commands will first be executed (both of them will write to PIF\_RAM), and then the requested data is transferred. So the transfer could take a while to run, because the SI might be waiting for the acknowledge for a long time. See PIF-NUS for more information about RD64B. In the normal case, VR4300 would have prepared a Joybus packet in PIF\_RAM to poll controllers. When the SI DMA read is run, the PIF will receive the RD64B packet and will actually perform the full joybus exchange with controllers, writing the state in PIF\_RAM. It will then send back the results to SI that will in turn write them to RDRAM. This means that the actual Joybus protocol is only executed when VR4300 asks to read the results via SI DMA, and the actual DMA will be delayed until the data is ready. During the DMA transfer, SI\_DRAM\_ADDR is updated. At the end of the transfer, it points to the last word in RDRAM that was written to. #### 0x0480 0008 - SI\_PIF\_AD\_WR4B * * * | SI\_PIF\_AD\_WR4B `0x0480 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[31:24\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-0 | **DATA:** 32-bit data to be transferred to PIF-RAM | This register is basically broken: it was probably meant to allow for a DMA transfer of 4 bytes to PIF\_RAM, but in reality it is just directly connected to an internal register of SI holding the "current data" word for PIF transfers. Curiously enough, writing to it does seem to trigger a WR4B serial packet to PIF, so it can actually be used to transfer a word, with the following sequence: * First, we need to populate the internal SI register that holds the "current address" to PIF. To do so, we can simply trigger a read from PIF\_RAM at the desired location. The read will be executed, and the internal SI register will hold that address. * Now, write a 32-bit word of data to \`SI\_PIF\_AD\_WR4B\`. This goes into the internal "current data" register of SI, and triggers a WR4B transfer using the "current address" (loaded with the previous trick) and the "current data", effectively writing the word to PIF RAM. The sequence triggers a non-blocking write, but also direct writes to the memory mapped area of PIF\_RAM are non-blocking, so there does not seem to be any reason of using this register. After writing to this register, the DMA\_BUSY bit in SI\_STATUS goes to 1 for a small amount of time, even though no actual DMA transfer is executed. #### 0x0480 0010 - SI\_PIF\_AD\_WR64B * * * SI\_PIF\_AD\_WR64B `0x0480 0010` **TODO** #### 0x0480 0014 - SI\_PIF\_AD\_RD4B * * * | SI\_PIF\_AD\_RD4B `0x0480 0014` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[31:24\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-0 | **DATA:** 32-bit data to be transferred to PIF-RAM | This register is very similar to SI\_PIF\_AD\_WR4B: it also is directly mapped to the internal "current data" word for PIF transfers, but writing to it triggers a RD4B serial packet to PIF. So a read is actually executed and the data is fetched into "current data" and is thus available for reading later. No DMA is performed though. Using a trick similar to that described in SI\_PIF\_AD\_WR4B, it is possible to actually triggers a memory read from a selected location, but it is superfluous since it is sufficient to access the memory mapped PIF-RAM to do the same. After writing to this register, the DMA\_BUSY bit in SI\_STATUS goes to 1 for a small amount of time, even though no actual DMA transfer is executed. #### 0x0480 0018 - SI\_STATUS * * * | SI\_STATUS `0x0480 0018` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | R-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | INTERRUPT | DMA\_STATE\[3:0\] | | | | | 7:0 | U-0 | U-0 | U-0 | U-0 | R-0 | R-0 | R-0 | R-0 | | PCH\_STATE\[3:0\] | | | | DMA\_ERROR | READ\_PENDING | IO\_BUSY | DMA\_BUSY | | | | | --- | --- | | bit 31-13 | **Undefined:** Initialized to `0` | | bit 12 | **INTERRUPT:** Copy of SI interrupt flag from [MIPS Interface](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0008_-_MI_INTERRUPT "MIPS Interface")
,
    Writing any value to SI\_STATUS acknowledges the interrupt.
    SI interrupts occur when a DMA or a direct write finishes. | | bit 11-8 | **DMA\_STATE\[3:0\]:** Internal DMA state. Non-zero values indicate activity. | | bit 7-4 | **PCH\_STATE\[3:0\]:** Internal PIF channel state. Non-zero values indicate activity. | | bit 3 | **DMA\_ERROR:** Set when overlapping DMA requests occur, or when writing to a misaligned address. Can only be cleared with a power reset. | | bit 2 | **READ\_PENDING:** Unknown? | | bit 1 | **IO\_BUSY:** Set when a direct memory write to PIF\_RAM is in progress. | | bit 0 | **DMA\_BUSY:** Set when a read or write DMA, or an IO write, is in progress. | iQue Player ----------- On the iQue Player, the Serial Interface was substantially reworked as the PIF-NUS was removed from the design; the DMA engine no longer transfers data to/from the PIF over a serial line. DMA writes from SDRAM to the SI simply acquires 32 bytes of data from SDRAM, processing of this data is deferred until DMA reads from the SI back to SDRAM similarly to N64. When a DMA read back to SDRAM is requested, the data supplied by the DMA write is processed and a response is fetched from joybus devices. The format of the data is stricter than on N64 and only properly supports small commands. Each joybus channel is allotted 8 bytes (effectively 7) in the data: \- The first byte of the first channel must be sent as 0xFF, otherwise the result will be meaningless (a buffer full of 0xFF). The first byte of the other channels is ignored and skipped over. - The second byte is the TX length as on N64, the lower 6 bits determine how many bytes the joybus device receives while bit 7 may behave the same as on n64, skipping the device. If the number of bytes to send is 0 the device receives nothing. - The third byte is the RX length as on N64, the lower 3 (TOVERIFY) bits determine how many bytes it should expect to receive from the joybus device. - The remaining 5 bytes contain data that may be sent to the joybus device. If the TX length is greater than 5, the last byte is repeated out to the device to fill the rest of the packet. When the data is DMA'd back to RAM, each channel contains: \- The first byte is always 0xFF, irrespective of what was there when the data was uploaded. - The second byte is the lower 6 bits of the input TX length. - The third byte is the lower 3 bits of the input RX length and bits 6 & 7 are error bits like on N64. - The fourth byte is always the fourth byte from the input data. - The remaining 4 bytes contain RX data received from the joybus device. If the amount of data received was less than 4 bytes, the remaining bytes are the same as the input data. Unlike on N64, it is not possible to upload a block of data to the SI and use it multiple times. It is also not necessary to set any bits in byte 63, since the DMA is just 32 bytes. Retrieved from "[https://n64brew.dev/wiki/Serial\_Interface?oldid=5663](https://n64brew.dev/wiki/Serial_Interface?oldid=5663) " --- # Reality Signal Processor - N64brew Wiki [](https://n64brew.dev/wiki/RSP#) Reality Signal Processor ======================== (Redirected from [RSP](https://n64brew.dev/wiki/RSP?redirect=no "RSP") ) The **Reality Signal Processor**, or **RSP**, is the portion of the RCP responsible for matrix math, lighting calculations, clipping, shading, and other highly parallel graphics tasks as well as audio processing. It is a programmable MIPS processor with a custom set of SIMD instructions for vectorized fixed point operations (exposed as COP2 -- a group of reserved instructions in the standard MIPS instruction set). The RSP is also able to directly drive the RDP (the hardware rasterizer) by accessing its registers, so that it can terminate the graphic pipeline by telling the RDP to draw triangles into the framebuffer. RSP has two different banks of onboard dedicated memories: IMEM (4KB) for instructions, and DMEM (4KB) for data. It has no external memory buses but has a DMA engine capable to copy code/data from/into DMEM/IMEM and the main RDRAM. The DMA engine can be driven by either the main CPU or the RSP itself. The code running on the RSP is usually called "microcode", but it's a standard MIPS program. The RSP can be programmed in custom microcode to handle specific tasks, though most commercial games leveraged one of several stock microcodes made available by Nintendo at the time. Contents -------- * [1 Specs](https://n64brew.dev/wiki/RSP#Specs) * [2 RSP CPU core](https://n64brew.dev/wiki/RSP#RSP_CPU_core) * [3 RSP CPU pipeline](https://n64brew.dev/wiki/RSP#RSP_CPU_pipeline) * [4 RSP interface](https://n64brew.dev/wiki/RSP#RSP_interface) Specs ----- | | | | --- | --- | | | Discription | | CPU Type | Cut down version of the MIPS4000 CPU | | Clock Speed | 62.5mhz | | Instruction Size | 32bit (1 Word) | | Dual Instruction | Yes (one scalar and one vector opcode at once) | | Pipeline Stages | 5 stage pipeline for both the Scalar and Vector Pipelines

IF, RD and WB stages are shared between the two pipelines | | IMEM Data Path | 64bit (This allows a dual instruction to happen) This can only be double word aligned for reads | | Scalar Register Size | 32 entries of 32bit in size (Word Writable) | | Vector Register Size | 32 entries of 128bit is size (8bit to 128bit Mask Writable File) | | DMEM Scalar Data Path | Up to 32bit Loads and Stores | | DMEM Vector Data Path | Up to 128bit Loads and Stores | | Scalar ALU Size | 32bit in side only | | Vector ALU Size | 8x 16bit vector ALU pipelines (48bit Final Accumulator) | RSP CPU core ------------ Main article: [Reality Signal Processor/CPU Core](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Core "Reality Signal Processor/CPU Core") The RSP CPU core is made by a stripped-down MIPS 32-bit core (without a few more advanced opcodes) referred to as Scalar Unit (SU), composed with a coprocessor (configured as COP2) that can perform SIMD operations on a separate set of vector registers, referred to as Vector Unit (VU). RSP CPU pipeline ---------------- Main article: [Reality Signal Processor/CPU Pipeline](https://n64brew.dev/wiki/Reality_Signal_Processor/CPU_Pipeline "Reality Signal Processor/CPU Pipeline") The RSP CPU pipeline is made of two different units: SU and VU. It allows to run two instructions in a single clock cycle, when following a specific coding pattern. RSP interface ------------- Main article: [Reality Signal Processor/Interface](https://n64brew.dev/wiki/Reality_Signal_Processor/Interface "Reality Signal Processor/Interface") The RSP interface is made of several memory-mapped registers and memory areas that allows the VR4300 to control the RSP. VR4300 is able to read and write to the internal IMEM/DMEM memory of the RSP to be able to upload the microcode to be run and fetch the results if required. Retrieved from "[https://n64brew.dev/wiki/Reality\_Signal\_Processor?oldid=4813](https://n64brew.dev/wiki/Reality_Signal_Processor?oldid=4813) " --- # Parallel Interface - N64brew Wiki [](https://n64brew.dev/wiki/Peripheral_Interface#) Parallel Interface ================== (Redirected from [Peripheral Interface](https://n64brew.dev/wiki/Peripheral_Interface?redirect=no "Peripheral Interface") ) The Parallel Interface (commonly referred to as the **PI**, or Peripheral Interface) is one of multiple I/O interfaces in the RCP, which is used to communicate with [game cartridges](https://n64brew.dev/wiki/Game_Pak "Game Pak") or other devices connected to either the cartridge port or expansion port on the bottom of the console. (e.g. 64DD) The PI is not to be confused with the [PIF](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") (or PIF-NUS) which is an entirely separate IC. Memory mapped registers are used to configure the Parallel Interface and initiate DMA reads and writes. The base address for these registers is `0x0460 0000`, 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 `0xA000 0000` to the address. As an example, to directly write to the PI\_DRAM\_ADDR register, use address `0xA460 0000`. Contents -------- * [1 The PI Bus](https://n64brew.dev/wiki/Peripheral_Interface#The_PI_Bus) * [1.1 Domains](https://n64brew.dev/wiki/Peripheral_Interface#Domains) * [1.2 Open bus behavior](https://n64brew.dev/wiki/Peripheral_Interface#Open_bus_behavior) * [2 Registers](https://n64brew.dev/wiki/Peripheral_Interface#Registers) * [2.1 0x0460 0000 - PI\_DRAM\_ADDR](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0000_-_PI_DRAM_ADDR) * [2.2 0x0460 0004 - PI\_CART\_ADDR](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0004_-_PI_CART_ADDR) * [2.3 0x0460 0008 - PI\_RD\_LEN](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0008_-_PI_RD_LEN) * [2.4 0x0460 000C - PI\_WR\_LEN](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_000C_-_PI_WR_LEN) * [2.5 0x0460 0010 - PI\_STATUS](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0010_-_PI_STATUS) * [2.6 0x0460 00n4 - PI\_BSD\_DOMn\_LAT](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_00n4_-_PI_BSD_DOMn_LAT) * [2.7 0x0460 00n8 - PI\_BSD\_DOMn\_PWD](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_00n8_-_PI_BSD_DOMn_PWD) * [2.8 0x0460 00nC - PI\_BSD\_DOMn\_PGS](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_00nC_-_PI_BSD_DOMn_PGS) * [2.9 0x0460 00n0 - PI\_BSD\_DOMn\_RLS](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_00n0_-_PI_BSD_DOMn_RLS) * [3 iQue Player-specific registers](https://n64brew.dev/wiki/Peripheral_Interface#iQue_Player-specific_registers) * [3.1 0x0460 0040 - PI\_BB\_ATB\_UPPER](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0040_-_PI_BB_ATB_UPPER) * [3.2 0x0460 0048 - PI\_BB\_NAND\_CTRL](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0048_-_PI_BB_NAND_CTRL) * [3.3 0x0460 004C - PI\_BB\_NAND\_CFG](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_004C_-_PI_BB_NAND_CFG) * [3.4 0x0460 0058 - PI\_BB\_RD\_LEN](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0058_-_PI_BB_RD_LEN) * [3.5 0x0460 005C - PI\_BB\_WR\_LEN](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_005C_-_PI_BB_WR_LEN) * [3.6 0x0460 0060 - PI\_BB\_GPIO](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0060_-_PI_BB_GPIO) * [3.7 0x0460 0070 - PI\_BB\_NAND\_ADDR](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_0070_-_PI_BB_NAND_ADDR) * [3.8 0x0461 0500 to 0x0461 0800 - PI\_BB\_ATB\_LOWER](https://n64brew.dev/wiki/Peripheral_Interface#0x0461_0500_to_0x0461_0800_-_PI_BB_ATB_LOWER) * [4 iQue Player-specific memory](https://n64brew.dev/wiki/Peripheral_Interface#iQue_Player-specific_memory) * [5 Physical Bus Pinout](https://n64brew.dev/wiki/Peripheral_Interface#Physical_Bus_Pinout) * [5.1 PI Interface Process](https://n64brew.dev/wiki/Peripheral_Interface#PI_Interface_Process) * [5.1.1 Address output](https://n64brew.dev/wiki/Peripheral_Interface#Address_output) * [5.1.2 Data Read](https://n64brew.dev/wiki/Peripheral_Interface#Data_Read) * [5.1.3 Constant Read](https://n64brew.dev/wiki/Peripheral_Interface#Constant_Read) * [6 DMA Transfers](https://n64brew.dev/wiki/Peripheral_Interface#DMA_Transfers) * [6.1 Internal process](https://n64brew.dev/wiki/Peripheral_Interface#Internal_process) * [6.2 Internal process: first block](https://n64brew.dev/wiki/Peripheral_Interface#Internal_process:_first_block) * [6.3 Followup transfers](https://n64brew.dev/wiki/Peripheral_Interface#Followup_transfers) * [6.4 PI\_WR\_LEN readbacks after a transfer](https://n64brew.dev/wiki/Peripheral_Interface#PI_WR_LEN_readbacks_after_a_transfer) * [6.5 DMA data dumps](https://n64brew.dev/wiki/Peripheral_Interface#DMA_data_dumps) 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](https://n64brew.dev/wiki/Memory_map "Memory map") 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 `0x0500_0000 - 0x1FBF_FFFF` and `0x1FD0_0000 - 0x7FFF_FFFF`. 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#Ranges 0x0500'0000 - 0x1FBF'FFFF and 0x1FD0'0000 - 0x7FFF'FFFF (PI external bus)](https://n64brew.dev/wiki/Memory_map#Ranges_0x0500'0000_-_0x1FBF'FFFF_and_0x1FD0'0000_-_0x7FFF'FFFF_(PI_external_bus) "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: | | | | | --- | --- | --- | | PI address range | Domain | Device | | 0x0000\_0000 - 0x04FF\_FFFF | Domain 1 | No known device exists that operates in this range | | 0x0500\_0000 - 0x05FF\_FFFF | Domain 2 | 64DD registers | | 0x0600\_0000 - 0x07FF\_FFFF | Domain 1 | 64DD ROM | | 0x0800\_0000 - 0x0FFF\_FFFF | Domain 2 | SRAM | | 0x1000\_0000 - 0xFFFF\_FFFF | Domain 1 | ROM (though this address range is huge, and ROM only typically occupied a small portion of it) | 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 `0x6666_DCBA` will return the value `0xDCBA_DCBA`. 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 * * * | PI\_DRAM\_ADDR `0x0460 0000` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DRAM\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | | DRAM\_ADDR\[7:1\] | | | | | | | 0 | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-1 | **DRAM\_ADDR\[23:1\]:** Base address of RDRAM for PI DMAs; notice that bit 0 cannot be written and is fixed to zero. | **Extra Details:** Note that DMA transfers are buggy if DRAM\_ADDR\[2:0\] are not all zero, see [below](https://n64brew.dev/wiki/Peripheral_Interface#Unaligned_DMA_transfer) . #### 0x0460 0004 - PI\_CART\_ADDR * * * | PI\_CART\_ADDR `0x0460 0004` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[31:24\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | CART\_ADDR\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | R-0 | | CART\_ADDR\[7:1\] | | | | | | | 0 | | | | | --- | --- | | bit 31-1 | **CART\_ADDR\[31:1\]:** Base address of the PI bus (e.g. cartridge) for PI DMAs; notice that bit 0 cannot be written and is fixed to 0. | **Extra Details:** This register is automatically updated by PI after any PI transfer (both DMA and direct I/O). In both cases, it will contain the first address _after_ the last transferred word. DMA transfers are a bit complex in this regard, so see below in this page where the mechanics of the transfers are detailed. #### 0x0460 0008 - PI\_RD\_LEN * * * | PI\_RD\_LEN `0x0460 0008` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | RD\_LEN\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **RD\_LEN\[23:0\]:** Number of bytes, minus one, to be transferred from RDRAM, to the PI bus | **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 * * * | PI\_WR\_LEN `0x0460 000C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[23:16\] | | | | | | | | | 15:8 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[15:8\] | | | | | | | | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | WR\_LEN\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-24 | **Undefined:** Initialized to `0` | | bit 23-0 | **WR\_LEN\[23:0\]:** Number of bytes, minus one, to be transferred from the PI bus, into RDRAM | **Extra Details:** Writing to this register will start the DMA transfer. Reading appears to almost always return \`0x7F\` (see [below](https://n64brew.dev/wiki/Peripheral_Interface#PI_WR_LEN_readbacks_after_a_transfer "Peripheral Interface") for exceptions). #### 0x0460 0010 - PI\_STATUS * * * | PI\_STATUS `0x0460 0010` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | R-0 | R-0 | RW-0 | RW-0 | | — | — | — | — | 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 * * * | PI\_BSD\_DOM1\_LAT `0x0460 0014`

PI\_BSD\_DOM2\_LAT `0x0460 0024` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | LAT\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-8 | **Undefined:** Initialized to `0` | | bit 7-0 | **LAT\[7:0\]:** The "LATch" value is the number of RCP cycles, minus one, after the address has been sent (falling edge of ALE\_L) and before the first read or write may start (falling edge of /RD or /WR) | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's LAT using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set LAT = 64 (meaning (64+1)\*16 = 1040ns). #### 0x0460 00n8 - PI\_BSD\_DOMn\_PWD * * * | PI\_BSD\_DOM1\_PWD `0x0460 0018`

PI\_BSD\_DOM2\_PWD `0x0460 0028` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | PWD\[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-8 | **Undefined:** Initialized to `0` | | bit 7-0 | **PWD\[7:0\]:** The "Pulse WiDth" value is the number of RCP cycles, minus one, the /RD or /WR signals are held low | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's PWD using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set PWD = 18 (meaning (18+1)\*16 = 304ns). #### 0x0460 00nC - PI\_BSD\_DOMn\_PGS * * * | PI\_BSD\_DOM1\_PGS `0x0460 001C`

PI\_BSD\_DOM2\_PGS `0x0460 002C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | PGS\[3:0\] | | | | | | | | --- | --- | | bit 31-4 | **Undefined:** Initialized to `0` | | bit 3-0 | **PGS\[3:0\]:** The "PaGe Size" value configures how many bytes can be sequentially read/written on the bus before sending the next base address (Size = 2^(PGS+2) bytes) | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's PGS using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set PGS = 7 (meaning 2^(7+2) = 512 bytes). The smallest possible value, 0, means 2^(0+2) = 4 bytes; the largest means 2^(15+2) = 128KiB. Page Size only matters for DMA transfers; all direct accesses via the PI are only ever 32 bits wide. Notice that Page Size refers to aligned pages. For instance, with the default setting of 512 bytes, the PI will never allow a single burst to cross a 512 byte boundary. For instance, requesting 16 bytes from address 508 will actually generate two different burst transfers on the PI bus: the first of 4 bytes from offset 508, and the second of 12 bytes from offset 512. #### 0x0460 00n0 - PI\_BSD\_DOMn\_RLS * * * | PI\_BSD\_DOM1\_RLS `0x0460 0020`

PI\_BSD\_DOM2\_RLS `0x0460 0030` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | RW-0 | RW-0 | | — | — | — | — | — | — | RLS\[1:0\] | | | | | | --- | --- | | bit 31-2 | **Undefined:** Initialized to `0` | | bit 1-0 | **RLS\[1:0\]:** The "ReLeaSe" value is the number of RCP cycles, minus one, that the /RD or /WR signals are held high between each 16-bits of data | **Extra Details:** During [IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2 "Initial Program Load") , the N64 will initialize Domain 1's RLS using data read from the cartridge [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . All official ROMs set RLS = 3 (meaning (3+1)\*16 = 64ns). iQue Player-specific 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 0040 - PI\_BB\_ATB\_UPPER * * * | PI\_BB\_ATB\_UPPER `0x0460 0040` (Read) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | IV Source | | 7:0 | U-? | U-? | U-? | U-? | U-? | | | | | — | — | CpuEn | DmaEn | log2(Num Blocks) | | | | | | | | --- | --- | | bit 8 | **IV Source:** Where to source the Initialization Vector from for AES decryption. See below. | | bit 5 | **CpuEn:** If set to 1, the mapping will be enabled for CPU reads | | bit 4 | **DmaEn:** If set to 1, the mapping will be enabled for DMA reads | | bit 3-0 | **log2(Num Blocks):** log2 of the number of contiguous NAND blocks to map. This is applied to an ATB entry when **ATB\_LOWER** registers are written. | **Extra Details** This register supplies only half of the configuration for an ATB entry, also see the **PI\_BB\_ATB\_LOWER** array of registers where PI addresses and the starting NAND block number are specified. Mappings work with sequences of blocks, whose length is a power of two. The register here contains the logarithm of the length so for instance writing "0" causes 1 block to be mapped; writing 4 causes 16 consecutive blocks to be mapped. ATB is the N64 PI address space emulator that translates PI DMAs into NAND flash accesses. Data stored on the NAND is encrypted with AES, ATB must transparently decrypt the data when a PI DMA requests it. To decrypt AES at an 0x10-aligned position **P** the data at **P-0x10** is also required, or if **P=0** then the Initialization Vector (IV) is required. At the start of a DMA, ATB will try to find the entry that maps the PI address for **P-0x10** into the NAND to fetch the needed prior data; for all cases but **P=0** this should resolve correctly with a contiguous PI address space mapping. To handle the **P=0** case an additional dummy mapping must precede the base address of the desired mapping, with the IV Source bit set to 1. When the IV Source bit is 1 the IV will be pulled from the memory at **0x046104D0** rather than reading any data off the NAND. For example if the mapping begins at PI address 0x10000000 as for Cartridge ROM, a dummy mapping for PI address 0x0FFFC000 with IV Source set to 1 should be programmed. #### 0x0460 0048 - PI\_BB\_NAND\_CTRL * * * | PI\_BB\_NAND\_CTRL `0x0460 0048` (Read) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | Busy | — | — | — | — | — | — | — | | 23:16 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | R-0 | R-0 | U-? | U-? | | — | — | — | — | Single-bit Error | Double-bit Error | — | — | | 7:0 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31 | **Busy:** Indicates that a command is currently executing. | | bit 11 | **Single-bit Error:** Indicates that a single-bit error was detected by ECC. These are automatically corrected so generally no action is required. | | bit 10 | **Double-bit Error:** Indicates that a double-bit error was detected by ECC. Unlike single-bit errors, these are not automatically recoverable. | | PI\_BB\_NAND\_CTRL `0x0460 0048` (Write) | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | Execute | Interrupt | — | — | — | — | — | — | | 23:16 | W-0 | | | | | | | | | NAND Command | | | | | | | | | 15:8 | W-0 | W-0 | W-0 | | W-0 | W-0 | W-0 | | | — | Buffer Select | Device Select | | Do ECC | Multicycle | Data Length \[9:8\] | | | 7:0 | W-0 | | | | | | | | | Data Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit 31 | **Execute:** Setting this bit when writing will cause the last written command to begin execution. | | bit 30 | **Interrupt:** Whether the FLASH interrupt should be raised when the command finishes execution. | | bit 29 | **?:** Unknown. Set when issuing Page Program (first cycle) | | bit 28 | **?:** Unknown. Set when issuing Read 1, Read Status and Read ID | | bit 27 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 26 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 25 | **?:** Unknown. Set when issuing Read 1, Block Erase (first cycle) and Page Program (first cycle) | | bit 24 | **?:** Unknown. Set when issuing Read 1, Read ID and Page Program (first cycle) | | bit 23-16 | **NAND Command:** NAND Command to execute. Corresponds directly to commands for the K9F1208U0M flash. | | bit 15 | **?:** Unknown. Set when issuing Read 1, Block Erase (second cycle) and Page Program (second cycle) | | bit 14 | **Buffer Select:** Selects which half of the 0x400-byte PI Buffer mapped at 0x04610000 should be used for DMA operations. See **iQue Player-specific Memory** for details on this buffer | | bit 13-12 | **Device Select:** Corresponds to Chip Enable signals on the card connector. Typically 0. | | bit 11 | **Do ECC:** Whether to do ECC | | bit 10 | **Multicycle:** Set to 1 if the command issued was not the last command in a multi-cycle sequence. | | bit 9-0 | **Data Length:** Data transfer length in bytes. Unlike most other lengths this is not length minus one, a length of 0 can be specified. | **Extra Details:** Writing 0 to this register will clear any pending FLASH interrupt. #### 0x0460 004C - PI\_BB\_NAND\_CFG * * * | PI\_BB\_NAND\_CFG `0x0460 004C` | | | | | | | | | | --- | --- | | 31:24 | U-? | | | | | | | | | Configuration | | | | | | | | | 23:16 | U-? | | | | | | | | | Configuration | | | | | | | | | 15:8 | U-? | | | | | | | | | Configuration | | | | | | | | | 7:0 | U-? | | | | | | | | | Configuration | | | | | | | | | | | | --- | --- | | bit 31-0 | **Configuration:** Likely specifies timing configurations for different NAND flash chips. It is currently unknown how to relate values programmed into this register and timing information found in datasheets. | **Extra Details** System software programs `0x753E3EFF` into this register to execute a Read ID command, then selects an appropriate configuration based on the ID: | ID \[31:16\] | NAND Size in Blocks | NAND Size in MiB | Configuration Value | Part Number | | --- | --- | --- | --- | --- | | 0xEC76 | 0x1000 | 64 | 0x441F1F3F | K9F1208U0M | | 0xEC79 | 0x2000 | 128 | 0x441F1F3F | K9K1G08U0A or K9K1G08U0B | | 0x9876 | 0x1000 | 64 | 0x753E1F3F | TC58512FT | | 0x2076 | 0x1000 | 64 | 0x441F1F3F | NAND512W3A | #### 0x0460 0058 - PI\_BB\_RD\_LEN * * * | PI\_BB\_RD\_LEN `0x0460 0058` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | | | | | — | | | | | | | | | 23:16 | U-? | | | | | | | | | — | | | | | | | | | 15:8 | U-? | | | | | | | W-? | | — | | | | | | | Length \[8\] | | 7:0 | W-? | | | | | | | | | Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Length:** DMA Transfer Length (-1). Writes initiate a DMA from SDRAM starting at **PI\_DRAM\_ADDR** to the PI Buffer at **0x04610000 + PI\_CART\_ADDR**. Exact bit width unknown, it is at least long enough to transfer 0x200 bytes. | **Extra Details** It is currently unknown what the behavior is if a DMA extends out of the bounds of the target PI Buffer, and whether both buffers can be accessed in one transfer. Spare buffers can't be accessed via DMA though. The busy bits in **PI\_STATUS** also applies to these transfers. These transfers also trigger an interrupt upon completion. It is the same interrupt used for regular PI DMAs. #### 0x0460 005C - PI\_BB\_WR\_LEN * * * | PI\_BB\_WR\_LEN `0x0460 005C` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | | | | | — | | | | | | | | | 23:16 | U-? | | | | | | | | | — | | | | | | | | | 15:8 | U-? | | | | | | | W-? | | — | | | | | | | Length \[8\] | | 7:0 | W-? | | | | | | | | | Length \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Length:** DMA Transfer Length (-1). Writes initiate a DMA from the PI Buffer at **0x04610000 + PI\_CART\_ADDR** to SDRAM starting at **PI\_DRAM\_ADDR**. Exact bit width unknown, it is at least long enough to transfer 0x200 bytes. | **Extra Details** It is currently unknown what the behavior is if a DMA extends out of the bounds of the target PI Buffer, and whether both buffers can be accessed in one transfer. Spare buffers can't be accessed via DMA though. The busy bits in **PI\_STATUS** also applies to these transfers. These transfers also trigger an interrupt upon completion. It is the same interrupt used for regular PI DMAs. #### 0x0460 0060 - PI\_BB\_GPIO * * * | PI\_BB\_GPIO `0x0460 0060` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-? | | U-? | U-? | U-? | R-? | | R-? | | Box ID \[15:14\] | | — | — | — | Box ID \[10:9\] | | Box ID \[8:6\] \[2\] | | 23:16 | R-? | | U-? | U-? | U-? | U-? | U-? | U-? | | Box ID \[8:6\] \[1:0\] | | — | — | — | — | — | — | | 15:8 | U-? | U-? | U-? | U-? | U-? | U-? | U-? | U-? | | — | — | — | — | — | — | — | — | | 7:0 | RW-? | | RW-? | RW-? | RW-? | | RW-? | RW-? | | RTC Output Enable | | LED Output Enable | Power Output Enable | RTC Control | | LED Control | Power Control | | | | | --- | --- | | bit 31-16 | **Box ID \[15:0\]:** System software calls this area the "Box ID". Various sub-fields are read out of this area separately for varying purposes. | | bit 31-30 | **Box ID \[15:14\]:** System software reads this as some sort of model identifier. Precise meaning unknown. Whether all players have the same value here is unknown. | | bit 26-25 | **Box ID \[10:9\]:** System clock speed identifier? System software reads this to determine delay intervals for some operations. | | bit 24-22 | **Box ID \[8:6\]:** The Boot ROM checks this against bits \[10:8\] in a register at MI+0x10, if they don't match this value is copied there and the system is rebooted? | | bit 7-6 | **RTC Output Enable:** Output enables for the RTC bit lines. If off, the bit lines will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit lines can be driven to logic low or logic high by writing 0 or 1 respectively to the RTC Control bits. | | bit 5 | **LED Output Enable:** Output enable for the LED bit line. If off, the bit line will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit line can be driven to logic low or logic high by writing 0 or 1 respectively to the LED Control bit. | | bit 4 | **Power Output Enable:** Output enable for the Power bit line. If off, the bit line will be in high impedance and can be driven to logic low or logic high by the other end of the line. If on, the bit line can be driven to logic low or logic high by writing 0 or 1 respectively to the Power Control bit. | | bit 3-2 | **RTC Control:** RTC communication happens through these bits. The communication protocol is described in the [ST M41T0 Serial RTC datasheet](https://www.st.com/content/ccc/resource/technical/document/datasheet/19/24/95/e2/85/6a/47/30/CD00003139.pdf/files/CD00003139.pdf/jcr:content/translations/en.CD00003139.pdf)
; the lower bit is the clock line while the upper bit is the data line. When RTC Output Enable bits are 0 the RTC can drive the bus, in which case reading the RTC Control bits will read the values sent by the RTC. | | bit 1 | **LED Control:** If the LED Output Enable is 1, writing 0 or 1 to this bit will drive the LED bit line low or high. If 0, the LED on the front of the player will light up. If 1, the LED will switch off. | | bit 0 | **Power Control:** If the Power Output Enable is 1, writing 0 or 1 to this bit will drive the Power bit line low or high. If 1, the power will remain on. If 0, the device will power off. | **Extra Details:** Whenever a GPIO control bit (with its corresponding output enable bit set) is set to 1, the corresponding bit line will be set to logic high (3.3v). If set to 0 (with output enable set) the bit line is set to logic low (0v). The LED lights up when the LED GPIO is 0 as the LED requires a voltage difference across it to light up. One side of the LED is fixed to 3.3v while the other side is connected to the LED GPIO port; when LED Control is 1 there is no voltage difference across the LED (3.3 - 3.3 = 0v) so it does not light up, while an LED Control of 0 creates a voltage difference (3.3 - 0 = 3.3v) so the LED lights up. #### 0x0460 0070 - PI\_BB\_NAND\_ADDR * * * | PI\_BB\_NAND\_ADDR `0x0460 0070` | | | | | | | | | | --- | --- | --- | | 31:24 | U-? | | | | | W-? | | | | — | | | | | Address \[26:24\] | | | | 23:16 | W-? | | | | | | | | | Address \[23:16\] | | | | | | | | | 15:8 | W-? | | | | | | | | | Address \[15:8\] | | | | | | | | | 7:0 | W-? | | | | | | | | | Address \[7:0\] | | | | | | | | | | | | --- | --- | | bit ?-0 | **Address:** Set the NAND flash address that commands issued by **PI\_BB\_NAND\_CTRL** will target. Exact bit width is unknown, however it is at least enough to address 128MiB (27 bits) | **Extra Details:** To convert a page number to an address, multiply it by 512. To convert a block number to an address, multiply it by 0x4000. #### 0x0461 0500 to 0x0461 0800 - PI\_BB\_ATB\_LOWER * * * | PI\_BB\_ATB\_LOWER `0x0461 0500 - 0x0461 0800` | | | | | | | | | | --- | --- | | 31:24 | U-? | | | | | | | | | NAND Block Number \[15:8\] | | | | | | | | | 23:16 | U-? | | | | | | | | | NAND Block Number \[7:0\] | | | | | | | | | 15:8 | U-? | | | | | | | | | PI Physical Address \[29:14\] \[15:8\] | | | | | | | | | 7:0 | U-? | | | | | | | | | PI Physical Address \[29:14\] \[7:0\] | | | | | | | | | | | | --- | --- | | bit 31-16 | **NAND Block Number:** Starting block number to map to the provided PI address. | | bit 15-0 | **PI Physical Address \[29:14\]:** PI address to begin the mapping at, divided by 0x4000 the NAND block size. | **Extra Details** There are 192 **ATB\_LOWER** registers. Issuing a write to a particular register will program that ATB entry with a mapping, also using the current contents of **ATB\_UPPER** to complete the entry configuration. The number of blocks to map comes from **ATB\_UPPER**. Mappings involving non-contiguous or unsorted NAND blocks must occupy multiple ATB entries. These ATB entries should be sorted by PI address, from lowest to highest. It is not possible to write addresses that are not aligned to the NAND block size (0x4000) It is not possible to map more contiguous blocks than the PI address alignment allows in a single entry. For example it is not possible to map 2 contiguous blocks in the same ATB entry if the base address is 0x10004000. The maximum number of blocks you can map for given `(pi_addr, nblocks)` in a single ATB entry is `1 << min(ctz(pi_addr/0x4000), ceil(log2(nblocks)))` where `ctz(x)` counts the number of trailing zeros in the binary representation of `x`. iQue Player-specific memory =========================== In addition to extra registers, the iQue Player maps additional memory into the PI registers address space for use in various PI operations. | Address Range | | Name | Description | | --- | --- | --- | --- | | 0x04610000 | 0x046101FF | PI Buffer 0 | Holds intermediate data between SDRAM and the NAND. NAND commands transfer data between this buffer and the flash; transfers between this buffer and SDRAM is done via DMAs triggered by **PI\_BB\_RD\_LEN** and **PI\_BB\_WR\_LEN**. AES decryptions happen in this buffer. | | 0x04610200 | 0x046103FF | PI Buffer 1 | Same as Buffer 0 in operation. | | 0x04610400 | 0x0461040F | PI Spare Data 0 | Holds "spare data" for buffer 0 contents. | | 0x04610410 | 0x0461041F | PI Spare Data 1 | Holds "spare data" for buffer 1 contents. | | 0x04610420 | 0x046104CF | AES Expanded Key | Holds the AES expanded key for AES decryption operations. | | 0x046104D0 | 0x046104DF | AES Initialization Vector | Holds the AES IV for AES decryption operations. | Access by the CPU to these buffers must be performed as if the buffers were memory mapped from PI address space: that is, it is important that the PI status register reports that the PI unit is idle before attempting a read or a write. Physical Bus Pinout =================== The PI Bus is a Bi-directional and multiplexed interface with a 16bit data path to the ROM, 64DD, [Flash](https://n64brew.dev/wiki/Flash "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. | | | | | --- | --- | --- | | Pin Name | Cart pins | Description | | AD0 | 28 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[16\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[0\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[0\] | | AD1 | 29 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[17\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[1\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[1\] | | AD2 | 30 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[18\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[2\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[2\] | | AD3 | 32 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[19\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[3\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[3\] | | AD4 | 36 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[20\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[4\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[4\] | | AD5 | 37 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[21\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[5\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[5\] | | AD6 | 40 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[22\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[6\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[6\] | | AD7 | 41 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[23\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[7\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[7\] | | AD8 | 16 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[24\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[8\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[8\] | | AD9 | 15 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[25\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[9\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[9\] | | AD10 | 12 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[26\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[10\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[10\] | | AD11 | 11 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[27\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[11\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[11\] | | AD12 | 7 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[28\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[12\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[12\] | | AD13 | 5 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[29\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[13\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[13\] | | AD14 | 4 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[30\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[14\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[14\] | | AD15 | 3 | This data bit is used with the following signals to represent the following

/ALEH - Signal changes from HIGH to LOW: Address Bit\[31\] is latched internally in the ROM

/ALEL - Signal changes from HIGH to LOW: Address Bit\[15\] is latched internally in the ROM

/WR or /RD - Signal changes from LOW to HIGH: This will read/write to that ROM location latched in the Cart. Bit \[15\] | | /ALEH | 35 | Parts on the PI bus are expected to latch the high address (Bits\[31:16\]) when this goes from HIGH to LOW.

When this signal goes from LOW to HIGH it resets the internal address system so it can await for a new address request.

This stays HIGH when in idle and LOW when processing data. Commercial ROMs also use this as /CE, entering a low-power state while this signal is high.

This signal will be high for at least 7 FSB cycles, each time a new address is loaded. | | /ALEL | 33 | Parts on the PI bus are expected to latch the low address (Bits\[15:0\]) when this goes from HIGH to LOW.

No action has been seen when this goes from LOW to HIGH.

This stays HIGH when in idle and LOW when processing data.

This signal will be high for at least 14 FSB cycles, each time a new address is loaded, ending 7 FSB cycles after ALEH falls. | | /WR | 8 | This is the signal that sends a write command to the FLASH ram, SRAM or 64DD

While this signal is low, the RCP drives the PI bus with the current word of data.

When this signal goes from LOW to HIGH external parts are expected to record the value at that moment, if they need it. The RCP and external parts are also expected to increase the internal address counter in preparation for the next word transferred.

The RCP will not change this signal from HIGH to LOW until either the Latency (PI\_BSD\_DOMn\_LAT) or Release (PI\_BSD\_DOMn\_RLS) registers have counted the required number of FSB clocks.

This stays HIGH when idle. | | /RD | 10 | This is the signal that sends a read command to the ROM, FLASH ram, SRAM or 64DD.

While this signal is low, the RCP expects that some device will drive the PI Bus.

When this signal goes from LOW to HIGH the RCP will record the value at that moment. The RCP and external parts are also expected to increase the internal address counter in preparation for the next word transferred.

The /RD signal has the same timing constraints as the /WR signal above.

This stays HIGH when idle. | PI Interface Process -------------------- ### Address output  [](https://n64brew.dev/wiki/File:Rom_address_output.png "Rom Address Output") ### Data Read  [](https://n64brew.dev/wiki/File:Rom_Read_Data.png "Rom Read Data") ### Constant Read  [](https://n64brew.dev/wiki/File:Constant_ROM_Access.png "Constant ROM Access") DMA Transfers ============= PI DMA is well defined for so-called "aligned transfers", which are defined by the following constraints: 1. RDRAM address must be 8 bytes aligned 2. PI address must be 2 bytes aligned 3. Length must be a multiple of 2 Notice that the second point might be considered redundant from a hardware point of view given that both registers holding addresses are fixed to be 2-byte aligned (LSB is fixed to 0), but from a software point of view, this has to be taken into account. The behavior of PI DMA when the first and third constraint are not respected is not well designed; it seems like the designers attempted to implement support for loosing these constraints but gave up in the middle, leaving the hardware in a state that can only be described as "buggy". This also leaks some internal details on how the transfers are performed. To implement PI DMA, the RCP uses an internal 128 byte buffer. The following section attempts to describe the exact process (though the \*actual\* process implemented in the hardware is unknown; the following does match in observable behavior). NOTE: only DMA write transfers (PI -> RDRAM) have been analyzed in detail, using default PI DOM1 settings. It is expected that read transfers (RDRAM -> PI) behave in a similar way, though it's not been fully tested yet. We also expect PI DOM1 page size setting to somehow affect the transfer, though this has also not been explored yet. #### Internal process The transfer is split in blocks of maximum 128 bytes each one. Within each block, the PI first fills the internal buffer fetching data from the PI bus, and then write backs the buffer contents to RDRAM. This can be observed by monitoring PI\_DRAM\_ADDR and PI\_CART\_ADDR: during the transfer, it can be first seen PI\_CART\_ADDR moving forward, and then PI\_DRAM\_ADDR catching up with a leap (writing to RDRAM is much faster than reading PI). In general for all blocks of the transfer (excluding the first one, see below), the logic appears to be as follows: * Compute the block size. This is the smallest between the remaining length, the end of the current RDRAM page, and 128 bytes (which is the maximum size of the internal buffer). RDRAM pages are 2 KiB (0x800) long, so for instance if the current RDRAM address (at the beginning of the block) is 0x147e0, the block size will be 0x20 because the RDRAM page ends at 0x147ff. * Fill the page using PI reads from the bus. All PI accesses are always 16-bit long, so if the block size was odd (which happens on the last block, if the remaining length is odd), one extra byte will be fetched from PI into the internal buffer. * Write back into RDRAM. The exact format of RDRAM writes is unknown at the moment; since PI DMA transfers are well-defined for 8-byte aligned RDRAM addresses, it is assumed that 64-bit writes are used (a burst like that used for D/I cache writebacks would require 16-byte alignment or more to be performed). If an extra byte was fetched in the previous step, that byte is also written to RDRAM. So in general odd-length PI DMA transfers will transfer one byte more than requested. The above logic applies for all blocks of the transfer, excluding the first one. The first block in fact is treated specially by PI. It appears that the goal of the designers was to use the first block to realign transfers to 8-byte in RDRAM, which possibly causes the first block to use smaller, masked writes to RDRAM. So, even if the RDRAM starting address is misaligned, all blocks besides the first one will begin from a 8-byte aligned RDRAM address, and behave with the logic described above. #### Internal process: first block These are the differences in logic while processing the first block, which mostly concerns how to handle the initial RDRAM misalignment. In this description, we refer to _RDRAM misalignment_ as the amount of bytes that the RDRAM address is distant from the previous 8-byte aligned word (that is, the misalignment is the value of the last 3 bits of the RDRAM address). Notice that the RDRAM address hardware register has the LSB fixed 0, so misalignment can be either 2, 4, or 6. * The internal 128 byte buffer is filled starting from the index matching the misalignment. This might affect the maximum size of the first block: for instance, if misalignment is 6, the maximum size is not 128 but 122, because the first 6 bytes are skipped. * Writes to RDRAM seems to use some kind of masking, so they are correctly done at the byte granularity. This means that odd length transfers in the first block appear to work correctly. Notice that this applies only to the first block whatever its size is; the size (as described above) might be limited by the end of the RDRAM page, in which case only odd transfers up to there are working correctly. * As an exception to the above exception, if the first block reaches the end of the 128 byte buffer, the last 2 bytes of the buffer are always written back in full to RDRAM, even though one less byte was requested. * Example: PI DMA transfer with misalignment 0 and RDRAM page end far away. Odd lengths up to 125 (included) work correctly; odd transfers of exactly 127 bytes are rounded up to 128 (since they reach the last 16-bit word of the buffer). Also odd transfers of 129 or more, since they need two blocks to be performed, fall back into the general rule where one more byte is transferred. * Example: PI DMA transfer with misalignment 6 and RDRAM page end far away. Odd lengths up to 119 (included) work correctly; odd transfers of exactly 121 bytes are rounded up to 122 (since they reach the last 16-bit word of the buffer). Also odd transfers of 123 or more, since they need two blocks to be performed, fall back into the general rule where one more byte is transferred. * There seems to be a hardware bug related how RDRAM writes are performed, in case of misaligned addresses. It seems like the hardware is counting the block length starting from index 0 of the buffer, even though the first byte was actually placed at the index matching the misalignment, and even though masking is performed correctly. This means that for instance, if misalignment is 6 and the length of 8, the following happens: * First, 8 bytes are fetched from the PI bus and put at index 6..13 in the internal buffer. * Then, RDRAM writes are performed but the hardware believes the block ends at index 8, so only bytes 6..8 are written back to RDRAM. * Symmetrically, if the buffer is full (128 bytes), the last 6 bytes will not be transferred because of the same bug (even if those bytes were fetched by the PI bus). So there will be a "hole" of 6 bytes in the RDRAM output buffer. For instance, if misalignment is 6 and the length is 1024, and the RDRAM page end is far away, the following happens on the first block: * Block size is computed as 122 bytes. * 122 bytes are fetched from the PI bus, and put at index 6..127 in the internal buffer. * RDRAM writes are performed but the hardware believes that the block ends at index 121, so only bytes 6..121 are written back to RDRAM. * Notice that, this notwithstanding, RDRAM address is correctly rounded up to 8 byte at the end of the block (see below), so the second block will behave correctly. There will be a hole in RDRAM as bytes 122.127 in the first block are never written back to RDRAM, so the content of RDRAM for those bytes is not affected by DMA. * RDRAM address register is always rounded up to the next 8 byte alignment at the end of the first block. In most normal cases, the logic above already ensures that the address ends up being aligned at the end of the block, but the rounding up happens even in cases like short transfers that ends with the first block at ends at an arbitrary byte. #### Followup transfers After a DMA transfer is finished, it is possible to trigger a "followup transfer", that is a transfer that sequentially continues the previous one, by simply writing a new length to the PI\_WR\_LEN register. In this case, the current values of PI\_DRAM\_ADDR and PI\_CART\_ADDR are used at the beginning of the transfers. Those values will match the last addresses as updated by the first transfer. The above section describes in details how PI reads and RDRAM writes are done, and registers are updated, so they also implicitly describe how a followup transfer behaves in various edge cases (short transfers, misaligned transfers, etc.) #### PI\_WR\_LEN readbacks after a transfer Reading back PI\_WR\_LEN after a transfer is done, appears to always be fixed at 0x7F. The only exception that has been noticed is when the transfer was smaller than 8 bytes: in that case, the value is 0x7F minus the initial RDRAM misalignment. For instance, if the RDRAM misalignment was 4, the value found in the register at the end of the transfer will be 0x7B. #### DMA data dumps To further investigate and understand how PI DMA is performed, the repo [n64\_pi\_dma\_test](https://github.com/rasky/n64_pi_dma_test) can be used. The repo contains data dumps acquires on real hardware of DMA transfers with all possible misalignments (0, 2, 4, 6), all lengths from 1 to 384 bytes, and all distances from RDRAM page end from 0 to 128 bytes. It also contains timing information on all those transfers. The repo can be used as a testsuite for emulators, but also to further investigate other side cases. Retrieved from "[https://n64brew.dev/wiki/Parallel\_Interface?oldid=5761](https://n64brew.dev/wiki/Parallel_Interface?oldid=5761) " --- # Clock Timing - N64brew Wiki [](https://n64brew.dev/wiki/Clock_Timing#) Clock Timing ============ N64 **Clock Timing** originates from two quartz crystal resonators that supply reference clocks to [phase-locked loop](https://en.wikipedia.org/wiki/Phase-locked_loop "wikipedia:Phase-locked loop") (PLL) clock synthesizers, which generate the system clocks used throughout the console. These reference inputs are designated X'tal1 (X1) and X'tal2 (X2) in the Macronix MX8350 datasheet. X1 is the clock source for the [Reality Coprocessor's](https://n64brew.dev/wiki/Reality_Coprocessor "Reality Coprocessor") [Video Interface](https://n64brew.dev/wiki/Video_Interface "Video Interface") , [Audio Interface](https://n64brew.dev/wiki/Audio_Interface "Audio Interface") , and [video encoder](https://n64brew.dev/wiki/Video_DAC "Video DAC") . X2 provides a reference clock which is synthesized to drive [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM") , from which downstream clocks for all non-AV systems are derived. Video and audio subsystem clocks derive from X1; all other system clocks derive from X2. X1 varies by region to account for differing broadcast standards. Contents -------- * [1 Crystal 1 (X1)](https://n64brew.dev/wiki/Clock_Timing#Crystal_1_(X1)) * [1.1 Crystal Frequency Derivation](https://n64brew.dev/wiki/Clock_Timing#Crystal_Frequency_Derivation) * [1.2 Clock Synthesis](https://n64brew.dev/wiki/Clock_Timing#Clock_Synthesis) * [1.2.1 MX8330MC pinout](https://n64brew.dev/wiki/Clock_Timing#MX8330MC_pinout) * [1.2.2 MX9911MC pinout](https://n64brew.dev/wiki/Clock_Timing#MX9911MC_pinout) * [1.2.3 MX8350 pinout](https://n64brew.dev/wiki/Clock_Timing#MX8350_pinout) * [2 Crystal 2 (X2)](https://n64brew.dev/wiki/Clock_Timing#Crystal_2_(X2)) * [2.1 VR4300 Clock Domains](https://n64brew.dev/wiki/Clock_Timing#VR4300_Clock_Domains) * [3 References](https://n64brew.dev/wiki/Clock_Timing#References) * [4 Footnotes](https://n64brew.dev/wiki/Clock_Timing#Footnotes) Crystal 1 (X1) -------------- A quartz crystal resonator on the N64 motherboard supplies the X1 reference clock to the clock synthesizer (U7 or U17)[\[1\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-1) in order to generate the VI clock frequency (VCLK). The N64 utilizes AT-cut crystals sourced from Daishinku Co., Ltd. (KDS)[\[2\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-2) with a baseline frequency tolerance of ±30 ppm[\[3\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-3) ; derived signals inherit this proportional error. ### Crystal Frequency Derivation  [](https://n64brew.dev/wiki/File:CCIR-1990-Rep.624-4-Table.png) Chrominance sub-carrier frequencies. M/NTSC: 3,579,545 ± 10 Hz; M/PAL: 3,575,611.49 ± 10 Hz ; B/PAL: 4,433,618.75 ± 5 Hz. Image from: [CCIR Report 624-4 (1990), Table II](http://handle.itu.int/11.1004/020.1000/4.283) Each regional broadcast standard defines a horizontal line frequency ( f H {\\displaystyle {\\displaystyle f\_{H}}}  ), from which the chrominance subcarrier frequency ( f S C {\\displaystyle {\\displaystyle f\_{SC}}}  )[\[4\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-4) is derived as a fixed rational multiple. The X1 resonant frequency is specified as 4 × f S C {\\displaystyle {\\displaystyle f\_{SC}}}  . All frequencies are given in hertz (Hz). | Standard | f H {\\displaystyle {\\displaystyle f\_{H}}} | f S C {\\displaystyle {\\displaystyle f\_{SC}}}   : f H {\\displaystyle {\\displaystyle f\_{H}}} | f S C {\\displaystyle {\\displaystyle f\_{SC}}} | X1 | | --- | --- | --- | --- | --- | | **NTSC** | 2,250,000 143 {\\displaystyle {\\displaystyle {\\frac {2{,}250{,}000}{143}}}} | f S C \= 455 2 f H {\\displaystyle {\\displaystyle f\_{SC}={\\frac {455}{2}}f\_{H}}} | 315 88 × 10 6 {\\displaystyle {\\displaystyle {\\frac {315}{88}}\\times 10^{6}}} | 315 22 × 10 6 {\\displaystyle {\\displaystyle {\\frac {315}{22}}\\times 10^{6}}} | | **PAL** | 15,625 {\\displaystyle {\\displaystyle 15{,}625}} | f S C \= ( 1135 4 + 1 625 ) f H {\\displaystyle {\\displaystyle f\_{SC}=\\left({\\frac {1135}{4}}+{\\frac {1}{625}}\\right)f\_{H}}} | 17,734,475 4 {\\displaystyle {\\displaystyle {\\frac {17{,}734{,}475}{4}}}} | 17,734,475 {\\displaystyle {\\displaystyle 17{,}734{,}475}} | | **MPAL** | 2,250,000 143 {\\displaystyle {\\displaystyle {\\frac {2{,}250{,}000}{143}}}} | f S C \= 909 4 f H {\\displaystyle {\\displaystyle f\_{SC}={\\frac {909}{4}}f\_{H}}} | 511,312,500 143 {\\displaystyle {\\displaystyle {\\frac {511{,}312{,}500}{143}}}} | 2,045,250,000 143 {\\displaystyle {\\displaystyle {\\frac {2{,}045{,}250{,}000}{143}}}} | Converted to decimal (Hz): | Standard | f H {\\displaystyle {\\displaystyle f\_{H}}} | f S C {\\displaystyle {\\displaystyle f\_{SC}}}   : f H {\\displaystyle {\\displaystyle f\_{H}}} | f S C {\\displaystyle {\\displaystyle f\_{SC}}} | X1 | | --- | --- | --- | --- | --- | | **NTSC** | 15,734.2657 | 227.5 f H {\\displaystyle {\\displaystyle f\_{H}}} | 3,579,545.4545 | 14,318,181.8182 | | **PAL** | 15,625 | 283.7516 f H {\\displaystyle {\\displaystyle f\_{H}}} | 4,433,618.75 | 17,734,475 | | **MPAL** | 15,734.2657 | 227.25 f H {\\displaystyle {\\displaystyle f\_{H}}} | 3,575,611.8881 | 14,302,447.5524 | ### Clock Synthesis  [](https://n64brew.dev/wiki/File:MX8350-Table.png) MX8350 functional description and frequency table. Image from: [IC-ON-LINE - Macronix MX8350 datasheet mirror](http://www.datasheet.hk/view_download.php?id=1145313&file=0069%5Cmx8350_566191.pdf)  [](https://n64brew.dev/wiki/File:MX8330_video_maths.png) MX8330 description. FSEL (frequency select) logic and Revision E notice. Image from: [Console5 - Macronix MX8330MC datasheet mirror](https://wiki.console5.com/tw/images/e/e3/MX8330.pdf) The PLL clock synthesizer (Macronix MX8330MC / MX9911MC / MX8350) derives the necessary clocks from the X1 crystal input: * **FSC**: Chrominance subcarrier = X1 ÷ 4 * **VCLK**: Video clock = X1 × multiplier ÷ 5 **FSEL (NTSC/!PAL)**:[\[5\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-5) Input pin; tied high or low. Selects the VCLK multiplier: | Standard | FSEL Pin State | Multiplier | | --- | --- | --- | | **NTSC, MPAL** | High | 17× | | **PAL** | Low | 14× | Video clock frequency is calculated as: V C L K \= X 1 × Multiplier 5 {\\displaystyle {\\displaystyle VCLK={\\frac {X1\\times {\\text{Multiplier}}}{5}}}}   All values in MHz: | Standard | FSC ( f S C {\\displaystyle {\\displaystyle f\_{SC}}}  ) | Crystal 1 (X1) | FSEL (NTSC/!PAL) | Multiplier | Divider | VCLK | | --- | --- | --- | --- | --- | --- | --- | | **NTSC** | 3.57954545 | 14.31818182 | High | 17 | 5 | 48.68181818 | | **PAL** | 4.43361875 | 17.734475 | Low | 14 | 5 | 49.65653 | | **MPAL** | 3.57561189 | 14.30244755 | High | 17 | 5 | 48.62832167 | #### MX8330MC pinout | | | | | | | --- | --- | --- | --- | --- | | Name | Pin | | Pin | Name | | FSO/5 | 1 | | 8 | FSC | | GND | 2 | | 7 | FSEL | | FSO | 3 | | 6 | OSCIN | | VDD | 4 | | 5 | OSCOUT | #### MX9911MC pinout | | | | | | | --- | --- | --- | --- | --- | | Name | Pin | | Pin | Name | | FSO/5 | 1 | | 8 | FSC | | GND | 2 | | 7 | FSEL | | N.C.[\[6\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-6) | 3 | | 6 | OSCIN | | VDD | 4 | | 5 | OSCOUT | #### MX8350 pinout | | | | | | | --- | --- | --- | --- | --- | | Name | Pin | | Pin | Name | | FSC | 1 | | 14 | !RESET | | GND | 2 | | 13 | OSC1 IN | | VCLK | 3 | | 12 | OSC1 OUT | | VDD | 4 | | 11 | GND | | OSC2 IN | 5 | | 10 | TEST | | OSC2 OUT | 6 | | 9 | VDD | | NTSC/!PAL | 7 | | 8 | RCLK | Crystal 2 (X2) -------------- A second quartz crystal resonator (X2) produces another reference clock which is synthesized by the PLL clock generator and routed to RDRAM. X2 is multiplied by 17[\[7\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-7) to produce RCLK,[\[8\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-8) from which all non-AV system clocks are derived, including the MasterClock (MClock).[\[9\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-9) All values in MHz: | Clock | Derivation | Fraction | Decimal | | --- | --- | --- | --- | | **X2** | n/a | 250 17 {\\displaystyle {\\displaystyle {\\frac {250}{17}}}}  [\[10\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-10) | 14.7058823529 | | **RCLK** | X2 × 17 | 250 {\\displaystyle {\\displaystyle 250}} | 250 | | **MClock** | RCLK ÷ 4 | 125 2 {\\displaystyle {\\displaystyle {\\frac {125}{2}}}} | 62.5 | | **[CPU](https://n64brew.dev/wiki/VR4300 "VR4300")
** | MClock × 3 2 {\\displaystyle {\\displaystyle {\\frac {3}{2}}}} | 375 4 {\\displaystyle {\\displaystyle {\\frac {375}{4}}}} | 93.75[\[11\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-11) | | **[Serial Interface](https://n64brew.dev/wiki/Serial_Interface "Serial Interface")
** | MClock ÷ 4 | 125 8 {\\displaystyle {\\displaystyle {\\frac {125}{8}}}} | 15.625 | | **[Cartridge](https://n64brew.dev/wiki/Game_Pak "Game Pak")
/ [PIF](https://n64brew.dev/wiki/PIF "PIF")
** | SI ÷ 8 | 125 64 {\\displaystyle {\\displaystyle {\\frac {125}{64}}}} | 1.953125 | ### [VR4300](https://n64brew.dev/wiki/VR4300 "VR4300") Clock Domains The NUS-CPU itself generates clock domains using an integrated PLL, referenced to the MClock input. This clock architecture is described in the NEC VR4300 documentation: * **PClock**: Principal operating frequency; generated from MClock via PLL using DivMode configuration pins (sampled on cold reset). Frequency is divided by 4 in low-power mode. * **SClock**: Internal system interface clock; normally equal to MClock. Reduced in tandem with PClock in low-power mode. * **TClock**: Transmit clock output for external agents. Always equal to MasterClock regardless of DivMode setting. | DivMode (1:0) | MClock : PClock | MClock (MHz) | PClock (MHz) | | --- | --- | --- | --- | | `0b00`[\[12\]](https://n64brew.dev/wiki/Clock_Timing#cite_note-12) | 1 : 1 | 62.5 | 62.5 | | `0b01` | 1 : 1.5 | 62.5 | 93.75 | | `0b10` | 1 : 2 | 62.5 | 125 | | `0b11` | 1 : 3 | 62.5 | 187.5 | The [VR4300 manual](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf) also notes: "The maximum value of PClock is the same as the maximum internal operating frequency of each product regardless of the frequency ratio." The higher DivMode ratios exceed the nominal operating specification of an umodified N64 CPU. References ---------- * [ITU Digital Collection - CCIR Report 624-4 (1990)](http://handle.itu.int/11.1004/020.1000/4.283) * [Console5 Tech Wiki - RDC - NUS-CPU-03/04 Schematic (mirror)](https://wiki.console5.com/wiki/File:N64_NUS-CPU-03-04.pdf) * [Datasheet Archive - Macronix MX8330MC datasheet (mirror)](https://www.datasheetarchive.com/datasheet/MX8330/Macronix-International?term=MX8330) * [Datasheet Archive - Macronix MX9911MC datasheet (mirror)](https://www.datasheetarchive.com/datasheet/MX9911/Macronix-International?term=mx9911) * [Datasheet Archive - Macronix MX8350 datasheet (mirror)](https://www.datasheetarchive.com/datasheet/MX8350/Macronix-International?term=MX8350) * [Datasheet Archive - KDS America - Quartz Crystals - AT-49, AT-38, UM-5, UM-1, UM-2, HC-49/U, p.6 (1993) (mirror)](https://www.datasheetarchive.com/datasheet/0a2c800efae9bfe2) * [KDS - AT-38/AT-49 Miniature Crystal Resonators datasheet (2011)](https://www.kds.info/wp-content/uploads/2015/11/2011-2012_032_en.pdf) * [NEC - VR4300 User's Manual (mirror)](https://n64brew.dev/wiki/File:VR4300-Users-Manual.pdf) * [ultra64.ca - MIPS Technologies Inc. - R4300 RISC Processor Specification, Revision 2.2 (mirror)](https://ultra64.ca/files/documentation/silicon-graphics/SGI_R4300_RISC_Processor_Specification_REV2.2.pdf) * [assemblergames.org - eb1560 et al - Mapping N64 Overclockability](https://assemblergames.org/viewtopic.php?t=25918) * [Google Patents - US6310814B1, _Rambus DRAM (RDRAM) apparatus and method for performing refresh operations_](https://patents.google.com/patent/US6310814B1) * [Google Patents - US3283170A, _Coupling transistor logic and other circuits_](https://patents.google.com/patent/US3283170A) * [Game Boy hardware database - Gekkio et al](https://gbhwdb.gekkio.fi/) * [FCC - Test Report No. : 11319287S-A-R2](https://fcc.report/FCC-ID/BKEHAC013/3265207.pdf) * [Wikimedia Commons - Evan-Amos - Nintendo-N64-Motherboard-Top.jpg](https://commons.wikimedia.org/wiki/File:Nintendo-N64-Motherboard-Top.jpg) * [Wikimedia Commons - Evan-Amos - Nintendo-Famicom-Motherboard-BL.jpg](https://commons.wikimedia.org/wiki/File:Nintendo-Famicom-Motherboard-BL.jpg) * [github.com - meauxdal - N64 Video Timing Reference](https://github.com/meauxdal/N64-Refresh-Rate-Reference) Footnotes --------- 1. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-1) Early boards use the 8-pin Macronix MX8330MC at U7; later revisions substitute with MX9911MC. "Funtastic"-era and later models consolidate U7 and U15 into a single 14-pin MX8350 synthesizer at U17. 2. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-2) Corroborated by [Game Boy hardware database](https://gbhwdb.gekkio.fi/) explicitly identifying identical crystals as Daishinku Co., Ltd. (est. 1959 as Daiwa Shinku Kogyosho), ["KDS" markings on Famicom and NES resonators](https://commons.wikimedia.org/wiki/File:Nintendo-Famicom-Motherboard-BL.jpg) , and an [FCC report](https://fcc.report/FCC-ID/BKEHAC013/3265207.pdf) indicating an ongoing supplier relationship between Nintendo and Daishinku. Crystals identical in appearance and marking convention to those positively identified as KDS crystals have been confirmed to appear on at least Game Boy, SNES, Virtual Boy, N64, and Game Boy Color hardware. 3. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-3) X1 and X2 are visually identified as AT-49 hermetically sealed through-hole AT-cut crystal resonators from [N64 motherboard photography (Evan-Amos, Wikimedia Commons)](https://commons.wikimedia.org/wiki/File:Nintendo-N64-Motherboard-Top.jpg) , consistent with KDS AT-49 appearance and lot encoding ([KDS AT-38/AT-49 Datasheet](https://www.kds.info/wp-content/uploads/2015/11/2011-2012_032_en.pdf) ). Base grade frequency tolerance is given at ±30 ppm. 4. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-4) The television broadcast standard-specific frequency that carries color information in composite video signals. Also referred to as color subcarrier or colorburst. Note that "burst" refers specifically to the sample of this subcarrier transmitted during the back porch to establish a phase and frequency reference. 5. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-5) This pin is marked FSEL on the MX8330MC and MX9911MC datasheets; the equivalent pin on the MX8350 is referred to as **NTSC/!PAL** (! = active low). 6. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-6) Because the Rambus Signaling Logic FSO pin is internally not connected (N.C.) on MX9911MC, it can only be used at U7 for AV reference clocks. It exclusively outputs TTL and is incapable of RSL. 7. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-7) On early boards using MX8330MC, this multiplier requires FSEL to be tied high on X2's paired discrete synthesizer. On boards with MX8350, this is a fixed 17× multiplier regardless of NTSC/!PAL pin state. 8. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-8) RCLK is distributed via Rambus Signaling Logic (RSL), a low-voltage swing interface, via the synthesizer's **FSO** (MX8330MC) or **RCLK** (MX8350) pin. This is distinct from the Transistor-Transistor Logic (TTL) **FSO/5** (MX8330MC) or **VCLK** (MX8350) pin. 9. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-9) See [SysAD\_Interface](https://n64brew.dev/wiki/SysAD_Interface "SysAD Interface") . 10. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-10) The MX8350 datasheet specifies an RCLK of 250 MHz and a multiplier of 17. 11. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-11) CPU clock multiplier set via [VR4300](https://n64brew.dev/wiki/VR4300 "VR4300") DivMode (Divide Mode) static pin inputs. 93.75 MHz is the nominal operating frequency. 12. [↑](https://n64brew.dev/wiki/Clock_Timing#cite_ref-12) DivMode value `0b00` is marked RFU (Reserved for Future Use) in the VR4300 User's Manual. In the MTI R4300i documentation, this mode is indicated to use a 1:1 ratio. Retrieved from "[https://n64brew.dev/wiki/Clock\_Timing?oldid=5820](https://n64brew.dev/wiki/Clock_Timing?oldid=5820) " --- # ROM Header - N64brew Wiki [](https://n64brew.dev/wiki/ROM_Header#) ROM Header ========== Contents -------- * [1 Standard header](https://n64brew.dev/wiki/ROM_Header#Standard_header) * [2 Advanced Homebrew ROM Header](https://n64brew.dev/wiki/ROM_Header#Advanced_Homebrew_ROM_Header) * [2.1 Features](https://n64brew.dev/wiki/ROM_Header#Features) * [2.2 Homebrew ROM Header special flags](https://n64brew.dev/wiki/ROM_Header#Homebrew_ROM_Header_special_flags) * [2.3 Support by emulators](https://n64brew.dev/wiki/ROM_Header#Support_by_emulators) * [2.4 Support by flashcarts](https://n64brew.dev/wiki/ROM_Header#Support_by_flashcarts) Standard header --------------- The following table shows the standard contents of the ROM header. Most of the structure of the header is defined by the [IPL3](https://n64brew.dev/wiki/IPL3 "IPL3") routines commonly used in commercial games (which are actually contained in the ROM itself); only a few fields are accessed by IPL2 (which is burned in [PIF ROM](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") ) and are thus hard-coded for all possible valid N64 ROMs. OffsetSizeNameDescription0x000x1ReservedThis byte is unused and the meaning is unknown. All known commercial games use `0x80` here. It is sometimes erroneously believe to be part of the PI BSD DOM1 configuration flags (next 3 bytes) but it is actually not part of the configuration. 0x010x3PI BSD DOM1 Configuration FlagsThese flags are used by IPL2 to configure access to the ROM (which is mapped in the PI DOM1 space, see [memory map](https://n64brew.dev/wiki/Memory_map "Memory map") ). IPL2 first configure the PI to its slowest speed to be able to read these bytes, and then use them to configure the correct speed to access the ROM. The meaning of these bytes are as follows: | | | | | | | --- | --- | --- | --- | --- | | Offset | Bits | Description | PI reg | Common value | | 0x01 | 4-5 | RLS (release timing) | [PI\_BSD\_DOM1\_RLS](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_00n0_-_PI_BSD_DOMn_RLS "Peripheral Interface") | 0x3 | | 0x01 | 0-3 | PGS (page size) | [PI\_BSD\_DOM1\_PGS](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_00nC_-_PI_BSD_DOMn_PGS "Peripheral Interface") | 0x7 | | 0x02 | 0-7 | PWD (pulse width) | [PI\_BSD\_DOM1\_PWD](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_00n8_-_PI_BSD_DOMn_PWD "Peripheral Interface") | 0x12 | | 0x03 | 0-7 | LAT (latency) | [PI\_BSD\_DOM1\_LAT](https://n64brew.dev/wiki/Peripheral_Interface#0x0460_00n4_-_PI_BSD_DOMn_LAT "Peripheral Interface") | 0x40 | The common values listed in the table are those used by all known commercial ROMs. Together with the previous byte, they contribute to create the "magic value" `0x80371240` that sometimes it is misused as a way to detect N64 ROM. These fields could be tuned in replica cartridges to adapt to the actual maximum physical speed that the ROM can be accessed, but please pay attention that many emulators expect to find the 4 "standard" values here and actually use them as a "fixed ID" to detect if the ROM dump was byte-swapped. If you are an emulator author, please make sure that your emulator can load a ROM with arbitrary values in these first 4 bytes, and assume the ROM is in plain "big-endian" format (not byte swapped, not endian swapped). All modern homebrew ROMs should ship in plain format anyway, as the other formats are obsolete and strongly discouraged. 0x040x4Clock RateConstant value used by libultra versions 2.0I and earlier, to naively calculate how much real-time has passed based on the CPU's Count register. The value is masked by 0xFFFFFFF0, then multiplied by 0.75 to account for the CPU clock's multiplier (1.5x) and that the Count register increments every 2 CPU cycles. However, if the masked value equals zero, libultra defaults to 0x03B9ACA0 (62,500,000) before multiplying by 0.75. All known ROMs, except those listed below, have this field set to 0x0000000F. | Clock Rate | libultra ver. | Game Code | Title | | --- | --- | --- | --- | | 0x03B9ACAF | 2.0D | NCUP | Cruis'n USA (Europe) | | 0x03B9ACAF | 2.0D | NCUE | Cruis'n USA (USA) (Rev A) | | 0x03B9ACAF | 2.0D | NCUE | Cruis'n USA (USA) (Rev B) | | 0x03B9ACAF | 2.0D | NCUE | Cruis'n USA (USA) | | 0x03A07F5F | 2.0G | NDMP | Doom 64 (Europe) | | 0x03A07F5F | 2.0H | NDMJ | Doom 64 (Japan) | | 0x03A07F5F | 2.0G | NDME | Doom 64 (USA) (Rev A) | | 0x03A07F5F | 2.0G | NDME | Doom 64 (USA) | | 0x03B9ACAF | 2.0G | NXGP | NBA Hangtime (Europe) | | 0x03B9ACAF | 2.0G | NXGE | NBA Hangtime (USA) | | 0x03A07F5F | 2.0I | NRIP | New Tetris, The (Europe) | | 0x03A07F5F | 2.0I | NRIE | New Tetris, The (USA) | | 0x03A07F5F | 2.0H | NQKP | Quake 64 (Europe) | | 0x03A07F5F | 2.0H | NQKE | Quake 64 (USA) | | 0x03A07F5F | 2.0F | NFXJ | Star Fox 64 (Japan) | | 0x03A07F5F | 2.0H | NFXE | Star Fox 64 (USA) (Rev A) | | 0x03A07F5F | 2.0H | NFXE | Star Fox 64 (USA) | | 0x03A07F5F | 2.0D | NSWP | Star Wars - Shadows of the Empire (Europe) | | 0x03A07F5F | 2.0D | NSWE | Star Wars - Shadows of the Empire (USA) (Beta) | | 0x03A07F5F | 2.0D | NSWE | Star Wars - Shadows of the Empire (USA) (Rev A) | | 0x03A07F5F | 2.0D | NSWE | Star Wars - Shadows of the Empire (USA) (Rev B) | | 0x03A07F5F | 2.0D | NSWE | Star Wars - Shadows of the Empire (USA) | | 0x03A07F5F | 2.0D | NSWJ | Star Wars - Teikoku no Kage (Japan) | | 0x03A07F5F | 2.0I | NEPP | Star Wars Episode I - Racer (Europe) (En,Fr,De) | | 0x03A07F5F | 2.0I | NEPJ | Star Wars Episode I - Racer (Japan) | | 0x03A07F5F | 2.0I | NEPE | Star Wars Episode I - Racer (USA) | Using Doom 64 as an example, 0x03A07F5F is masked to 0x03A07F50 (60,850,000), and then multiplied by 0.75 equals 0x02B85F7C (45,637,500). Keep in mind that there is no known way for software to change the clock frequency used by either the RCP or CPU. So if/when games use the Count register divided by 46,875,000 to measure a real-time second, the calculated result will be inaccurate. [Polprzewodnikowy](https://n64brew.dev/wiki/User:Polprzewodnikowy "User:Polprzewodnikowy") from the N64brew Discord, experimented with Doom 64 and Star Fox 64 on real hardware. They found that increasing this value increases the delay before the copyright screen or Nintendo logo (respectively) shows up on screen. 0x080x4Boot AddressInitial PC in RDRAM. IPL3 will DMA 1 MiB of ROM code from offset 0x1000 to this address when it has finished initializing the hardware, and then jump here to boot the ROM. The most common value for this field is 0x80000400. The IPL3 for CIC 6103 behaves differently: the value stored here is subtracted by 0x100000 before the jump. For instance, Paper Mario (a game with CIC 6103) contains the value 0x80125C00 in this field, but the actual entry point is 0x80025C00. The IPL3 for CIC 6106 also behaves differently: the value stored here is subtracted by 0x200000 before the jump. 0x0C0x4Libultra VersionThese 4 bytes are meant to indicate the version of the libultra SDK the ROM was compiled with. | | | | | --- | --- | --- | | Offset | Size | Description | | 0x0 | 0x2 | Reserved, or possibly used to store the patch version. | | 0x2 | 0x1 | Major and minor version of libultra as decimal value. For instance, version "2.0" is encoded as decimal 20 (hex 0x14). | | 0x3 | 0x1 | Revision version of libultra, as ASCII letter. | For instance, libultra version "2.0L" is reported as `00 00 14 4C` This field is not used by IPL or anything else, it is just for information; moreover, many games report the wrong version here. 0x100x8Check Code64-bit check code calculated on 1 Mbyte of ROM contents starting from offset 0x1000 (after IPL3). This check code is computed with a custom algorithm implemented by IPL3 that verifies the integrity of the ROM before booting it. If the check code doesn't match, IPL3 refuses to boot the ROM and hangs the console. Sometimes, these 8 bytes are referred to as "CRC HI/LO" or "CRC1/2", but the check code algorithm is not a CRC, and splitting it into two 4-byte words seem confusing when it is really a 64-bit value. To see the exact algorithm used by IPL3, see the [source code of the chksum64 tool](https://github.com/rasky/chksum64) . 0x180x8ReservedThis field is unused by Nintendo, but has seen usage by other developers. For the Conker's Bad Fur Day debug ROM, this field is set to `6F 23 01 3A 2F C9 CB 36`. For the Perfect Dark debug ROM, this field is set to `54 E4 D8 38 2F BD 95 36`. Since both of these games were made by RAREWARE, this field could be a check code for their Security Dongle hardware, which was required to be able to boot these games on the N64. Another ROM, the StarCraft 64 beta, also has data in this field. However, it seems to just be random, as the remaining header fields are also set to random values. 0x200x14Game TitleString that contains the name of the game. The encoding is usually either ASCII or JIS X 0201 (a subset of Shift-JIS). Padding Is usually performed with 0x20 (ASCII space).0x340x7ReservedThis field is unused by Nintendo, but saw accidental usage in the Tommy Thunder prototype ROM, where the first 4 bytes are set to 0x20. This is possibly due to the developers thinking that the game title field was 0x18 bytes rather than 0x14.0x3B0x4Game CodeThe game code is 4 ASCII characters that are split up into 3 parts - the category code, the unique code and the destination code.OffsetSizeNameDescription0x00x1Category CodeAn alphanumeric character that specifies the kind of media the game is stored on. | | | | --- | --- | | N | Game Pak | | D | 64DD Disk | | C | Expandable Game: Game Pak Part | | E | Expandable Game: 64DD Disk Part | | Z | Aleck64 Game Pak | 0x10x2Unique CodeTwo alphanumeric characters that identify the game.0x30x1Destination CodeAn alphanumeric character that specifies the destination the game is intended for. | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | | A | All | | H | Netherlands | | S | Spain | | B | Brazil | | I | Italy | | U | Australia | | C | China | | J | Japan | | W | Scandinavia | | D | Germany | | K | Korea | | X | Europe | | E | North America | | L | Gateway 64 (PAL) | | Y | Europe | | F | France | | N | Canada | | Z | Europe | | G | Gateway 64 (NTSC) | | P | Europe | While this field has no direct meaning for the hardware and no game is known to read it, flashcarts, menus and emulators normally use this field to boot a ROM using the TV type that supposedly matches what the game expects. A value of 0x00 is commonly found in homebrew as a way to declare "region free" (that is, the game will work on any TV type). 0x3F0x1ROM VersionThis byte is used to identify the version of the game. Normally the value is 0 for the first version, 1 for the second and so on.0x400xFC0IPL3This area contains the [IPL3 boot code](https://n64brew.dev/wiki/IPL3 "IPL3") . Each ROM ships its own IPL3 that is meant to work together with the [CIC](https://n64brew.dev/wiki/CIC "CIC") installed in the ROM. Before running IPL3, IPL2 checks its checksum also using a seed coming from the CIC, which binds each IPL3 code to a specific version of CIC. Advanced Homebrew ROM Header ---------------------------- The Advanced Homebrew ROM Header format is a convention that has been agreed upon in the N64 homebrew community to add additional information in the header, using unused fields. The goal of this convention is to let homebrew ROMs declare the correct saveype and controllers that are expected to play the game. The format has been introduced by the EverDrive 64 flashcard and has been later enhanced by the homebrew community. This is useful because emulators normally work using a game database which matches games using checksum to find out which savetype and controllers are expected by the game, to help gamers play the game. For instance, when a N64 emulator detects that a Perfect Dark ROM is loaded, it will automatically emulate a 16Kb EEPROM to save the game (that was the original support present in the physical cartridge for the original game), and will possibly also emulate a preinstalled Transfer PAK, as the accessory can be used with the game. This is done purely by matching the Perfect Dark ROM checksum in a database, so a homebrew game, which would probably not be present in game databases, would suffer from non-working saves and possibly wouldn't be able to use special controllers or accessories. Instead, by using the Advanced Hombrew ROM Header, emulators can automatically configure the required emulated hardware as expected without having to keep any additional database, by simply decoding specific fields of the ROM header. In addition to emulators, also development flashcarts and loaders can use this format to automatically configure the correct savetype when the ROM is loaded. ### Features The Homebrew Header features are mainly divided into 3 groups: * **Savetype:** this allows the developers to declare what save type (if any) the ROM expects. Emulators and flashcarts can thus auto-configure for the provided save type to provide a smooth experience to the user. * **Controllers:** this allows the developers to declare the suggested controllers and paks configuration to best experience the ROM. For instance a game might request to plug a standard N64 controller on port 1 with a rumble pak, and a browser ROM might suggest to plug keyboard and mouse instead. * **Metadata:** this allows the developers to ship information such as author, release date, website, descriptions, screenshots, boxarts, all directly embedded in the ROM. Flashcart menus and ROM browsers in emulators (or any other ROM tool) can use this information to provide a more rich browsing experience to users. See the [full spec for ROM metadata](https://n64brew.dev/wiki/ROM_Metadata "ROM Metadata") . ### Homebrew ROM Header special flags OffsetBytesNameDescription0x341Controller 1This byte contains the suggested / expected controller hardware that should be attached to port 1. Values 0x01-0x7F indicate a standard N64 controller, possibly with some installed pak. Values 0x80-0xFE indicate a different kind of controller. | | | | | | --- | --- | --- | --- | | 0x00 | No information provided.

Emulators should follow their standard configuration for this port. | 0x80 | N64 mouse | | 0x01 | N64 controller with Rumble Pak | 0x81 | VRU | | 0x02 | N64 controller with Controller Pak | 0x82 | Gamecube controller | | 0x03 | N64 controller with Transfer Pak | 0x83 | Randnet keyboard | | 0xFF | Nothing attached to this port | 0x84 | Gamecube keyboard | 0x351Controller 2This byte contains the suggested / expected controller hardware that should be attached to port 2. See byte 0x34 for more information.0x361Controller 3This byte contains the suggested / expected controller hardware that should be attached to port 3. See byte 0x34 for more information.0x371Controller 4This byte contains the suggested / expected controller hardware that should be attached to port 4. See byte 0x34 for more information.0x381FlagsMiscellaneous flags | | | | --- | --- | | Bit | Description | | 0 | The ROM contains an [embedded metadata ZIP](https://n64brew.dev/wiki/ROM_Metadata "ROM Metadata")
, with information and images about this ROM. | | 1-7 | Currently unused (should be 0) | 0x3C2Game IDThis must contain the ASCII characters "ED". It is used as an ID to identify that the Advanced Homebrew ROM header format Is being used by this ROM.0x3F1SavetypeThis byte mostly contains information on the savetype. It must be decoded as a bitfield: | | | | --- | --- | | Bit | Description | | 0 | The game uses the serial RTC (via Joybus) | | 1 | The game is region-free and will work on any TV type

(even though a region is declared in the game code). Menus are encouraged to boot this ROM using the native TV type of the console, rather than overriding it. | | 2 | Unused | | 3 | Unused | | 4-7 | Savetype expected by the game. possibile values:


0: None

1: 4K EEPROM

2: 16K EEPROM

3: 256K SRAM

4: 768K SRAM (banked)

5: Flash RAM

6: 1M SRAM | ### Support by emulators Emulators not listed here do not support the advanced homebrew ROM header format. | | | | | | --- | --- | --- | --- | | Emulator | Savetype | Controllers | Metadata | | Ares | Yes | Yes | No | | cen64 | Yes | No | No | | Parallel Launcher | Yes | Yes | No | | mupen64plus (core) | Yes | No | No | | Rosalie's Mupen GUI | Yes | No | No | ### Support by flashcarts Support by flashcarts can vary depending on the USB loader being used and/or the flashcart operating system. Notice that flashcarts can emulate a specific savetype but have nothing to do with controllers, so the maximum expected support is related to savetype. | | | | | | | --- | --- | --- | --- | --- | | Flashcart | Loader | Savetype | Controllers | Metadata | | 64drive | 64drive official C tool | No | No | No | | 64drive menu | No | No | No | | g64drive | Yes | No | No | | UNFLoader | Yes | No | No | | Everdrive 64 | Menu (OS V3.x) | Yes | No | No | | Menu (OS V2.x) | No | No | No | | ed64 | No | No | No | | UNFLoader | Yes | No | No | | SummerCart64 | N64FlashcartMenu | Yes | No | No | | sc64deployer | Yes | No | No | | UNFLoader | Yes | No | No | NOTE: Flashcart menus and tools normally will not implement the controller feature of the homebrew header, as they can't physically plug the right controllers or paks on behalf of the user. The most they could do is to show to the user the "suggested" best configuration of controllers and paks to play a certain game, Retrieved from "[https://n64brew.dev/wiki/ROM\_Header?oldid=5766](https://n64brew.dev/wiki/ROM_Header?oldid=5766) " --- # Initial Program Load - N64brew Wiki [](https://n64brew.dev/wiki/Initial_Program_Load#) Initial Program Load ==================== | | | | --- | --- | | ![](https://upload.wikimedia.org/wikipedia/commons/b/b4/Ambox_important.svg) | **Notice!**

This page is intended solely for research, archival, and preservation purposes. Disassembled code may be subject to copyright law. | The **Initial Program Load** (commonly called **IPL**, or the **boot sequence**) is a set of instructions that the console performs every time it starts. There are multiple stages to this process, referred to as: IPL1, IPL2 and IPL3. When a [64DD](https://n64brew.dev/wiki/64DD "64DD") is in use, there is also an IPL4 stage. Upon power-on, NMI, or "soft" reset, the program counter is set by hardware to `0xBFC00000`, which is the beginning of the PIF ROM address space. This ROM is baked into the [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS") chip. _When reading disassemblies, always remember that branch instructions have delay slots (which some branches may discard under certain circumstances). The operations performed in delay slots, may not be related to the branch itself._ Each line is formatted like so: `[address][opcode][assembly instruction] # comments`. Contents -------- * [1 IPL1](https://n64brew.dev/wiki/Initial_Program_Load#IPL1) * [2 IPL2](https://n64brew.dev/wiki/Initial_Program_Load#IPL2) * [2.1 IPL3](https://n64brew.dev/wiki/Initial_Program_Load#IPL3) * [2.1.1 Nintendo proprietary IPL3](https://n64brew.dev/wiki/Initial_Program_Load#Nintendo_proprietary_IPL3) * [2.1.2 Libdragon open-source IPL3](https://n64brew.dev/wiki/Initial_Program_Load#Libdragon_open-source_IPL3) * [3 IPL4](https://n64brew.dev/wiki/Initial_Program_Load#IPL4) IPL1 ==== This stage spans only the first `0xD4` bytes (`0xBFC00000 - 0xBFC000D3`) as executing instructions directly out of the PIF is relatively slow. Only what is absolutely necessary, is executed here. IPL1 resets the console to a consistent reset state, and moves the remaining bytes of the PIF ROM, that is IPL2, into the RSP's IMEM (`0xA4001000`). The goal of IPL1 is basically to move out execution context from the PIF as soon as possible for two reasons: first, running code directly from PIF requires a very slow serial transfer for each transferred word, and given that CPU cache doesn't work in this context, execution is **very** slow. Second, as soon as the CPU is out of this context, the PIF ROM can be locked again, for security reasons, hoping to minimize the window in which PIF returns the ROM on the bus and can thus be easily dumped. \### IPL1 ### 0xBFC00000 - 0xBFC000D3 (0xD4 bytes long) ### \# SEGMENT 1 # Initialize CP0 Status & Config registers \[0xBFC00000\]\[0x3C093400\]\[LUI t1, 0x3400\] \# t1 = 0x34000000 \[0xBFC00004\]\[0x40896000\]\[MTC0 t1, SR\] \# SR = t1 (enables CP0, CP1, and FPU registers) \[0xBFC00008\]\[0x3C090006\]\[LUI t1, 0x0006\] \# t1 = 0x00060000 \[0xBFC0000C\]\[0x3529E463\]\[ORI t1, t1, 0xE463\] \# t1 = 0x0006E463 \[0xBFC00010\]\[0x40898000\]\[MTC0 t1, Config\] \# Config = t1 (sets SysAD port writeback pattern to "D", sets Big-Endian mode, and sets KSEG0 as a cached region) \# SEGMENT 2 # \# 2a: Wait for RSP halt \[0xBFC00014\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \[0xBFC00018\]\[0x8D080010\]\[LW t0, t0, 0x0010\] \# t0 = value stored at 0xA4040010 (RSP\_STATUS register) \[0xBFC0001C\]\[0x31080001\]\[ANDI t0, t0, 0x0001\] \# t0 = t0 & 0x0001 (isolates the 'halt' bit) \[0xBFC00020\]\[0x5100FFFD\]\[BEQL t0, zr, 0xFFFD\] \# if t0 == 0, branch to 0xBFC00018 (this is a spin loop, waiting for the RSP to halt) \[0xBFC00024\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \[0xBFC00028\]\[0x2408000A\]\[ADDIU t0, zr, 0x000A\] \# t0 = 0x0000000A \[0xBFC0002C\]\[0x3C01A404\]\[LUI at, 0xA404\] \# at = 0xA4040000 \[0xBFC00030\]\[0xAC280010\]\[SW t0, at, 0x0010\] \# write t0 (0x0000000A) into 0xA4040010 (RSP\_STATUS register: sets 'halt' and clears 'rsp interrupt' bits) \[0xBFC00034\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \[0xBFC00038\]\[0x8D080018\]\[LW t0, t0, 0x0018\] \# t0 = value stored at 0xA4040018 (RSP\_DMA\_BUSY register) \[0xBFC0003C\]\[0x31080001\]\[ANDI t0, t0, 0x0001\] \# t0 = t0 & 0x0001 (isolates the 'halt' bit) \[0xBFC00040\]\[0x5500FFFD\]\[BNEL t0, zr, 0xFFFD\] \# if t0 != 0, branch to 0xBFC00038 (this is a spin loop, waiting for the RSP to not be halted) \[0xBFC00044\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \# 2b: Reset PI \[0xBFC00048\]\[0x24080003\]\[ADDIU t0, zr, 0x0003\] \# t0 = 0x00000003 \[0xBFC0004C\]\[0x3C01A460\]\[LUI at, 0xA460\] \# at = 0xA4600000 \[0xBFC00050\]\[0xAC280010\]\[SW t0, at, 0x0010\] \# write t0 (0x00000003) into 0xA4600010 (PI\_STATUS register: clears PI interrupt and resets PI controller) \# 2c: Clear video output \[0xBFC00054\]\[0x240803FF\]\[ADDIU t0, zr, 0x03FF\] \# t0 = 0x000003FF \[0xBFC00058\]\[0x3C01A440\]\[LUI at, 0xA440\] \# at = 0xA4400000 \[0xBFC0005C\]\[0xAC28000C\]\[SW t0, at, 0x000C\] \# write t0 (0x000003FF) into 0xA440000C (VI\_V\_INTR register: sets vertical interrupt trigger to half-line 0x3FF) \[0xBFC00060\]\[0x3C01A440\]\[LUI at, 0xA440\] \# at = 0xA4400000 \[0xBFC00064\]\[0xAC200024\]\[SW zr, at, 0x0024\] \# write zr (0x00000000) into 0xA4400024 (VI\_H\_VIDEO register: sets the start and end of active video image to zero) \[0xBFC00068\]\[0x3C01A440\]\[LUI at, 0xA440\] \# at = 0xA4400000 \[0xBFC0006C\]\[0xAC200010\]\[SW zr, at, 0x0010\] \# write zr (0x00000000) into 0xA4400010 (VI\_V\_CURRENT register: clears the VI interrupt) \# 2d: Stop audio \[0xBFC00070\]\[0x3C01A450\]\[LUI at, 0xA450\] \# at = 0xA4500000 \[0xBFC00074\]\[0xAC200000\]\[SW zr, at, 0x0000\] \# write zr (0x00000000) into 0xA4500000 (AI\_DRAM\_ADDR register: sets DMA RDRAM address to zero) \[0xBFC00078\]\[0x3C01A450\]\[LUI at, 0xA450\] \# at = 0xA4500000 \[0xBFC0007C\]\[0xAC200004\]\[SW zr, at, 0x0004\] \# write zr (0x00000000) into 0xA4500004 (AI\_LENGTH register: sets transfer length to zero) \# 2e: Wait for any RSP DMAs to complete \[0xBFC00080\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \[0xBFC00084\]\[0x8D080010\]\[LW t0, t0, 0x0010\] \# t0 = value stored at 0xA4040010 (RSP\_STATUS register) \[0xBFC00088\]\[0x31080004\]\[ANDI t0, t0, 0x0004\] \# t0 = t0 & 0x0004 (isolates the 'dma busy' bit) \[0xBFC0008C\]\[0x5500FFFD\]\[BNEL t0, zr, 0xFFFD\] \# if t0 != 0, branch to 0xBFC00084 (this is a spin loop, waiting for any RSP DMAs to complete) \[0xBFC00090\]\[0x3C08A404\]\[LUI t0, 0xA404\] \# t0 = 0xA4040000 \# SEGMENT 3 # Copy IPL2 from PIF to RSP IMEM \# 3a: Initialize start/end addresses \[0xBFC00094\]\[0x3C0BA400\]\[LUI t3, 0xA400\] \# t3 = 0xA4000000 \[0xBFC00098\]\[0x3C0CBFC0\]\[LUI t4, 0xBFC0\] \# t4 = 0xBFC00000 \[0xBFC0009C\]\[0x3C0DBFC0\]\[LUI t5, 0xBFC0\] \# t5 = 0xBFC00000 \[0xBFC000A0\]\[0x256B1000\]\[ADDIU t3, t3, 0x1000\] \# t3 = 0xA4001000 (start of RSP IMEM) \[0xBFC000A4\]\[0x258C00D4\]\[ADDIU t4, t4, 0x00D4\] \# t4 = 0xBFC000D4 (start of IPL2 in PIF ROM) \[0xBFC000A8\]\[0x25AD071C\]\[ADDIU t5, t5, 0x071C\] \# t5 = 0xBFC0071C (end of IPL2 in PIF ROM) \# 3b: Load/Store 1 word (4 bytes) at a time, moving instruction data from \[0xBFC000D4 - 0xBFC0071B\], into \[0xA4001000 - 0xA4001647\] \[0xBFC000AC\]\[0x8D890000\]\[LW t1, t4, 0x0000\] \# t1 = value stored at address t4 \[0xBFC000B0\]\[0x258C0004\]\[ADDIU t4, t4, 0x0004\] \# increment t4 (+4) \[0xBFC000B4\]\[0x256B0004\]\[ADDIU t3, t3, 0x0004\] \# increment t3 (+4) \[0xBFC000B8\]\[0x158DFFFC\]\[BNE t4, t5, 0xFFFC\] \# if t4 != t5, branch to 0xBFC000AC (repeat the loop) \[0xBFC000BC\]\[0xAD69FFFC\]\[SW t1, t3, 0xFFFC\] \# write t1 into address (t3 - 4) \# SEGMENT 4 # Jump to IPL2 in IMEM \[0xBFC000C0\]\[0x3C0BA400\]\[LUI t3, 0xA400\] \# t3 = 0xA4000000 \[0xBFC000C4\]\[0x3C1DA400\]\[LUI sp, 0xA400\] \# sp = 0xA4000000 \[0xBFC000C8\]\[0x256B1000\]\[ADDIU t3, t3, 0x1000\] \# t3 = 0xA4001000 (beginning of IMEM and IPL2) \[0xBFC000CC\]\[0x01600008\]\[JR t3\] \# jump to IMEM/IPL2 (and executes delay slot) \[0xBFC000D0\]\[0x37BD1FF0\]\[ORI sp, sp, 0x1FF0\] \# sp = 0xA4001FF0 (this prepares sp for use in IPL2) IPL2 ==== After IPL1 finishes moving the instructions of this stage to RSP IMEM, it jumps to `0x04001000` to start this second stage. IPL2 has just a very simple task: it loads IPL3 from the ROM (offsets 0x40-0x1000) and verifies its checksum. To do so, it actually sends the checksum to the PIF, which verifies if it's correct by separately fetching the expected one from the CIC. If the checksum doesn't match, PIF halts execution of the CPU (by asserting the NMI pin). IPL2 instead just proceeds to calling IPL3, hoping for the best. To access the ROM and be able to read the IPL3, IPL2 needs to configure the PI bus access timings. To do first, first it configures PI to its minimum speed, and reads the [first 4 bytes of the header](https://n64brew.dev/wiki/ROM_Header "ROM Header") . Byte 1-3 in fact are expected to contain the (fastest) PI timings that the ROM supports. It then configures those timings, and proceeds reading the IPL3 into DMEM, one word at a time (notice that DMA wouldn't be possible here, because PI DMA only reads/writes to RDRAM, not DMEM). IPL3 ---- This stage is executed out of RSP DMEM, which was loaded with the first `0x1000` bytes of the game cartridge (or alternatively, whatever may be inserted in the cartridge or expansion slot on the bottom of the console). IPL2 will jump to `0x04000040` (`0x40` bytes after the start of DMEM, to account for the ROM header). The goal of IPL3 is to initialize RDRAM, which requires a complex [initialization process](https://n64brew.dev/wiki/RDRAM#Initialization_Sequence "RDRAM") that includes current calibration. During this stage, IPL3 also discovers whether there is an Expansion Pak installed and thus whether the additional 4 MiB of RDRAM are available. After RDRAM is initialized, it proceeds to booting the game. ### Nintendo proprietary IPL3 Over the course of the commercial life of the console, Nintendo released six known variants of the IPL3 stage, each one with its own matching CIC chip. In fact, as discussed previously for IPL2, each IPL3 bootcode has its checksum verified against the hardcoded value written in the CIC chip. This basically means that you can't easily change a physical CIC chip on the cartridge, without also replacing the IPL3 code. A seventh variant can be seen when analyzing the dumps of the GameBooster 64, Action Replay Pro 64, and GameShark Pro (v3.3): where the `0x0FC0` bytes after the header, are all zeros, and are dynamically loaded by the cartridge. For each of the six variants, there is a matching CIC chip found in each cartridge. There are 10 official CIC chips: | NTSC | PAL | % of ROMs (Qty) | | --- | --- | --- | | CIC-NUS-6102 | CIC-NUS-7101 | 87.6% (826) | | CIC-NUS-6103 | CIC-NUS-7103 | 6.5% (61) | | CIC-NUS-6105 | CIC-NUS-7105 | 4.5% (43) | | CIC-NUS-6106 | CIC-NUS-7106 | 0.09% (8) | | CIC-NUS-6101 | CIC-NUS-7102 | 0.05% (5) | Each of these NTSC/PAL pairs share an IPL3 variant, except **6101** and **7102**. After Nintendo IPL3 has initialized the RDRAM, it proceeds loading 1 MiB of game code i(ROM addresses 0x10001000 - 0x10101000), into RDRAM starting from the boot address specified in the [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") (offset 0x8), and the jumping to it. This actually finishes the boot sequence and hands off the execution to the actual game. The amount of code being loaded (1 MiB) is hardcoded and cannot be changed. Games are expected to cope with it. In most cases, this is more than needed, and extra loaded data is basically ignored (and maybe reloaded as part of the game asset loading code, to different addresses). If the game requires more than 1 MiB of code, it is expected to load it manually (eg: via dynamically loadable code segments, sometimes called "overlays"). Details to be added here... Details to be added here... Details to be added here... Details to be added here... Details to be added here... Details to be added here... ### Libdragon open-source IPL3 The open source homebrew SDK [Libdragon](https://libdragon.dev/) ships with an [open source IPL3](https://github.com/DragonMinded/libdragon/tree/trunk/boot) , that is embedded by default in all the ROMs made with libdragon. Compared to the proprietary one, it is much faster (boots in ~100ms vs ~500ms for an average ROM) and expects the game in ELF format, so that it is able to properly read text and data segments of any size (not just a single hardcoded one) and clear BSS. It is unencumbered and was implemented with a clean room approach, so it is supposedly free of licensing concerns. To allow booting on a real console in a true hardware boot scenario (eg: replica cartridges), each release binary is bruteforced (with GPUs) so that its checksum matches the expected one for CIC 6102. IPL2 will then successfully recognizes it as a valid IPL3 for that CIC, and allows boot to proceed. IPL4 ==== **TODO** Retrieved from "[https://n64brew.dev/wiki/Initial\_Program\_Load?oldid=5596](https://n64brew.dev/wiki/Initial_Program_Load?oldid=5596) " --- # N64brew Game Jam 2024 - N64brew Wiki [](https://n64brew.dev/wiki/N64brew_Game_Jam_2024#) N64brew Game Jam 2024 ===================== The fifth annual N64 homebrew game jam put together by the N64brew community on Discord. The theme, “Competitive Minigames”, was announced on October 18, 2024. The game jam was announced and began on October 13, 2024 and ended on December 13, 2024. Team limits were limited to 5 members, and only limited to 1 team. [![The N64Brew logo. The words “N64Brew” are next to a multicoloured Jam Jar, with the tag of #5 on the lid.](https://static.wikitide.net/n64wiki/e/ee/N64Brew-summer-game-jam-2024.png)](https://n64brew.dev/wiki/File:N64Brew-summer-game-jam-2024.png) All participants were able to choose to enter a raffle to earn a free SummerCart64 from donation by Command\_Tab, and N64 Watch from the 1996 US Launch of the Nintendo 64, donated by Rocky. As in other years, the prize pot for the winner of this year's jam will be donated to a charity per the winner's choosing. The prize pot for the 2024 N64brew Game Jam was totalled at $1753,18 USD. The jam winner, HailToDodongo, decided to donate the $1753,18 pot that was raised in this game jam (which we rounded up to the nearest dollar) to the Alzheimer's Association. The AA is a non-profit that is dedicated to helping research Alzheimer's + Dementia, as well as providing support for patients via consultations and support groups. All entries within this years Game Jam were required to use the Libdragon SDK. Usage of Tiny3D was entirely optional but was highly suggested, and the Minigame Jam Template as a base, and that 4 players must be supported. As with the 2023 Game Jam, the N64Brew community were able to vote on entries. Teams were able to submit multiple entries. The Voting Form for entries was made available on December 30, 2024, and closed for voting on January 15, 2025. Game Jam participants were also allowed to vote, but scores assigned to their own submission were ignored for obvious reasons. The score sheet was published once voting ended, with anonymized voters, so jam participants could view and review feedback submitted. The winner(s) of the Game Jam were announced on January 17, 2025, after a delayed original date of December 20, 2024. Prominent Nintendo 64 enthusiast and videogame reviewer, Hard4Games, reviewed the Game Jam submissions in video-format (recorded on January 18, 2025) which was published on January 31, 2025, and provided their own judgement & scores of the submissions. Contents -------- * [1 Submissions](https://n64brew.dev/wiki/N64brew_Game_Jam_2024#Submissions) * [2 Results](https://n64brew.dev/wiki/N64brew_Game_Jam_2024#Results) * [3 Trivia](https://n64brew.dev/wiki/N64brew_Game_Jam_2024#Trivia) * [4 External Links](https://n64brew.dev/wiki/N64brew_Game_Jam_2024#External_Links) Submissions ----------- | Entry | Solo/Team | Participant(s) | Source Code | | --- | --- | --- | --- | | [64 Beats](https://n64brew.dev/wiki/64_Beats?action=edit&redlink=1 "64 Beats (page does not exist)") | Solo | JvPeek | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/24](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/24) | | [Coin Rush](https://n64brew.dev/wiki/Coin_Rush?action=edit&redlink=1 "Coin Rush (page does not exist)") | Solo | HailToDodongo | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/27](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/27) | | [Destroy!](https://n64brew.dev/wiki/Destroy!?action=edit&redlink=1 "Destroy! (page does not exist)") | Team | Ultrarare (lambertjamesd, pixelcrustpunk, SapphireTactics, jtn191, nick12355) | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/16](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/16) | | [Furball](https://n64brew.dev/wiki/Furball?action=edit&redlink=1 "Furball (page does not exist)") | Solo | radishriver | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/20](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/20) | | [Hydra Harmonics](https://n64brew.dev/wiki/Hydra_Harmonics?action=edit&redlink=1 "Hydra Harmonics (page does not exist)") | Team | Catch-64 (GammaTrove, N64 Squid, Brozilla, StatycTyr) | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/21](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/21) | | [Land Grab](https://n64brew.dev/wiki/Land_Grab?action=edit&redlink=1 "Land Grab (page does not exist)") | Solo | meeq | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/22](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/22) | | [Larceny](https://n64brew.dev/wiki/Larceny?action=edit&redlink=1 "Larceny (page does not exist)") | Solo | ripper253 | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/26](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/26) | | [Le Tohu-Bohu](https://n64brew.dev/wiki/Le_Tohu-Bohu?action=edit&redlink=1 "Le Tohu-Bohu (page does not exist)") | Solo | tfmoe\_\_ | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/18](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/18) | | [Lucker’s Arena](https://n64brew.dev/wiki/Lucker%E2%80%99s_Arena?action=edit&redlink=1 "Lucker’s Arena (page does not exist)") | Solo | ritter7124 | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/25](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/25) | | [Mallard](https://n64brew.dev/wiki/Mallard?action=edit&redlink=1 "Mallard (page does not exist)") | Solo | JoshKautz | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/12](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/12) | | [May The Best Snowman Win](https://n64brew.dev/wiki/May_The_Best_Snowman_Win?action=edit&redlink=1 "May The Best Snowman Win (page does not exist)") | Solo | Razz64 | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/28](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/28) | | [Paintball](https://n64brew.dev/wiki/Paintball?action=edit&redlink=1 "Paintball (page does not exist)") | Solo | anacierdem | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/11](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/11) | | [Sauna Rush](https://n64brew.dev/wiki/Sauna_Rush?action=edit&redlink=1 "Sauna Rush (page does not exist)") | Solo | Milton | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/13](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/13) | | [Sneks](https://n64brew.dev/wiki/Sneks?action=edit&redlink=1 "Sneks (page does not exist)") | Solo | mr\_J05H | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/23](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/23) | | [SpaceWaves](https://n64brew.dev/wiki/SpaceWaves?action=edit&redlink=1 "SpaceWaves (page does not exist)") | Solo | SpookyIluha | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/19](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/19) | | [SwordStrike](https://n64brew.dev/wiki/SwordStrike?action=edit&redlink=1 "SwordStrike (page does not exist)") | Solo | SuperBoognish | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/14](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/14) | | [The Third Arm](https://n64brew.dev/wiki/The_Third_Arm?action=edit&redlink=1 "The Third Arm (page does not exist)") | Team | Riistahillo (samuli\_, routalanttu) | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/15](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/15) | | [Triple H - “Halcyon Hexagons”, “Holes”, “Hot Hot Hexagons”](https://n64brew.dev/wiki/Triple_H_-_%E2%80%9CHalcyon_Hexagons%E2%80%9D,_%E2%80%9CHoles%E2%80%9D,_%E2%80%9CHot_Hot_Hexagons%E2%80%9D?action=edit&redlink=1 "Triple H - “Halcyon Hexagons”, “Holes”, “Hot Hot Hexagons” (page does not exist)") | Team | Strawberry Byte (kae\_lin, .zoncabe, s4ys, Mew) | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/17](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/17) | | [Underground Grind](https://n64brew.dev/wiki/Underground_Grind?action=edit&redlink=1 "Underground Grind (page does not exist)") | Solo | RaisedWizardry | [https://github.com/n64brew/N64brew-GameJam2024-Template/pull/29](https://github.com/n64brew/N64brew-GameJam2024-Template/pull/29) | Results ------- | | | | --- | --- |Finalists | Entry | Rank | | Coin Rush | 1st Place | | SpaceWaves | 2nd Place | | Hydra Harmonics | 3rd Place | Trivia ------ * 43 sign-ups total, with 19 of them submitting something. That's a 43% survival rate. * 34 of the sign-ups were solo. * 4 of the survivors were teams. Two teams of 4, one of 5, one of 2 * Total of 21 games. Only one team seems to have submitted more than one game * 50% of the submitters marked themselves as "Beginner in N64 development" The N64brew Game Jam 2024 Contestant Interviews video is also available as a Nintendo 64 playable file on the console itself. You can now watch the entire 4-hour interview of the N64Brew GameJam #5 right on your favourite N64 console itself! The initially 250GB FMV was fit into 64MB of cartridge space using Opus and MPEG1 wizardry inside the Libdragon SDK, which was done by Spookyiluha. External Links -------------- 1. [N64Brew Game Jam 2024 Announcement Video](https://www.youtube.com/watch?v=ni85fl5ct4Q) 2. [N64brew Game Jam 2024 Winners N64Squid](https://n64squid.com/homebrew/competitions/n64brew-game-jam-2024/) 3. [N64brew Game Jam #5 -2024 - Contestant Interviews](https://www.youtube.com/watch?v=4AT76zH4xy8) 4. [N64Brew Game Jam 2024 Score Form & Responses](https://tinyurl.com/32cy7vyb) 5. [21 NEW N64 games \*you can play now\*](https://www.youtube.com/watch?v=A_XSY3w8-Pg) Retrieved from "[https://n64brew.dev/wiki/N64brew\_Game\_Jam\_2024?oldid=5670](https://n64brew.dev/wiki/N64brew_Game_Jam_2024?oldid=5670) " --- # Controller Pak/Filesystem - N64brew Wiki [](https://n64brew.dev/wiki/Controller_Pak/Filesystem#) Controller Pak/Filesystem ========================= < [Controller Pak](https://n64brew.dev/wiki/Controller_Pak "Controller Pak") Save data management on the Controller Pak is facilitated through a simple proprietary filesystem. Most games that support this Pak have a menu that can be accessed by holding START while turning on the N64. In all official and unofficial Controller Paks, there are 32,768 bytes of SRAM available, which is split into 256-byte sectors referred to as "pages". This allows for a maximum capacity of 128 pages, 5 of which are reserved for the filesystem data, and the remaining 123 for user software. A software can create one or more save files, up to a total of 16 files, referred to as "notes". Contents -------- * [1 Filesystem data format](https://n64brew.dev/wiki/Controller_Pak/Filesystem#Filesystem_data_format) * [1.1 Overall Structure](https://n64brew.dev/wiki/Controller_Pak/Filesystem#Overall_Structure) * [1.2 Page 0: ID Sector](https://n64brew.dev/wiki/Controller_Pak/Filesystem#Page_0:_ID_Sector) * [1.3 Page 1+2: FAT](https://n64brew.dev/wiki/Controller_Pak/Filesystem#Page_1+2:_FAT) * [1.4 Page 3+4: Note Table](https://n64brew.dev/wiki/Controller_Pak/Filesystem#Page_3+4:_Note_Table) * [1.5 Libdragon extensions to note table](https://n64brew.dev/wiki/Controller_Pak/Filesystem#Libdragon_extensions_to_note_table) * [2 Additional Documentation](https://n64brew.dev/wiki/Controller_Pak/Filesystem#Additional_Documentation) Filesystem data format ---------------------- ### Overall Structure | Overall structure (Standard 32 KiB) | | | --- | --- | | Page no. | Description | | Page 0 | ID Sector - critical sanity and identification | | Page 1 | FAT - allocation of pages (chains) | | Page 2 | FAT Backup - exact copy of the FAT | | Page 3-4 | Note Table - allocation of Note entries | | Page 5-127 | Note data sectors | For larger (banked) cpaks, the Index Table can span multiple pages. For instance, in a 512 KiB controller paks, there are of 2048 pages. Since each Index Table page can contain inodes up to 128 pages, we need 2048 / 128 = 16 Index Table pages. The layout thus becomes: | Overall structure (512 KiB) | | | --- | --- | | Page no. | Description | | Page 0 | ID Sector - critical sanity and identification | | Page 1-16 | Index Table - allocation of user data sectors | | Page 17-32 | Backup Index Table - exact copy of Index Table | | Page 33-34 | Note Table - allocation of Note entries | | Page 35-2047 | Note data sectors | ### Page 0: ID Sector The ID sector primarily houses basic identifying and system information for the file system and the specific Controller Pak. | ID sector structure (256 bytes) | | | | --- | --- | --- | | Offset | Size | Description | | 0x00 | 32 | Label area | | 0x20 | 32 | ID Block (Primary) | | 0x40 | 32 | _unused_ | | 0x60 | 32 | ID Block (Backup 1) | | 0x80 | 32 | ID Block (Backup 2) | | 0xA0 | 32 | _unused_ | | 0xC0 | 32 | ID Block (Backup 3) | | 0xE0 | 32 | _unused_ | The first 32-bytes of the ID Sector is known as the Label. It was never officially used nor were its specifications ever supplied to developers. It is implied to be a sort of text-based label or tag. In practice, only garbage data is ever written here during a repair operation, or as a side-effect from example code being copy-pasted to produce the numbers 0 to 32. The 32-byte ID block provides system information such as the serial number, device ID, the capacity of the media, and two protective checksums. In total, four identical copies of the ID block are stored in this sector in case one or more become corrupted. | ID Block structure (32 bytes) | | | | --- | --- | --- | | Offset | Size | Description | | 0x00 | 24 | Serial number | | 0x18 | 1 | _unused (normally 0)_ | | 0x19 | 1 | Device ID | | 0x1A | 1 | Bank size (normally 1) | | 0x1B | 1 | _unused (normally 0)_ | | 0x1C | 2 | Checksum 1 | | 0x1E | 2 | Checksum 2 | The serial number uniquely identifies the particular controller pak. It can be filled with random bytes or any other value that is unique across all extant controller paks. The device ID field identifies the type of device. Bit 0 should always be set to 1 to indicate a controller pak, the other bits are normally 0 but can contain garbage. The bank size describes the capacity of the controller pak in number of 32 KiB banks. Since a standard first-party cpak is 32 KiB, this field will be 1, but in general Nintendo SDK supported up to 62 banks. The first checksum is a 16-bit big endian word computed by summing the first fourteen 16-bit words in the structure. That is every word in the block that is not either of the checksums. The second checksum is also a 16-bit word computed by subtracting the first checksum from the value 0xFFF2. ### Page 1+2: FAT The FAT is used as the allocation table for the file system utilizing a page chaining strategy similar to [FAT16](https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#File_Allocation_Table) . The FAT consists of one full page representing an array of 128 two-byte words, known as FAT entries, where the index of the word in the array corresponds to the page with the same index number on the controller pak. | Index Table page (256 bytes) | | | | --- | --- | --- | | Offset | Size | Description | | 0x00 | 1 | _unused (always 0)_ | | 0x01 | 1 | Checksum | | 0x02 | 254 | Entries for pages 1-127 | In a standard 32 KiB cpak, the first 5 pages are reserved (see the Overall Structure above), so entries for them will contain 0. The lower-byte of the first entry stores the (8-bit) checksum for the entire table. The checksum is computed by summing all the bytes for all of the 127 remaining entries (i.e. starting at byte offset 0x02 through the end of the page). In a multi-bank cpak, theFAT is made of multiple pages with the exact same structure (see the Overall Structure above). Each page will still have its own checksum, which means that the first page in each bank will be wasted. For instance, in a 512 KiB cpak, there are 35 reserved page (see the Overall Structure above), so the first 35 entries of the first FAT page will be 0 (though the first one will contain the checksum). The second FAT page will contain entries for pages 128-255, but the entry for page 128 is used to store the checksum, so the whole page 128 (first page of the second bank of the cpak) will never be used. Two identical copies of the FAT are stored in case of data corruption. Since each page has its own checksum, readers can switch between the main copy and the backup copy at page granularity, by verifying the checksum. FAT entries contains a value indicating that it is unallocated (not used by a note), a value indicating it is the last in a chain (the last page at the end of a note), or the index of the next page in a multiple page chain. The first page for a note is always stored in the note entry data structure and the entire note can then be found by walking the chain in the FAT until the end value is encountered. | FAT entry values | | | --- | --- | | Value | Description | | 0 | Indicates a reserved page | | 1 | Indicates this is the last page in sequence. | | 3 | Indicates this page is unallocated free space. | | \>= 5 | Indicates the next page in the sequence | ### Page 3+4: Note Table The Note Table is simply a list of 16 Note entries. Each entry consists of 32 bytes and identifies the start page, game code and other data. The positions in the list correspond to the slot numbers displayed in the memory card manager. Unused entries are zeroed out. | Note entry format (32 bytes) | | | | --- | --- | --- | | Offset | Size | Description | | 0x00 | 4 | Game Code - ASCII, e.g. NSME | | 0x04 | 2 | Publisher Code - ASCII, e.g. 01 | | 0x06 | 2 | Start page - Index number of the first page of the note | | 0x08 | 1 | Status - Bit field of flags | | 0x09 | 3 | Unused, contains garbage data? | | 0x0C | 4 | File extension - Text using custom encoding | | 0x10 | 16 | File name - Text using custom encoding | The game code is a short ASCII encoded string that identifies the specific game that created and owns the note. This is the same code as the media format code that appears in the [ROM header](http://en64.shoutwiki.com/wiki/ROM#Cartridge_ROM_Header) for each game. These codes are specific not only to the particular game but also to the type of media (e.g. cartridge) and geographic region. The publisher code are unique codes that were assigned by Nintendo to their licensed partners. The publisher codes and are also typically ASCII characters but do not appear in the ROM header and so are more difficult to catalogue. **NOTE:** Datel GameShark uses non-ASCII gamecode/pubcode (0x3BADD1E5, 0xFADE respectively). In general, most user interfaces won't display those anyway, and nothing wrong happened with the Nintendo SDK. The status field contains bit flags. The flag 0x02 should always be set. The file extension is optional and uses the same custom character encoding used by the file name. Four characters are reserved but the stock controller pak manager used by most games only displays the first character. Some games, such as Ogre Battle 64, which implement their own manager do display the extended characters when present. Unused characters should be padded with NUL. The file name appears as the primary text string describing the contents of the note in the slot when the note is displayed in the controller pak manager of any game. The note text is encoded using a custom character encoding specific to the controller pak file system that includes punctuation plus basic Latin and Japanese characters. Unused characters should be padded with NUL. | Note name text encoding | | | | | | | | | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | | \_0 | \_1 | \_2 | \_3 | \_4 | \_5 | \_6 | \_7 | \_8 | \_9 | \_A | \_B | \_C | \_D | \_E | \_F | | 0\_ | NUL | | | | | | | | | | | | | | | SPC | | 1\_ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | | 2\_ | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | | 3\_ | W | X | Y | Z | ! | " | # | ' | \* | + | , | \- | . | / | : | \= | | 4\_ | ? | @ | 。 | ゛ | ゜ | ァ | ィ | ゥ | ェ | ォ | ッ | ャ | ュ | ョ | ヲ | ン | | 5\_ | ア | イ | ウ | エ | オ | カ | キ | ク | ケ | コ | サ | シ | ス | セ | ソ | タ | | 6\_ | チ | ツ | テ | ト | ナ | ニ | ヌ | ネ | ノ | ハ | ヒ | フ | ヘ | ホ | マ | ミ | | 7\_ | ム | メ | モ | ヤ | ユ | ヨ | ラ | リ | ル | レ | ロ | ワ | ガ | ギ | グ | ゲ | | 8\_ | ゴ | ザ | ジ | ズ | ゼ | ゾ | ダ | ヂ | ヅ | デ | ド | バ | ビ | ブ | ベ | ボ | | 9\_ | パ | ピ | プ | ペ | ポ | | | | | | | | | | | | | A\_ | | | | | | | | | | | | | | | | | | B\_ | | | | | | | | | | | | | | | | | | C\_ | | | | | | | | | | | | | | | | | | D\_ | | | | | | | | | | | | | | | | | | E\_ | | | | | | | | | | | | | | | | | | F\_ | | | | | | | | | | | | | | | | | ### Libdragon extensions to note table Libdragon extended the note entry as follows: | Note entry format (32 bytes) | | | | --- | --- | --- | | Offset | Size | Description | | 0x00 | 4 | Game Code - ASCII, e.g. NSME | | 0x04 | 2 | Publisher Code - ASCII, e.g. 01 | | 0x06 | 2 | Start page - Index number of the first page of the note | | 0x08 | 1 | Status - Bit field of flags | | **0x09** | **1** | **Size of padding bytes in the last page.**

**This allows to reconstruct the exact file size.** | | **0x0A** | **2** | **CRC16 (poly 0x5935, not reflected).**

**Calculated over the whole note including CRC bytes set to 0.** | | 0x0C | 4 | File extension - Text using custom encoding | | 0x10 | 16 | File name - Text using custom encoding | The additional CRC is used as a way to verify that extensions are in use. In fact, we assume that if the CRC doesn't match, the note wasn't written by libdragon and thus extensions cannot be trusted. The only actual extension is the padding size. This byte stores the amount of padding in the last page. By knowing this number, it is possible to reconstruct the actual file size, which provides a nicer interface for developers. Additional Documentation ------------------------ [Official N64 Programming Manual](https://ultra64.ca/files/documentation/online-manuals/man/pro-man/pro26/index26.3.html) Retrieved from "[https://n64brew.dev/wiki/Controller\_Pak/Filesystem?oldid=5639](https://n64brew.dev/wiki/Controller_Pak/Filesystem?oldid=5639) " --- # 64DD/Commands - N64brew Wiki [](https://n64brew.dev/wiki/64DD/Commands#) 64DD/Commands ============= < [64DD](https://n64brew.dev/wiki/64DD "64DD") Command List ------------ Some commands are only available in either Retail, Development or Writer 64DD drives. All commands past 0x20 are considered hidden, and require the use of Command 0x50 to unlock them. Further verification will need to be done as it is entirely dependent on the firmware ROM in the H8/300 CPU inside the 64DD. | Command | Description | Devices | | --- | --- | --- | | 0x00 | No Operation | All | | 0x01 | Seek (Read) | All | | 0x02 | Seek (Write) | All | | 0x03 | Recalibration | All | | 0x04 | Sleep / Brake | All | | 0x05 | Start | All | | 0x06 | Set Standby Delay | All | | 0x07 | Set Sleep Delay | All | | 0x08 | Clear Disk Change Flag | All | | 0x09 | Clear Reset & Disk Change Flag | All | | 0x0A | Read ASIC Version | All | | 0x0B | Set Disk Type | All | | 0x0C | Request Status | All | | 0x0D | Standby | All | | 0x0E | Index Lock Retry | All | | 0x0F | Set RTC**(1)** Year/Month Time | All | | 0x10 | Set RTC**(1)** Day/Hour Time | All | | 0x11 | Set RTC**(1)** Minute/Second Time (Initiate Cache Write) | All | | 0x12 | Get RTC**(1)** Year/Month Time | All | | 0x13 | Get RTC**(1)** Day/Hour Time | All | | 0x14 | Get RTC**(1)** Minute/Second Time (Initiate Cache Read) | All | | 0x15 | Set LED Timer | All | | 0x18 | Write Offset Seek | Writer | | 0x19 | Check Offset | Writer | | 0x1B | Read Program Version | All | | 0x1C | EEPROM Read | Retail | | 0x1D | EEPROM Write | Retail | | 0x20 | Unknown | ? | | 0x21 | Unknown | ? | | 0x23 | Unknown | ? | | 0x24 | Unknown | ? | | 0x25 | Unknown | ? | | 0x26 | Unknown | ? | | 0x27 | Unknown | ? | | 0x28 | Unknown | All | | 0x29 | Unknown | ? | | 0x2A | Unknown | All | | 0x2B | Unknown | All | | 0x2C | Unknown | All | | 0x2D | Unknown | All | | 0x2E | EXT1 Read | Development | | 0x2F | EXT1 Write | Development | | 0x30 | Set Memory Address | All | | 0x31 | Read Byte from Memory Address | All | | 0x32 | Write Byte to Memory Address | All | | 0x33 | Unknown | ? | | 0x34 | Unknown | ? | | 0x35 | Unknown | ? | | 0x36 | Unknown | ? | | 0x38 | Unknown | ? | | 0x39 | Unknown | ? | | 0x3A | Unknown | ? | | 0x3B | Unknown | ? | | 0x3C | Unknown | ? | | 0x3D | Unknown | ? | | 0x3E | Unknown | All | | 0x3F | Unknown | All | | 0x40 | Unknown | ? | | 0x41 | Unknown | ? | | 0x42 | Unknown | ? | | 0x44 | EEPROM Read | All | | 0x45 | EEPROM Write | All | | 0x47 | Unknown | All | | 0x48 | Unknown | All | | 0x49 | Unknown | ? | | 0x4A | Unknown | ? | | 0x4B | Unknown | ? | | 0x4D | Unknown | All | | 0x50 | Unlock Extra Commands | All | **(1)** _Real Time Clock_ Retrieved from "[https://n64brew.dev/wiki/64DD/Commands?oldid=5547](https://n64brew.dev/wiki/64DD/Commands?oldid=5547) " --- # N64brew Game Jam 2025 - N64brew Wiki [](https://n64brew.dev/wiki/N64brew_Game_Jam_2025#) N64brew Game Jam 2025 ===================== The sixth annual N64 home-brew game jam put together by the N64brew community on Discord, and announced and began on December 1, 2025, and ended February 1, 2026. The theme, “Repair”, was announced December 8, 2025. Teams were limited to 5 members, and only limited to 1 team, as well as 1 submission. [![The N64Brew logo. The words “N64Brew” are next to a yellow coloured Jam Jar, with the tag of #6 on the lid.](https://static.wikitide.net/n64wiki/d/df/N64BrewGameJam6LogoText.png)](https://n64brew.dev/wiki/File:N64BrewGameJam6LogoText.png) As of February 18, 2026, the prize pool is $3340.29 USD. This years game jam reverted to the “Classic” game jam style of past years, having 3 judges score each entry, instead of user-submitted votes. The judges for the 2025 Game Jam were Krom, Mikeryan, and ppcasm, which were announced on January 27, 2026. Each judge will fill in a score card, and the scores of all will be tallied up. All participants within the Game Jam were eligible to be entered into a prize raffle for a SummerCart64, donated by Command\_Tab, as well as 3 Analogue3D’s (2 black versions, one white version) if they submitted something to the Game Jam. Contents -------- * [1 Submissions](https://n64brew.dev/wiki/N64brew_Game_Jam_2025#Submissions) * [2 Results](https://n64brew.dev/wiki/N64brew_Game_Jam_2025#Results) * [3 Trivia](https://n64brew.dev/wiki/N64brew_Game_Jam_2025#Trivia) * [4 Videos](https://n64brew.dev/wiki/N64brew_Game_Jam_2025#Videos) * [5 External Links](https://n64brew.dev/wiki/N64brew_Game_Jam_2025#External_Links) Submissions ----------- | Entry | Solo/Team | Participant(s) | Source Code | | --- | --- | --- | --- | | AsteRisk | Solo | Brainpann | [https://github.com/N64brew-Game-Jam-2025/AsteRisk](https://github.com/N64brew-Game-Jam-2025/AsteRisk) | | Biosphere 64 \* | Solo | Milton7854 | [https://github.com/N64brew-Game-Jam-2025/biosphere64](https://github.com/N64brew-Game-Jam-2025/biosphere64) | | BotBoy!64 | Team | Psyops Studio (CYPRESS, dakodacomposer, Nupi, StatycTyr, DC.all) | [https://github.com/N64brew-Game-Jam-2025/BotBoy64](https://github.com/N64brew-Game-Jam-2025/BotBoy64) | | Box Fix Box with Box | Solo | Razz | [https://github.com/N64brew-Game-Jam-2025/BoxFixBoxWithBox](https://github.com/N64brew-Game-Jam-2025/BoxFixBoxWithBox) | | Bunbrush in Operation Friendship Repair | Team | Team Happy-Ferret (Happy-Ferret, Sloan 누구, jorquera) | [https://github.com/N64brew-Game-Jam-2025/Bunbrush](https://github.com/N64brew-Game-Jam-2025/Bunbrush) | | Cathode Quest 64 | Solo | HailToDodongo | [https://github.com/N64brew-Game-Jam-2025/Cathode-Quest-64](https://github.com/N64brew-Game-Jam-2025/Cathode-Quest-64) | | Console Clash | Solo | tfmoe\_\_ | [https://github.com/N64brew-Game-Jam-2025/Console-Clash](https://github.com/N64brew-Game-Jam-2025/Console-Clash) | | DamN64 | Team | vrgl117 games (vieux - vrgl117, Isabel, manuhoz) | [https://github.com/N64brew-Game-Jam-2025/DamN64](https://github.com/N64brew-Game-Jam-2025/DamN64) | | Fallout - Vault 64 | Solo | Rumbarrel64 | [https://github.com/N64brew-Game-Jam-2025/Fallout\_-Vault-64](https://github.com/N64brew-Game-Jam-2025/Fallout_-Vault-64) | | Ice Fishing \* | Team | Team zenden (Zest, Ghumdraup, moxy, Cobra!, blamerobots) | [https://github.com/N64brew-Game-Jam-2025/Icefishing](https://github.com/N64brew-Game-Jam-2025/Icefishing) | | Junkrunner 64 | Team | Team Ultrarare (lambertjamesd, Pyroxene, SapphireOnze, beta\_dynast, terzdesign) | [https://github.com/N64brew-Game-Jam-2025/Junkrunner64](https://github.com/N64brew-Game-Jam-2025/Junkrunner64) | | Kaiju Response Team | Team | Kaiju Response Team (Xtagon, Will) | [https://github.com/N64brew-Game-Jam-2025/kaiju-response-team](https://github.com/N64brew-Game-Jam-2025/kaiju-response-team) | | Moonfish | Team | Team GTvLC (ася, themoonmademehigh) | [https://github.com/N64brew-Game-Jam-2025/moonfish](https://github.com/N64brew-Game-Jam-2025/moonfish) | | Mysterious Barricades | Solo | visualculture | [https://github.com/N64brew-Game-Jam-2025/MysteriousBarricades](https://github.com/N64brew-Game-Jam-2025/MysteriousBarricades) | | Giallo: Crystal Dreams on Death’s Wing \* | Solo | Bop0880 | [https://github.com/N64brew-Game-Jam-2025/Crystal-dreams-on-deaths-wing](https://github.com/N64brew-Game-Jam-2025/Crystal-dreams-on-deaths-wing) | | Pandemonium | Team | Team Zero Cool (BoxingBruin, HelloNewman) | [https://github.com/N64brew-Game-Jam-2025/Pandemonium](https://github.com/N64brew-Game-Jam-2025/Pandemonium) | | Phazer 64 | Solo | DarkRage64 | [https://github.com/N64brew-Game-Jam-2025/phazer64](https://github.com/N64brew-Game-Jam-2025/phazer64) | | Plug-N-Repair \* | Solo | Dr. Ludos | [https://github.com/N64brew-Game-Jam-2025/Plug-N-Repair](https://github.com/N64brew-Game-Jam-2025/Plug-N-Repair) | | Repairman vs. Creatures \* | Solo | ValTheRabbit | [https://github.com/N64brew-Game-Jam-2025/Repairman](https://github.com/N64brew-Game-Jam-2025/Repairman) | | Robo Renovations \* | Team | Team Catch 64 (Gary Jones III, N64 Squid) | [https://github.com/N64brew-Game-Jam-2025/Robo-Renovations](https://github.com/N64brew-Game-Jam-2025/Robo-Renovations) | | S.U.G.G.A.M.A | Solo | Someone2639 | [https://github.com/N64brew-Game-Jam-2025/suggoma](https://github.com/N64brew-Game-Jam-2025/suggoma) | | Somewhere to Escape | Solo | RaisedWizardry | [https://github.com/N64brew-Game-Jam-2025/somewhere-to-escape](https://github.com/N64brew-Game-Jam-2025/somewhere-to-escape) | | The Big Fix | Solo | Shootfast | [https://github.com/N64brew-Game-Jam-2025/the\_big\_fix](https://github.com/N64brew-Game-Jam-2025/the_big_fix) | | Tool Time 64 \* | Team | Project-Simulacrum (tree, FadedParadigm, hbw) | [https://github.com/N64brew-Game-Jam-2025/tt64](https://github.com/N64brew-Game-Jam-2025/tt64) | | Uncharted Terra 2264 | Solo | Spookyiluha | [https://github.com/N64brew-Game-Jam-2025/uncharted-terra-2264](https://github.com/N64brew-Game-Jam-2025/uncharted-terra-2264) | | Untitled Racing Game | Team | Team Turtledove (lepidotós, chr0nomaton, hashalon, TurtleBox) | [https://github.com/N64brew-Game-Jam-2025/Untitled-Racing-Game](https://github.com/N64brew-Game-Jam-2025/Untitled-Racing-Game) | | Wizard Critter 64 | Team | Team Wizard Cat (Digi-Space Productions, CandiedCat, bigtimegoat) | [https://github.com/N64brew-Game-Jam-2025/Wizard-Critter-64](https://github.com/N64brew-Game-Jam-2025/Wizard-Critter-64) | | Wrench Wrangle | Solo | Fazana | [https://github.com/N64brew-Game-Jam-2025/Wrench-Wrangle](https://github.com/N64brew-Game-Jam-2025/Wrench-Wrangle) | Most are forked from GitHub repositories under their authors' namespace. The submissions above are read-only, but their upstream repositories can be followed for any updates made after the jam. \* About 7 submissions were developed outside of public GitHub repositories, with no upstream repository available. Their source can be found in archived repositories created by the Jam organizers. Updates for some of these may be found on [N64Brew Game Jam Itch.io](https://itch.io/jam/n64brew-game-jam-6/entries) . Results ------- | | | | --- | --- |Finalists | Entry | Rank | | Junkrunner 64 | 1st Place | | Cathode Quest 64 | 2nd Place | | Phazer 64 | 3rd Place | Trivia ------ * 27 of the 28 entries for this Game Jam utilized Libdragon. * No one chose to utilize LibUltra this jam, which was a first. * Bunbrush uses hkz-libn64 (like the original ScummVM port). * sUGGOMA is the first N64 game written in the zig programming language. * There were 53 total sign-ups for the Game Jam, with 17 of which being teams. * 16 solos ultimately submitted an entry (30% survival rate), while 12 teams submitted (70% survival rate). Videos ------ 1. [N64Brew Game Jam 2025 Announcement Video](https://www.youtube.com/watch?v=7P1tJFhhRe0) 2. [N64Brew Game Jam 2025 Submission Videos](https://www.youtube.com/playlist?list=PLBgD2QiDUApAPSQA0jceOw1eH7FdVowao) 3. [lambertjames' playthrough video](https://www.youtube.com/watch?v=UC3Z6cLsNJ0) 4. [mrmxy's playthrough video part 1](https://www.youtube.com/watch?v=e_V06BZyfak) 5. [mrmxy's playthrough video part 2](https://www.youtube.com/watch?v=I940Cgj7A14) 6. [Hard4Games playthrough video](https://youtu.be/_yFPb5NH0Gc) External Links -------------- 1. [N64Brew Game Jam Itch.io](https://itch.io/jam/n64brew-game-jam-6/entries) 2. [N64Brew Jam 2025 GitHub](https://github.com/orgs/N64brew-Game-Jam-2025) 3. [N64brew Game Jam 2025 Submissions](https://tinyurl.com/tremdctf) 4. [N64 Squid's Game Jam 2025 article](https://n64squid.com/homebrew/competitions/n64brew-game-jam-2025) Retrieved from "[https://n64brew.dev/wiki/N64brew\_Game\_Jam\_2025?oldid=5797](https://n64brew.dev/wiki/N64brew_Game_Jam_2025?oldid=5797) " --- # 64DD/Interface - N64brew Wiki [](https://n64brew.dev/wiki/64DD/Interface#) 64DD/Interface ============== < [64DD](https://n64brew.dev/wiki/64DD "64DD") The 64DD Interface is the only interface to the 64DD hardware, such as the drive, real-time clock, and more. All memory-mapped registers on 64DD only use the high 16-bit word, the low 16-bit word only returns `0000`. Contents -------- * [1 Registers](https://n64brew.dev/wiki/64DD/Interface#Registers) * [1.1 0x0500 0500 - ASIC\_DATA](https://n64brew.dev/wiki/64DD/Interface#0x0500_0500_-_ASIC_DATA) * [1.2 0x0500 0504 - ASIC\_MISC\_REG](https://n64brew.dev/wiki/64DD/Interface#0x0500_0504_-_ASIC_MISC_REG) * [1.3 0x0500 0508 - ASIC\_STATUS (R) / ASIC\_CMD (W)](https://n64brew.dev/wiki/64DD/Interface#0x0500_0508_-_ASIC_STATUS_(R)_/_ASIC_CMD_(W)) * [1.4 0x0500 050C - ASIC\_CUR\_TK](https://n64brew.dev/wiki/64DD/Interface#0x0500_050C_-_ASIC_CUR_TK) * [1.5 0x0500 0510 - ASIC\_BM\_STATUS (R) / ASIC\_BM\_CTL (W)](https://n64brew.dev/wiki/64DD/Interface#0x0500_0510_-_ASIC_BM_STATUS_(R)_/_ASIC_BM_CTL_(W)) * [1.6 0x0500 0514 - ASIC\_ERR\_SECTOR](https://n64brew.dev/wiki/64DD/Interface#0x0500_0514_-_ASIC_ERR_SECTOR) * [1.7 0x0500 0518 - ASIC\_SEQ\_STATUS (R) / ASIC\_SEQ\_CTL (W)](https://n64brew.dev/wiki/64DD/Interface#0x0500_0518_-_ASIC_SEQ_STATUS_(R)_/_ASIC_SEQ_CTL_(W)) * [1.8 0x0500 051C - ASIC\_CUR\_SECTOR (R)](https://n64brew.dev/wiki/64DD/Interface#0x0500_051C_-_ASIC_CUR_SECTOR_(R)) * [1.9 0x0500 0520 - ASIC\_HARD\_RESET (W)](https://n64brew.dev/wiki/64DD/Interface#0x0500_0520_-_ASIC_HARD_RESET_(W)) 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 #### 0x0500 0500 - ASIC\_DATA * * * | ASIC\_DATA `0x0500 0500` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[15:8\] | | | | | | | | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | DATA\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31-16 | **DATA\[15:0\]:** Used as argument for commands, and used to return a value after a command is issued. | #### 0x0500 0504 - ASIC\_MISC\_REG * * * | ASIC\_MISC\_REG `0x0500 0504` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | MISC\_REG\[15:8\] | | | | | | | | | 23:16 | R-0 | R-0 | R-0 | R-0 | R-0 | R-1 | R-0 | R-1 | | MISC\_REG\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31-16 | **MISC\_REG\[15:0\]:** Unknown, on a retail 64DD drive, it returns the value `0005`. | #### 0x0500 0508 - ASIC\_STATUS (R) / ASIC\_CMD (W) * * * **When Reading:** | ASIC\_STATUS `0x0500 0508` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | R-0 | U-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | — | DATA\_REQ | — | C2\_XFER | BM\_ERROR | BM\_INT | MECHA\_INT | DISK | | 23:16 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | BUSY | RESET | — | SPM\_OFF | HEAD\_RETRACT | WPROTECT\_ERR | MECHA\_ERROR | DISK\_CHANGE | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 30 | **DATA\_REQ:** When reading from disk, this would be set to 1 when the data is ready to be read from the buffer, and when writing to disk, this would be set to 1 when the drive asks for the data to be written. | | bit 28 | **C2\_XFER:** When reading from disk, this would be set to 1 when all 4 C2 sector data are read into the buffer. | | bit 27 | **BM\_ERROR:** This bit is set to 1 when the Buffer Manager has an error. | | bit 26 | **BM\_INT:** This bit is set to 1 when the Buffer Manager issues an interrupt. (Reading the register considers this specific interrupt as acknowledged.) | | bit 25 | **MECHA\_INT:** This bit is set to 1 when the drive controller (H8/300 CPU) issues an interrupt. (Generally after any command issued is processed.) | | bit 24 | **DISK:** Disk is inserted. | | bit 23 | **BUSY:** If set to 1, the drive controller is currently busy either initializing, or currently processing a command. | | bit 22 | **RESET:** If set to 1, the drive is considered in Reset mode. (It is recommended to disable that mode.) | | bit 20 | **SPM\_OFF:** If set to 1, the Spindle Motor is considered OFF. | | bit 19 | **HEAD\_RETRACT:** If set to 1, the Drive Head is retracted. | | bit 18 | **WPROTECT\_ERR:** Write Protect Error. (Set to 1 when attempting to write a write protected zone.) | | bit 17 | **MECHA\_ERROR:** If set to 1, then the processed command ended in error. | | bit 16 | **DISK\_CHANGE:** Set to 1 any time a disk has been inserted at least once (including at power on). It is NOT reset after a disk has been removed. | For detecting the 64DD, this register is read and checked if the first 16-bit word is `0000`, if not then the 64DD is considered not present. **When Writing:** | ASIC\_CMD `0x0500 0508` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | CMD\[15:8\] | | | | | | | | | 23:16 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | CMD\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31-16 | **CMD\[15:0\]:** Writing to this register would [issue a command](https://n64brew.dev/wiki/64DD/Commands "64DD/Commands")
. | #### 0x0500 050C - ASIC\_CUR\_TK * * * | ASIC\_CUR\_TK `0x0500 050C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | U-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | — | INDEX\_LOCK | | HEAD | TRACK\[11:8\] | | | | | 23:16 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | TRACK\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 30-29 | **INDEX\_LOCK:** If 11, then the head is locked to the track and could be accessed. | | bit 28 | **HEAD:** Current Head (0 or 1) | | bit 27-16 | **TRACK\[11:0\]:** Current Cylinder Track | #### 0x0500 0510 - ASIC\_BM\_STATUS (R) / ASIC\_BM\_CTL (W) * * * **When Reading:** | ASIC\_BM\_STATUS `0x0500 0510` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | U-0 | U-0 | U-0 | U-0 | R-0 | R-0 | R-0 | | RUNNING | — | — | — | — | ERROR | MICRO\_STATUS | BLOCKS | | 23:16 | R-0 | R-0 | R-0 | U-0 | U-0 | U-0 | U-0 | R-0 | | C1\_CORRECT | C1\_DOUBLE | C1\_SINGLE | — | — | — | — | C1\_ERROR | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31 | **RUNNING:** If set to 1, then the Buffer Manager is currently running. | | bit 26 | **ERROR:** Buffer Manager Error State | | bit 25 | **MICRO\_STATUS:** Micro Sequencer Error State? (Used to recognize as an error.) | | bit 24 | **BLOCKS:** If set to 1, it's in BLOCK mode where an entire cylinder track is read. | | bit 23 | **C1\_CORRECT:** (Unconfirmed) Sector has been corrected by C1 error correction. | | bit 22 | **C1\_DOUBLE:** Sector has 2 errors, detected with C1 error correction. | | bit 21 | **C1\_SINGLE:** Sector has 1 error, detected with C1 error correction. | | bit 16 | **C1\_ERROR:** Sector couldn't be corrected with C1 error correction. | **When Writing:** | ASIC\_BM\_CTL `0x0500 0510` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | START\_BM | BM\_MODE | BM\_INT\_MASK | BM\_RESET | BM\_DISABLE\_OR\_CHK | BM\_DISABLE\_C1 | BM\_XFERBLKS | BM\_MECHA\_INT\_RESET | | 23:16 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | SECTOR\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31 | **START\_BM:** If set to 1, then the Buffer Manager should be running. | | bit 30 | **BM\_MODE:** If set to 1, the Buffer Manager is set to Read Mode, else it's set to Write Mode. | | bit 29 | **BM\_INT\_MASK:** (Unconfirmed) If set to 1, then Buffer Manager interrupts are masked. | | bit 28 | **BM\_RESET:** If set to 1, resets the Buffer Manager. | | bit 27 | **BM\_DISABLE\_OR\_CHK:** (Unconfirmed) | | bit 26 | **BM\_DISABLE\_C1:** (Unconfirmed) Disables C1 Error Correction? | | bit 25 | **BM\_XFERBLKS:** If set to 1, processes the entire cylinder track. | | bit 24 | **BM\_MECHA\_INT\_RESET:** If set to 1, acknowledges and resets the MECHA\_INT Command interrupt. | | bit 23-16 | **SECTOR\[7:0\]:** Sector to start processing with | #### 0x0500 0514 - ASIC\_ERR\_SECTOR * * * | ASIC\_ERR\_SECTOR `0x0500 0514` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | AM\_FAIL | MICRO\_FAIL | SPINDLE\_FAIL | OVER\_RUN | OFFTRACK | NO\_DISK | CLOCK\_UNLOCK | SELF\_STOP | | 23:16 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31 | **AM\_FAIL:** ? | | bit 30 | **MICRO\_FAIL:** ? | | bit 29 | **SPINDLE\_FAIL:** ? | | bit 28 | **OVER\_RUN:** ? | | bit 27 | **OFFTRACK:** ? | | bit 26 | **NO\_DISK:** If set to 1, disk is not inserted. | | bit 25 | **CLOCK\_UNLOCK:** ? | | bit 24 | **SELF\_STOP:** ? | #### 0x0500 0518 - ASIC\_SEQ\_STATUS (R) / ASIC\_SEQ\_CTL (W) * * * | ASIC\_SEQ\_STATUS / ASIC\_SEQ\_CTL `0x0500 0518` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | MICRO\_INT\_MASK | MICRO\_PC\_ENABLE | — | — | — | — | — | — | | 23:16 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | RW-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31 | **MICRO\_INT\_MASK:** (Unconfirmed) | | bit 30 | **MICRO\_PC\_ENABLE:** If set to 1, enable Micro Sequencer. Set to 0 to disable. | #### 0x0500 051C - ASIC\_CUR\_SECTOR (R) * * * | ASIC\_CUR\_SECTOR `0x0500 051C` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | SECTOR\[7:0\] | | | | | | | | | 23:16 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | R-0 | | — | — | — | — | — | — | — | — | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31-24 | **SECTOR\[7:0\]:** (Unconfirmed) Current Sector | #### 0x0500 0520 - ASIC\_HARD\_RESET (W) * * * | ASIC\_HARD\_RESET `0x0500 0520` | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 31:24 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | RESET\[15:8\] | | | | | | | | | 23:16 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | W-0 | | RESET\[7:0\] | | | | | | | | | 15:8 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | 7:0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | U-0 | | — | — | — | — | — | — | — | — | | | | | --- | --- | | bit 31-16 | **RESET\[15:0\]:** Write `AAAA` to reset the 64DD hardware. Reset mode would be enabled after some reinitialization time. | Retrieved from "[https://n64brew.dev/wiki/64DD/Interface?oldid=5549](https://n64brew.dev/wiki/64DD/Interface?oldid=5549) " --- # User:Bigbass - N64brew Wiki [](https://n64brew.dev/wiki/User:Bigbass#) Bigbass ======= Joined 2 August 2020 * [User page](https://n64brew.dev/wiki/User:Bigbass) * [Discussion](https://n64brew.dev/wiki/User_talk:Bigbass?action=edit&redlink=1) Administrator of [https://n64brew.dev/](https://n64brew.dev/) Administrator of [https://gtnh.miraheze.org/](https://gtnh.miraheze.org/) Moderator for [https://tasvideos.org/](https://tasvideos.org/) * * * You can also find me here: * [Github](https://github.com/sponsors/bigbass1997/) * [Mastodon](https://hachyderm.io/@bigbass) * **@bigbass** on [Discord](https://discord.com/) Retrieved from "[https://n64brew.dev/wiki/User:Bigbass?oldid=5445](https://n64brew.dev/wiki/User:Bigbass?oldid=5445) " --- # SGI Audio Tools - N64brew Wiki [](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#) SGI Audio Tools =============== (Redirected from [Libultra/sgi-audio-tools](https://n64brew.dev/wiki/Libultra/sgi-audio-tools?redirect=no "Libultra/sgi-audio-tools") ) Contents -------- * [1 Introduction](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Introduction) * [2 Prerequisites](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Prerequisites) * [3 Authoring a Song with the SGI Audio Tools](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Authoring_a_Song_with_the_SGI_Audio_Tools) * [3.1 Compressing Sequence Data](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Compressing_Sequence_Data) * [3.1.1 Converting Your MIDI File(s)](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Converting_Your_MIDI_File(s)) * [3.1.2 Compressing Your MIDI File(s)](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Compressing_Your_MIDI_File(s)) * [3.1.3 Compiling Your MIDI File(s)](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Compiling_Your_MIDI_File(s)) * [3.2 Compressing Sounds](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Compressing_Sounds) * [3.2.1 Converting samples with SoX](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Converting_samples_with_SoX) * [3.2.2 Creating a code book for each file](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Creating_a_code_book_for_each_file) * [3.2.3 Compressing each sample](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Compressing_each_sample) * [3.3 Authoring the Instrument Bank File](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Authoring_the_Instrument_Bank_File) * [3.3.1 Before we Begin](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Before_we_Begin) * [3.3.2 The Instrument Bank File](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#The_Instrument_Bank_File) * [3.3.3 envelope](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#envelope) * [3.3.4 keymap](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#keymap) * [3.3.5 sound](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#sound) * [3.3.6 instrument](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#instrument) * [3.3.7 bank](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#bank) * [3.3.8 Percussion Sounds](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Percussion_Sounds) * [3.3.9 N64 SDK Example Instrument Bank](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#N64_SDK_Example_Instrument_Bank) * [3.3.10 Compiling the Instrument Bank](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Compiling_the_Instrument_Bank) * [3.4 Finishing up](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Finishing_up) * [4 Playing a song with NuSystem](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Playing_a_song_with_NuSystem) * [4.1 Linking the Audio Library](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Linking_the_Audio_Library) * [4.2 Setting up playback](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Setting_up_playback) * [4.3 Starting/stoping playback](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Starting/stoping_playback) * [5 Making an Instrument Bank for Sound Effects](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Making_an_Instrument_Bank_for_Sound_Effects) * [5.1 Looping](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Looping) * [5.1.1 Looping Compressed Audio](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Looping_Compressed_Audio) * [5.1.2 Looping Non-Compressed Audio](https://n64brew.dev/wiki/Libultra/sgi-audio-tools#Looping_Non-Compressed_Audio) Introduction ------------ The Nintendo 64 SDK comes with two "batteries included" audio libraries, the SGI Audio Tools and the N64SoundTools. The SGI Audio Tools are a collection of command-line tools for preparing samples and MIDI sequences for playback on the Nintendo 64. The N64SoundTools was written by Acclaim Studios Manchester (formerly Software Creations) and encompasses a kind of DAW for authoring and editing songs for the Nintendo 64. This article hopes to give a step-by-step reference for authoring sounds and music with the SGI Audio Tools and playing them in a NuSystem-based game. While not as intuitive and straightforward as the N64SoundTools, there are advantages to having a collection of command-line tools as they're entirely scriptable and can help automate compiling/editing of sound data. Prerequisites ------------- This article assumes you're familiar with the following terms/concepts: * Audio samples * ADSR and envelopes * Sample rate * MIDI * Linear predictive coding * AIFF format * the PATH environment variable (for program quick access) If you're a bit unfamiliar, a quick search, tutorial, or Wikipedia skim should suffice. This article assumes you have the SGI Audio Tools as part of the Nintendo 64 SDK. The programs in particular you're going to need are: * `tabledesign` * `vadpcm_enc` * `ic` * `midicvt` * `midicomp` * `sbc` This article also assumes that you're using the aforementioned programs in a Windows 95-like environment. An emulator, such as [Oracle VirtualBox](https://www.virtualbox.org/) works fine too. The n64decomp project has decompiled `tabledesign` and `adpcm` [here](https://github.com/n64decomp/sdk-tools) . It's possible to build those two particular programs yourself and run them in the environment of your choice, which might make your life a little easier! A good warm-up for this article might be to compile and run the `nu3` NuSystem sample, as it's more or less a "hello world" that plays the sort of audio files we're looking to generate. Keep note of the Makefile including the audio library and the spec file adding the `sbk`, `ctl`, and `tbl` files to the ROM. If you're able to compile/run `nu3`, even in an emulator, you'll be in a good place to test/debug/iterate an issues that pop up in your program. Authoring a Song with the SGI Audio Tools ----------------------------------------- ### Compressing Sequence Data #### Converting Your MIDI File(s) MIDI files are generally either **Type 0** or **Type 1**. The former specifies all of the notes in a single "track" while the latter has multiple tracks, typically for each instrument. The SGI tools require MIDI files to be in Type 0 and provides the `midicvt` tool to convert to it. Programs such as [MuseScore](https://musescore.org/) likely export in Type 1, so it's usually a good idea to convert to Type 0 before continuing.  [](https://n64brew.dev/wiki/File:Midicvt_sample_image.png "Screenshot of midicvt run successfully.") For each of your original MIDI files, run the following: `midicvt some_midi.mid some_midi_converted.mid` `some_midi_converted.mid` is the name of the converted file in this example. #### Compressing Your MIDI File(s) Once your MIDI files have been converted to Type 0, we'll be converting them to a compressed sequence format specialized for embedded playback on the Nintendo 64. The NuSystem library is written to use compressed MIDI files for songs. If you inspect the library, you'll notice that it uses a `ALCSPlayer` for storing/playing songs. It is possible to use uncompressed Type 0 MIDI, but you'll need to look into editing/rebuilding NuSystem or your own audio code with Nintendo's core audio library.  [](https://n64brew.dev/wiki/File:Midicomp_usage.png "Midicomp being successfully used.") For each of your converted MIDI files, run the following: `midicomp some_midi_converted.mid some_midi_compressed.cmf` You'll now have various `cmf` files for each of your songs. #### Compiling Your MIDI File(s) Now that we've compressed each MIDI file, its time to compile them into one "song bank". This will be added to your ROM and loaded in at runtime. To do this, we'll be using the `sbc` tool. Run the following command with each of your `cmf` files as parameters. `sbc -Osongs.sbk first_song_compressed.cmf second_song_compressed.cmf third_song_compressed.cmf`  [](https://n64brew.dev/wiki/File:Sbc_used.png "sbc successfully used here") The **ordering is important** here! Keep note of the order of each parameter, as when you're selecting your songs in your game's source code, you'll be indexing them as they're ordered here (eg: `first_song_compressed.cmf` will be `0`, second\_song\_compressed.cmf will be `1`, etc.). Note the lack of space between the `-O` flag and the output file name. This seems to be intended. 🤷 ### Compressing Sounds #### Converting samples with SoX [SoX](http://sox.sourceforge.net/) bills itself as _the Swiss Army knife of sound processing programs_. Its uses include (but aren't limited to) converting audio between formats, providing effects, and even recording. Given that SoX is an open-source tool, it's well worth including into any game developer's setup. The `tabledesign` and `vadpcm_enc` tools require audio samples to be in AIFF or AIFC. If the samples you're using are in a different format, such as WAV, you can use SoX to batch-convert your samples. It's also a good idea to resample each effect to the same sample rate, such as 32000Hz. If you're generating your instrument bank file via a script, you can hardcode the sample rate which will let you spend less time coding/debugging. If we want to convert an arbitrary WAV file to AIFF with a sample rate of 32000 and in mono we can enter: `sox some_file.wav -r 32000 -c 1 converted_file.aiff` This article assumes that the reader is converting their files to AIFF with SoX. #### Creating a code book for each file You'll want to create a code book for each AIFF sample you want to use in your song. To do this, you'll run the `tabledesign` command on each of your samples and save the output of that program to a file. For clarity, we'll be suffix-ing each code book with `.table` but it's not necessary to do. On each of your samples, run the following: `tabledesign song_sample.aiff > song_sample.table` It's worth noting that by default `tabledesign` will print to `STDOUT`. The `>` operator for writing to a file should work both on Unix-like and Windows here. #### Compressing each sample Once we've created our code book(s), we'll want to convert our AIFF samples to Nintendo's compressed AIFC formats. To do that, we'll be using `vadpcm_enc`. On each of your samples, run the following: `vadpcm_enc -c song_sample.table song_sample.aiff compressed_song_sample.aifc` The following `.aifc` file(s) will be compiled in to make a sound bank. ### Authoring the Instrument Bank File #### Before we Begin This is likely the most tricky and confusing parts of the SGI Audio Tools, so be sure to take a break if you're finding yourself frustrated. Take comfort in that what you're feeling is pretty normal, and that others have been in the same spot. Section 18.1.12 of the _Nintendo 64 Programming Manual_ is a pretty comfortable overview of what each section of an instrument bank file does. It's not "correct" in certain areas though, and copy/pasting the shown examples won't always work with `ic`. A particular example is that the manual says to reference each instrument in your `bank` section with `program` when in fact you'll need to use `instrument` instead. If you're looking for a reference of a working instrument bank, it's best to check the example banks at `ultra/usr/src/pr/assets/banks/` included with the SDK. They'll run through `ic` fine and help clarify things for you. #### The Instrument Bank File An instrument bank file usually has the file extension of `.ins`. Inside it contains one or more of each of the following: * `envelope` section(s), indicating an [ADSR](https://en.wikipedia.org/wiki/Envelope_(music)) * `keymap` section(s), indicating the range of "piano keys" a sound occupies, as well as other data * `sound` section(s), indicating a sampled sound as well as the `envelope` and `keymap` it uses * `instrument` section(s), indicating a "MIDI instrument" with a volume, pan, and various `sound`s * A single `bank` section, indicating the sample rate, and which `instrument`s correspond to which MIDI instrument numbers in your sequences. This will include a specialized instrument for the drumset channel. #### envelope The SGI Audio Tools represent an [ADSR](https://en.wikipedia.org/wiki/Envelope_(music)) with `envelope`s. Volume for each of the ADSR points ranges from `0` to `127`. Time is modelled in microseconds for each of the ADSR points. Different samples and sounds can use the same `envelope`, but your tracks will generally sound better if you ensure that each sample has a matching envelope. If you're unsure, it doesn't An `envelope` looks like the following: envelope AnExampleEnvelope { attackTime = 10000; attackVolume = 127; decayTime = 500000; decayVolume = 100; releaseTime = 200000; } In the example above, the volume goes from `0` to `127` in 10000 microseconds (the attack), then decays to 100 over `500000` microseconds. When the sound using is envelope ends, the sound fades out over `200000` microseconds. This should generally match up to your sample. For more information, review the N64 SDK Documentation at [18.1.2.5](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-05) . #### keymap A `keymap` represents a range of "keys" for a sound to cover. The [MIDI Standard](https://www.inspiredacoustics.com/en/MIDI_note_numbers_and_center_frequencies) represents each of the western music pitches from `0` to `127`. `60` can be considered middle C. A `keymap` looks like the following: keymap AnExampleKeymap { velocityMin = 0; velocityMax = 127; keyMin = 0; keyMax = 127; keyBase = 60; detune = 0; } The example above maps to every available pitch as `keyMin` is `0` and `keyMax` is `127`. `keyBase` represents the "reference pitch" to scale when changing keys. In the example above, a sample with the pitch of middle C should be used. Samples at different frequencies will require a different `keyBase` value. For more information, review the N64 SDK Documentation at [18.1.2.4](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-04) . #### sound A `sound` combines a `keymap`, `envelope`, and a compressed sample file together into a unit. A `sound` also has properties for stereo panning and volume from `0` to `127` each. An example might look like: sound AnExampleSound { use ("your/particular/path/to/compressed\_song\_sample.aifc"); pan = 64; volume = 127; keymap = AnExampleKeymap; envelope = AnExampleEnvelope; } Note how the `keymap` and `envelope` parts correspond to names of our examples above. For more information, review the N64 SDK Documentation at [18.1.2.3](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-03) . #### instrument An `instrument` models a single MIDI instrument. It consists of one or more `sound`s. An example `instrument` might look like: instrument AnExampleInstrument { volume = 127; pan = 64; sound = AnExampleSound; } Note how `sound` property matches the name of an existing sound above. An `instrument` can specify multiple `sound`s. For example, `GenMidiBank.inst` in the N64 SDK uses four sounds for a MIDI Cello: instrument Cello { volume = 127; pan = 64; vibratoType = 128; /\* 128, 129, 130, 131 \*/ vibratoRate = 222; /\* 0 to 255 \*/ vibratoDepth = 6; /\* 0 to 255 \*/ vibratoDelay = 1; /\* 1 to 255 \*/ sound = Cello00; sound = Cello01; sound = Cello02; sound = Cello03; } Each of the corresponding `sound`s have different `keymap`s and samples that cover different ranges of notes. This can produce nicer-quality audio as the pitch of a sample doesn't need to be distorted as much. The tradeoff being more audio memory required for your `instrument`, especially at higher sampling frequencies such as 44100Hz. For more information, review the N64 SDK Documentation at [18.1.2.2](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-02) . #### bank The `bank` section is a collection of `instrument` and the final piece of the puzzle for our file. Each [MIDI instrument number](http://fmslogo.sourceforge.net/manual/midi-instrument.html) gets assigned a particular `instrument`. An example `bank` might look like: bank SongBank { sampleRate = 32000; percussionDefault = Percussion\_Kit; instrument \[0\] = AnExampleSound; instrument \[65\] = AnExampleAltoSax; instrument \[107\] = AnExampleKoto; } Here we associate various `instrument`s with different MIDI instrument numbers. `0` represents MIDI notes that are played with an Acoustic Grand Piano, and we've told the audio library that we should use `AnExampleSound` as the voice of Acoustic Grand Piano. MIDI notes that use instrument `65` get associated with an instrument called `AnExampleAltoSax`. You'll want the value for `sampleRate` to match the frequency your sample files are tuned to. This is the reason we converted all of our samples to the same rate with SoX above. `percussionDefault` is explained in the following section. Note that you aren't required to have an `instrument` associated with every MIDI instrument number. If your song, for example, is only a solo piano piece then you'd only need to worry about an `instrument` for `0`. It's best to only include sounds for MIDI instruments that you need. Anything else is audio memory that could be better spent elsewhere, such as sound effects or higher-frequency samples. For more information, review the N64 SDK Documentation at [18.1.2.1](http://n64devkit.square7.ch/pro-man/pro18/18-01.htm#02-01) . Note that the example for `bank` in the manual says to use `program` for referencing an `instrument`. This isn't correct and the `ic` tool will give you an error if `program` is used. #### Percussion Sounds Percussion in MIDI is a bit unique in that [channel 10 is reserved for percussion](https://en.wikipedia.org/wiki/General_MIDI#Percussion) and that each note maps to a specific instrument. "Middle C" has a note number of 60 which is always a high bongo sound on the percussion channel. To accommodate this, we create a special `instrument` for percussive sounds. Each different instrument will have its own `sound`, `envelope`, and `keymap` that only covers its corresponding key. An example percussion setup for an Electric Base Drum (MIDI key `36`) might look like the following: keymap Percussive\_Bass\_Drum\_1Keymap { velocityMin = 0; velocityMax = 127; keyMin = 36; keyMax = 36; keyBase = 36; detune = 0; } sound Percussive\_Bass\_Drum\_1Sound { use ("electric\_bass\_drum\_sample.aifc"); pan = 64; volume = 127; keymap = Percussive\_Bass\_Drum\_1Keymap; envelope = SomeBassDrumEnvelope; } Which would then integrate into an example percussion `instrument`: instrument Percussion\_Kit { volume = 127; pan = 64; sound = Percussive\_Bass\_Drum\_1Sound; sound = Percussive\_Acoustic\_SnareSound; sound = Percussive\_Low\_TomSound; sound = Percussive\_Open\_Hi\_HatSound; sound = Percussive\_High\_Mid\_TomSound; sound = Percussive\_Crash\_Cymbal\_1Sound; sound = Percussive\_High\_TomSound; sound = Percussive\_Ride\_Cymbal\_1Sound; sound = Percussive\_High\_BongoSound; sound = Percussive\_Low\_BongoSound; sound = Percussive\_CabasaSound; sound = Percussive\_MaracasSound; sound = Percussive\_ShakerSound; } The `bank` would then set `percussionDefault` to be `Percussion_Kit`. #### N64 SDK Example Instrument Bank The N64 SDK has reference Instrument Banks at `ultra/usr/src/pr/assets/banks`. If you're ever stuck on how something should look or are getting errors with `ic`, they can be a helpful guide to see how things are done. #### Compiling the Instrument Bank We use the instrument compiler program (`ic`) to turn our Instrument Bank file into `.tbl` and `.ctl` files for running ingame. Run `ic` on your `.ins` file like the following: ic -OSongBank SongBank.ins Note that `SongBank` in this case whatever you called your `.ins` file. Also, note the lack of space before the `-O` argument. This seems to be correct for the tool. If your `.ins` file doesn't have any errors, you should see an output like this:  [](https://n64brew.dev/wiki/File:Ic_success.png "A bunch of garbled output from the instrument compiler, but no specific line numbers.") You should also then have `.tbl` and `.ctl` files in the same directory with the name you put before the `-O` parameter.  [](https://n64brew.dev/wiki/File:Ic_new_files.png "The CTL and TBL output files shown via the DIR command.") If there are syntax or other errors in your `.ins` file, you might get an error message like this:  [](https://n64brew.dev/wiki/File:Ic_error.png "The instrument compiler showing an error.") Even though the output looks garbled, try not to be discouraged! The final bit of output will show the line number of the error. The first place to look is often the associated line.  [](https://n64brew.dev/wiki/File:Screen_Shot_2020-10-06_at_10.05.28_PM.png "An Instrument Bank file with a missing semicolon on line 28/29.") In this case, the example error message was caused by a missing semicolon on line 28/29. Much like the C-family of programming languages, semicolons indicate the start/end of statements. If you're missing one, the instrument compiler might associate two lines as a whole. Be sure to check the lines above and below if you're not initially sure where the error might be. ### Finishing up Once we've completed the steps above, we should now have the following: * A `sbk` that consists of our converted/compressed MIDI sequences * `ctl` and `tbl` files for our samples We'll be including the above files into our ROM's spec file, then requesting the audio library to load and play them. Playing a song with NuSystem ---------------------------- TODO ### Linking the Audio Library TODO ### Setting up playback TODO ### Starting/stoping playback TODO Making an Instrument Bank for Sound Effects ------------------------------------------- TODO ### Looping #### Looping Compressed Audio TODO See [20.5](http://n64devkit.square7.ch/pro-man/pro20/20-05.htm#01) of the N64 SDK for more information on this. #### Looping Non-Compressed Audio As NuSystem is primarily compiled to use compressed sequenced audio, this is outside the scope of this article. However, [17.3.4](http://n64devkit.square7.ch/pro-man/pro17/17-03.htm#04) in the N64 SDK can help with non-compressed sequences. Retrieved from "[https://n64brew.dev/wiki/SGI\_Audio\_Tools?oldid=3975](https://n64brew.dev/wiki/SGI_Audio_Tools?oldid=3975) " --- # Category:Software Development Kits - N64brew Wiki [](https://n64brew.dev/wiki/Category:Software_Development_Kits#) Category:Software Development Kits ================================== SDK's are software libraries that provide predefined functions and utilities often used in the creation of 2D and 3D games. Pages in category "Software Development Kits" --------------------------------------------- The following 3 pages are in this category, out of 3 total. ### L * [Libdragon](https://n64brew.dev/wiki/Libdragon "Libdragon") * [Libultra](https://n64brew.dev/wiki/Libultra "Libultra") ### P * [Pseultra](https://n64brew.dev/wiki/Pseultra "Pseultra") Retrieved from "[https://n64brew.dev/wiki/Category:Software\_Development\_Kits?oldid=57](https://n64brew.dev/wiki/Category:Software_Development_Kits?oldid=57) " --- # Libultra/Memory Allocation - N64brew Wiki [](https://n64brew.dev/wiki/Libultra/Memory_Allocation#) Libultra/Memory Allocation ========================== < [Libultra](https://n64brew.dev/wiki/Libultra "Libultra") If you have not programmed embedded systems before, you may be surprised by some of the issues that come up when allocating memory on the Nintendo 64! Contents -------- * [1 Background: Problems malloc/free must solve](https://n64brew.dev/wiki/Libultra/Memory_Allocation#Background:_Problems_malloc/free_must_solve) * [2 Cache Tearing: Another Problem](https://n64brew.dev/wiki/Libultra/Memory_Allocation#Cache_Tearing:_Another_Problem) * [3 Memory Allocation Strategies](https://n64brew.dev/wiki/Libultra/Memory_Allocation#Memory_Allocation_Strategies) * [3.1 Global Variables](https://n64brew.dev/wiki/Libultra/Memory_Allocation#Global_Variables) * [3.2 Allocate Memory at Startup](https://n64brew.dev/wiki/Libultra/Memory_Allocation#Allocate_Memory_at_Startup) * [3.3 Use Memory Pools](https://n64brew.dev/wiki/Libultra/Memory_Allocation#Use_Memory_Pools) * [3.4 Writing Your Own malloc](https://n64brew.dev/wiki/Libultra/Memory_Allocation#Writing_Your_Own_malloc) * [3.5 Use Freelists (Object Pools)](https://n64brew.dev/wiki/Libultra/Memory_Allocation#Use_Freelists_(Object_Pools)) Background: Problems malloc/free must solve ------------------------------------------- The normal way to allocate memory in C on a modern system is to use malloc and free, provided by the standard C library. These functions are not magic! They use various data structures (some clever, some simple) to keep track of which ranges of memory are in use, and which blocks of memory are available. **Note:** In C++, new/delete are similar to malloc/free, and std::unique\_ptr / std::shared\_ptr (by default) are wrappers around new/delete. So the same problems with malloc and free also apply to C++. The main problem that malloc/free must face is that you can allocate memory and free memory however you like. This can cause _heap fragmentation,_ which is when you have free memory, but the free memory is “fragmented” into many small chunks. If you need to allocate a larger chunk of memory, you can’t use the small chunks. If heap fragmentation gets bad enough, malloc/free will start failing, and your game won't be able to continue running. * [Wikipedia: Fragmentation (computing)](https://en.wikipedia.org/wiki/Fragmentation_(computing)) * [Stack Overflow: What is memory fragmentation?](https://stackoverflow.com/q/3770457/82294) **Is this a problem on modern systems, too?** Yes, but modern systems have tons of memory (usually, gigabytes) and it’s easier to deal with fragmentation problems if you have a large amount of memory to play with. Modern systems will use a malloc/free implementation like [dlmalloc](http://gee.cs.oswego.edu/dl/html/malloc.html) , [jemalloc](http://jemalloc.net/) , or [tcmalloc](https://github.com/google/tcmalloc) . Cache Tearing: Another Problem ------------------------------ The N64 CPU (the VR4300) has a data cache with 16-byte cache lines. That means that the CPU cannot write 1 byte, 4 bytes, or 8 bytes to RAM—it can **only** write in 16 byte chunks, aligned to 16-byte boundaries (if you are using normal, cached RAM). This means that if the CPU and RSP write to the same cache line at the same time, one of them will win, and the other one will lose! For example, let’s say you need to load some data into RAM from the cartridge: static bool is\_loaded = false; static u8 my\_data\[256\]; // Bad! Contains errors! void load\_data(void) { if (!is\_loaded) { dma\_io\_message\_buffer = (OSIoMesg){ ... /\* etc \*/ .dramAddr = &my\_data, .devAddr = my\_data\_offset, .size = sizeof(my\_data), }; osEPiStartDma(rom\_handle, &dma\_io\_message\_buffer, OS\_READ); osRecvMesg(&dma\_message\_queue, NULL, OS\_MESG\_BLOCK); is\_loaded = true; } } What’s the problem with this code? The problem is that `my_data` may cross multiple cache lines, and there may be other data in those cache lines. For example, it’s possible that `is_loaded` (or **anything else**… it might be an unpleasant surprise in another file) is placed in the same cache line as `my_data`, so when the CPU finally writes `is_loaded = true;` to RAM, it also overwrites a chunk of `my_data` (which was written by the RCP). The only reasonable solution is to make sure that memory (every 16-byte chunk) is only either used by the CPU or the RSP, but never both at the same time (unless both sides only reading). To do this, memory used by the RSP should usually be allocated with 16-byte alignment, and those 16-byte chunks should not be shared with anything else. This applies to data loaded from cartridge, buffers used by the RSP and RDP—anything that you might write from the RCP side. This applies to everything—global variables, local variables, and data allocated with malloc/free. **Summary:** Any 16-byte chunk of data should not be used by both the RCP and the CPU, or bad things will happen. Easy way to do this is to align your objects to 16-byte boundaries. Memory Allocation Strategies ---------------------------- ### Global Variables Use global variables whenever you need to allocate something with a fixed size and don't want to free it. This is the easiest way to allocate memory and should be used whenever it is an option. For example, if you want to set aside 128 KB of RAM to use for a texture cache, you can do it very simply: u8 my\_texture\_cache\[128 \* 1024\] \_\_attribute\_\_((aligned(16))): Obviously, you cannot easily free this memory and use it for something else (without using something like overlays). However, if you always want 128 KB of texture cache, and need to use it for your entire game, then this is a good way to do it. ### Allocate Memory at Startup Another easy solution is to allocate memory at startup, and never free it. u8 \*my\_texture\_cache; void init\_textures(void) { my\_texture\_cache = malloc(128 \* 1024); if (my\_texture\_cache == NULL) { abort(); // Or however you want to handle errors. } } This may be more convenient than using global variables, and it lets you allocate a variable amount of memory. For example, you may decide to allocate a larger framebuffer on PAL systems, or allocate larger caches on systems with the Expansion Pak (8 MB RAM). ### Use Memory Pools A “memory pool” sets aside a certain amount of memory, lets you allocate it in smaller chunks, and lets you free the entire pool all at once so it can be reused. An example where this might be useful: if your game has different levels, you might decide to use a memory pool for the level-specific data. When you load a level, all data for the level gets placed into the memory pool. When you switch to a new level, the pool is reset and the pool can be reused for a different level. This can be very convenient if objects in your levels may be different sizes, but you don’t need to dynamically free objects during a level (because you can only free the entire pool all at once). A very simple memory pool might look like this: #include #include // A contiguous zone where memory can be allocated. struct mem\_zone { uintptr\_t pos; // Pointer to current free space position. uintptr\_t start; // Pointer to start of zone. uintptr\_t end; // Pointer to end of zone. const char \*name; }; // Allocate a memory zone with the given size. void mem\_zone\_init(struct mem\_zone \*z, size\_t size, const char \*name) { void \*ptr = malloc(size); if (ptr == NULL) { abort(); // Put your error handling here. } z->pos = (uintptr\_t)ptr; z->start = (uintptr\_t)ptr; z->end = (uintptr\_t)ptr + size; } **Note:** If you are using the old version of GCC (version 2.7.2) that ships with the Windows XP version of the tools, you will not have ``. You can work around this by defining `uintptr_t` yourself: // HACK: Works on N64! typedef unsigned long uintptr\_t; To allocate, you just bump the “pos” pointer up in the zone: // Allocate memory from the zone. void \*mem\_zone\_alloc(struct mem\_zone \*z, size\_t size) { if (size == 0) { return NULL; } // Round up to multiple of 16 bytes. size = (size + 15) & ~(size\_t)15; // How much free space remaining in zone? size\_t rem = z->end - z->pos; if (rem < size) { abort(); // Out of memory. Put your error handling here. } uintptr\_t ptr = z->pos; z->pos = ptr + size; return (void \*)ptr; } You can free all the memory just by resetting the pointer: // Free all objects in the zone. void mem\_zone\_free\_all(struct mem\_zone \*z) { z->pos = z->start; } ### Writing Your Own malloc So, where does malloc get its memory from? You can actually write your own, very simple malloc if you like! If `_bss_end` is the symbol for the end of the static memory that your program uses, then you can create a single memory zone for the remaining N64 RAM and use that to allocate your memory pools from. Using the memory pool code above, we create the “main memory” pool by using `_bss_end` and `osGetMemSize()` (which is a LibUltra function). #include #include extern u8 \_bss\_start; struct mem\_zone main\_memory; // Create a memory pool containing all RAM not used by code + global variables. void init\_memory(void) { // Round \_bss\_start up to 16 byte boundary. uintptr\_t start = ((uintptr\_t)&\_bss\_start + 15) & ~(uintptr\_t)15; main\_memory.pos = start; main\_memory.start = start; main\_memory.end = 0x80000000 + osGetMemSize(); } **Note:** The `osGetMemSize()` is part of LibUltra. We can then just define `malloc` to use this pool: #include void \*malloc(size\_t size) { return mem\_zone\_alloc(&main\_memory, size); } We then don’t define a `free` function. With this system, you can’t free memory allocated with `malloc`. ### Use Freelists (Object Pools) A freelist is a simple way of reusing objects so you don’t have to call malloc or free. With a freelist, you keep a linked list of objects which are not being used. A simple freelist might look like this: // Monster data structure. struct monster { vec3 position; int health; struct monster \*next\_free; }; #define MONSTER\_COUNT 100 struct monster monster\_array\[MONSTER\_COUNT\]; struct monster \*monster\_free; // Pointer to first free monster. At startup, allocate memory for your objects and add them all to the freelist. // Initialize monsters. Call once, at startup. void monster\_init(void) { for (int i = 0; i < MONSTER\_COUNT - 1; i++) { monster\_array\[i\]->next\_free = &monster\_array\[i + 1\]; } monster\_array\[MONSTER\_COUNT - 1\]->next\_free = NULL; } You can allocate by pulling an item from the freelist, and removing it from the freelist. // Allocate a monster from the freelist. struct monster \*monster\_new(void) { union monster\_entry \*mon = monster\_free; if (mon == NULL) { // Freelist is empty. return NULL; } // Remove from freelist. monster->free = mon->next; return &mon->monster; } To free an item, just put it back on the list. // Return a monster to the freelist. void monster\_free(struct monster \*mon) { mon->next\_free = monster\_free; monster\_free = mon; } There are more clever ways to do this, but this is a start. Freelists and object pools are very common, even today, in modern games on modern hardware. Retrieved from "[https://n64brew.dev/wiki/Libultra/Memory\_Allocation?oldid=5902](https://n64brew.dev/wiki/Libultra/Memory_Allocation?oldid=5902) " --- # N64brew Game Jam 2023 - N64brew Wiki [](https://n64brew.dev/wiki/N64brew_Game_Jam_2023#) N64brew Game Jam 2023 ===================== The fourth annual N64 homebrew game jam put together by the N64brew community on Discord. The theme, "**Summer**", was announced on June 25, 2023. The game jam began on June 30, 2022 and ended on August 21, 2023. This year, the number per team was increased to 5, instead of 4, from previous years. [![The N64Brew logo. The words "Summer Game Jam" are on the bottom on a crest of a wave, behind a sandy, tropical beach setting.](https://static.wikitide.net/n64wiki/thumb/1/12/N64Brew-summer-game-jam-2023.png/748px-N64Brew-summer-game-jam-2023.png)](https://n64brew.dev/wiki/File:N64Brew-summer-game-jam-2023.png) All submissions into the N64brew Summer Game Jam earned entrants a raffle ticket for a [Everdrive](https://n64brew.dev/wiki/Everdrive_64 "Everdrive 64") X7 flash cartridge giveaway, donated by LambertJamesd. As in other years, the prize pot for the winner of this year's jam will be donated to a charity per the winner's choosing. The prize pot for the 2023 N64brew Summer Game Jam was $776.76 USD. Five out of the six entries for the N64Brew Game Jam 2023 utilized [Libdragon](https://n64brew.dev/wiki/Libdragon "Libdragon") over Nintendo's [official SDK](https://n64brew.dev/wiki/Libultra "Libultra") . In contrast to previous years of having pre-selected judges for the Jam, the 2023 Summer Game Jam opted to go with a Ranked Choice Voting approach, a first in its history, provided by the N64Brew Discord Community. Voting for each submission opened to N64Brew members on August 22, 2023, and ended on August 27, 2023, at 11:59:59 EST. This choice of voting works as follows: There was a list of all the submissions for users to vote on, and they each assign a rank from 1 (least favorite) to 6 (favorite), with one unique rank per submission (no two submissions with the same number). Game Jam participants were also allowed to vote, but scores assigned to their own submission were ignored for obvious reasons. The score sheet was published once voting ended, with anonymized voters, so jam participants could view and review feedback submitted. A total of 30 votes were cast. The winners of the Game Jam were announced in September 2023. Prominent Nintendo 64 enthusiast and videogame reviewer, Hard4Games, reviewed the Game Jam submissions in video-format on September 6, 2023, and provided their own judgement & scores of the submissions. Contents -------- * [1 Submissions](https://n64brew.dev/wiki/N64brew_Game_Jam_2023#Submissions) * [2 Results](https://n64brew.dev/wiki/N64brew_Game_Jam_2023#Results) * [3 Trivia](https://n64brew.dev/wiki/N64brew_Game_Jam_2023#Trivia) * [4 External Links](https://n64brew.dev/wiki/N64brew_Game_Jam_2023#External_Links) Submissions ----------- | Entry | Solo/Team | Participant(s) | Engine/Framework | Source Code | External link(s) | | --- | --- | --- | --- | --- | --- | | [Alien Sun](https://n64brew.dev/wiki/Alien_Sun?action=edit&redlink=1 "Alien Sun (page does not exist)") | Solo | 9\_Nova | libdragon | [https://github.com/9nova/aliensun](https://github.com/9nova/aliensun) | [https://9nova.itch.io/aliensun](https://9nova.itch.io/aliensun) | | [Brew Volleyball](https://n64brew.dev/wiki/Brew_Volleyball?action=edit&redlink=1 "Brew Volleyball (page does not exist)") | Solo | tfmoe\_\_ | Libdragon | [https://github.com/marian-m12l/n64brew-gamejam-4](https://github.com/marian-m12l/n64brew-gamejam-4) | | [Megatextures Tech Demo](https://n64brew.dev/wiki/Megatextures_Tech_Demo?action=edit&redlink=1 "Megatextures Tech Demo (page does not exist)") | Team | Team Ultra Rare (lambertjamesd, jtn191, sapphiretactics) | libultra | [https://github.com/lambertjamesd/n64brew2023](https://github.com/lambertjamesd/n64brew2023) | | [Sand Castle Clamour](https://n64brew.dev/wiki/Sand_Castle_Clamour?action=edit&redlink=1 "Sand Castle Clamour (page does not exist)") | Solo | traill | Libdragon | [https://github.com/Traillio/sand\_castle\_clamour](https://github.com/Traillio/sand_castle_clamour) | | [Sand City](https://n64brew.dev/wiki/Sand_City?action=edit&redlink=1 "Sand City (page does not exist)") | Team | Pocket Sand (Wade Typhon, cryb) | libdragon | [https://github.com/Wade-Tyhon/Sand-City](https://github.com/Wade-Tyhon/Sand-City) | | [Summer's Story](https://n64brew.dev/wiki/Summer%27s_Story?action=edit&redlink=1 "Summer's Story (page does not exist)") | Team | Polygon Sandwich (anacierdem, Dc.all) | Libdragon | [https://github.com/anacierdem/summers\_story](https://github.com/anacierdem/summers_story) | [https://anacierdem.itch.io/summers-story](https://anacierdem.itch.io/summers-story) | Results ------- | | | | | --- | --- | --- |Finalists | Entry | Score | Rank | | Summer's Story | 125 | 1st Place | | Megatextures Tech Demo | 118 | 2nd Place | | Alien Sun | 113 | 3rd Place | | Sand City | 104 | 4th Place | | Sand Castle Clamour | 91 | 5th Place | | Brew Volleyball | 54 | 6th Place | Trivia ------ The [N64brew Game Jam 2023 Announcement/promo video](https://drive.google.com/file/d/1Zi5B9NEkTQ44YIUqFiCBHkn7Og7SpIQt/view) is also viewable on an actual Nintendo 64 console itself! Just pop the file into your flash cartridge of choice and you're ready to hit play! Rasky from N64brew helped with this endeavor! External Links -------------- 1. [Jam Announcement & Theme Reveal Video](https://www.youtube.com/watch?v=_Pr66YI-oyE) 2. [N64brew Game Jam 2023 Winners N64Squid](https://n64squid.com/homebrew/competitions/n64brew-game-jam-2023/) 3. [N64Brew Game Jam 2023 Score Form & Responses](https://docs.google.com/spreadsheets/d/16aN3aKoofLNU8fYHinj9Pr5WqvMS2lJvLH3muYqIj0w/edit#gid=1841170038) 4. [BRAND NEW Nintendo 64 Games | @n64brew - Hard4Games](https://www.youtube.com/watch?v=bMikRIjOpcM) Retrieved from "[https://n64brew.dev/wiki/N64brew\_Game\_Jam\_2023?oldid=4940](https://n64brew.dev/wiki/N64brew_Game_Jam_2023?oldid=4940) " --- # Nintendo 64 - N64brew Wiki [](https://n64brew.dev/wiki/Nintendo_64#) Nintendo 64 =========== The Nintendo 64 (officially abbreviated as N64, hardware model number pre-term: NUS, stylized as NINTENDO64) is a home video game console developed and marketed by Nintendo. Named for its 64-bit central processing unit, it was released in June 1996 in Japan, September 1996 in North America, and March 1997 in Europe and Australia. It was the last major home console to use the ROM cartridge as its primary storage format until the Switch in 2017. The Nintendo 64 was discontinued in 2002 following the launch of its successor, the GameCube, in 2001. Retrieved from "[https://n64brew.dev/wiki/Nintendo\_64?oldid=192](https://n64brew.dev/wiki/Nintendo_64?oldid=192) " --- # Secure Kernel Calls - N64brew Wiki [](https://n64brew.dev/wiki/Secure_Kernel_Calls#) Secure Kernel Calls =================== **This page is relevant only for the iQue Player, not the original Nintendo 64. If you're an N64 developer, this page isn't useful to you.** Secure Kernel Calls (or **SKCs**) are the mechanism through which application software, such as games or the iQue Menu, can communicate with the Secure Kernel. SKCs are called like any other function, following the MIPS C ABI. On the application side, they are implemented as stubs linked as part of the ROM. Contents -------- * [1 The mechanism](https://n64brew.dev/wiki/Secure_Kernel_Calls#The_mechanism) * [2 Types used in Secure Kernel Calls](https://n64brew.dev/wiki/Secure_Kernel_Calls#Types_used_in_Secure_Kernel_Calls) * [2.1 Ticket bundle](https://n64brew.dev/wiki/Secure_Kernel_Calls#Ticket_bundle) * [2.2 Launch CRLs](https://n64brew.dev/wiki/Secure_Kernel_Calls#Launch_CRLs) * [2.3 Recrypt list](https://n64brew.dev/wiki/Secure_Kernel_Calls#Recrypt_list) * [3 Calls](https://n64brew.dev/wiki/Secure_Kernel_Calls#Calls) * [3.1 skGetId](https://n64brew.dev/wiki/Secure_Kernel_Calls#skGetId) * [3.2 skLaunchSetup](https://n64brew.dev/wiki/Secure_Kernel_Calls#skLaunchSetup) * [3.3 skLaunch](https://n64brew.dev/wiki/Secure_Kernel_Calls#skLaunch) * [3.4 skRecryptListValid](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptListValid) * [3.5 skRecryptBegin](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptBegin) * [3.6 skRecryptData](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptData) * [3.7 skRecryptComputeState](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptComputeState) * [3.8 skRecryptEnd](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptEnd) * [3.9 skSignHash](https://n64brew.dev/wiki/Secure_Kernel_Calls#skSignHash) * [3.10 skVerifyHash](https://n64brew.dev/wiki/Secure_Kernel_Calls#skVerifyHash) * [3.11 skGetConsumption](https://n64brew.dev/wiki/Secure_Kernel_Calls#skGetConsumption) * [3.12 skAdvanceTicketWindow](https://n64brew.dev/wiki/Secure_Kernel_Calls#skAdvanceTicketWindow) * [3.13 skSetLimit](https://n64brew.dev/wiki/Secure_Kernel_Calls#skSetLimit) * [3.14 skExit](https://n64brew.dev/wiki/Secure_Kernel_Calls#skExit) * [3.15 skKeepAlive](https://n64brew.dev/wiki/Secure_Kernel_Calls#skKeepAlive) * [4 Debug calls](https://n64brew.dev/wiki/Secure_Kernel_Calls#Debug_calls) * [4.1 References](https://n64brew.dev/wiki/Secure_Kernel_Calls#References) The mechanism ============= An application runs an SKC by calling the relevant stub function. Each stub is a small chunk of code that loads the number of the SKC to call into $v0, then triggers a Secure Kernel trap by reading from [MI\_BB\_SECURE\_EXCEPTION](https://n64brew.dev/wiki/MIPS_Interface#0x0430_0014_-_MI_BB_SECURE_EXCEPTION "MIPS Interface") . SKCs are implemented in the Secure Kernel as regular C functions, and the SKC handler passes through the argument registers unchanged, so these stubs help to ensure that the CPU registers contain the correct parameters. All known SKCs return an `int32_t`; 0 on success, and <0 on error. Patched or custom SKs may return >0 for non-original errors. Types used in Secure Kernel Calls ================================= #### Ticket bundle | | | | | | --- | --- | --- | --- |Ticket bundle[\[1\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompTicketBundle-1) | Offset | C type | Name | Description | | 0x00 | `Ticket *`[\[2\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompTicket-2)
[\[3\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-iQBTicket-3) | `ticket` | A pointer to an iQue Player ticket structure. | | 0x04 | `Certificate *[5]`[\[4\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompCert-4) | `ticketCerts` | An array of 5 pointers to content certificates; this must be a valid certificate chain, such that the first certificate signs the ticket, the second certificate signs the first certificate, etc., until a certificate is signed by `Root`. Unused certificate slots should be set to `NULL`. | | 0x18 | `Certificate *[5]`[\[4\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompCert-4) | `cmdCerts` | An array of 5 pointers to content certificates; this must also be a valid certificate chain, but that signs the ticket's embedded CMD structure. | The ticket structure contains all of the information needed for SK to set up the encryption hardware to decrypt the application to be launched. SK ensures that the ticket and its included CMD[\[5\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompCmd-5) [\[6\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-iQBCmd-6) are signed by iQue, as the CMD contains the SHA-1 hash of the application to be launched. #### Launch CRLs * * * | | | | | | --- | --- | --- | --- |Launch CRLs[\[7\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompLaunchCrls-7) | Offset | C type | Name | Description | | 0x00 | `CRL bundle`[\[7\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompLaunchCrls-7) | `ticketRL` | A Certificate Revocation List bundle for revoking certificates that sign tickets. | | 0x1C | `CRL bundle`[\[7\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompLaunchCrls-7) | `certRL` | A Certificate Revocation List bundle for revoking certificates that sign other certificates. | | 0x38 | `CRL bundle`[\[7\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompLaunchCrls-7) | `cmdRL` | A Certificate Revocation List bundle for revoking certificates that sign CMD structures. | These 3 revocation lists are used when ensuring the ticket provided to SK is signed, so that iQue can revoke any certificates used to sign content that should no longer be accepted. #### Recrypt list * * * | | | | | | --- | --- | --- | --- |Recrypt list[\[7\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompLaunchCrls-7) [\[8\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-iQBRecrypt-8) | Offset | C type | Name | Description | | 0x00 | `ECC signature`[\[9\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompEccSig-9) | `sig` | An ECC signature (using the console's ECC private key and the identity `0x06091968`[\[10\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompEccIdent-10)
, presumably a significant date for one of the developers) over the following data. | | 0x40 | `uint32_t` | `entries` | The number of entries in the list. | | 0x44 | `Recrypt entry[entries]` | `list` | An array of recrypt entries. Each entry contains the recrypt key and state for one piece of content on the console. | The data provided to SK here usually comes directly from `recrypt.sys` on the iQue Player's NAND. It may be modified as part of an SKC, and as such it gets written back to the file after the call. Calls ===== These are the SKCs found in the SK version on all known consoles. | | | | --- | --- |Retail SKCs | Number | Name | | 0 | [skGetId](https://n64brew.dev/wiki/Secure_Kernel_Calls#skGetId) | | 1 | [skLaunchSetup](https://n64brew.dev/wiki/Secure_Kernel_Calls#skLaunchSetup) | | 2 | [skLaunch](https://n64brew.dev/wiki/Secure_Kernel_Calls#skLaunch) | | 3 | [skRecryptListValid](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptListValid) | | 4 | [skRecryptBegin](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptBegin) | | 5 | [skRecryptData](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptData) | | 6 | [skRecryptComputeState](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptComputeState) | | 7 | [skRecryptEnd](https://n64brew.dev/wiki/Secure_Kernel_Calls#skRecryptEnd) | | 8 | [skSignHash](https://n64brew.dev/wiki/Secure_Kernel_Calls#skSignHash) | | 9 | [skVerifyHash](https://n64brew.dev/wiki/Secure_Kernel_Calls#skVerifyHash) | | 10 | [skGetConsumption](https://n64brew.dev/wiki/Secure_Kernel_Calls#skGetConsumption) | | 11 | [skAdvanceTicketWindow](https://n64brew.dev/wiki/Secure_Kernel_Calls#skAdvanceTicketWindow) | | 12 | [skSetLimit](https://n64brew.dev/wiki/Secure_Kernel_Calls#skSetLimit) | | 13 | [skExit](https://n64brew.dev/wiki/Secure_Kernel_Calls#skExit) | | 14 | [skKeepAlive](https://n64brew.dev/wiki/Secure_Kernel_Calls#skKeepAlive) | #### skGetId * * * `skGetId(uint32_t * bbid_out)` Retrieves the console's unique BBID. Always returns 0. #### skLaunchSetup * * * `skLaunchSetup(Ticket bundle * bundle, Launch CRLs * crls, Recrypt list * list)` Prepares SK for launching an application. The ticket and CMD in the bundle are copied to internal memory, the keys are decrypted, and the encryption hardware is prepared with the AES key and IV. Returns various negative numbers to indicate errors, e.g. the application needs to be recrypted. #### skLaunch * * * `skLaunch(void * address)` Launches the application, by first performing some verification on the contents, then jumping to the provided address. This requires that the entrypoint has already been loaded to the correct place in memory and that all the hardware has been set up correctly. Returns -1 on error, including if the application returns, which should not be possible under normal circumstances. #### skRecryptListValid * * * `skRecryptListValid(Recrypt list * list)` Verifies that the provided recrypt list is valid, i.e. that it's a valid size and that the signature is correct. Returns -1 if the list is invalid. #### skRecryptBegin * * * `skRecryptBegin(Ticket bundle * bundle, Launch CRLs * crls, Recrypt list * list)` Prepares SK for recrypting an application. The ticket in the bundle is verified, the recrypt list is searched for an existing entry, one is made if none exists, the encryption hardware is configured, and SK prepares for performing a SHA-1 hash. Returns a subset of what `skLaunchSetup` returns. #### skRecryptData * * * `skRecryptData(void * data, uint32_t size)` Recrypts the provided data, using the previously set context. Returns -1 if the data pointer is invalid. #### skRecryptComputeState * * * `skRecryptComputeState(void * data, uint32_t size)` Due to how AES-CBC works, in order to recover a partially-completed recryption, the last chunk of data that was successfully recrypted must be provided (non-recrypted) to set up the context prior to recrypting any new data. The last 16 bytes of data provided with this function are copied to SK's recrypt context. #### skRecryptEnd * * * `skRecryptEnd(Recrypt list * list)` Notifies SK that the recryption is complete. SK updates the recrypt list to indicate that the relevant entry has been fully recrypted, and checks to see if the SHA-1 hash it calculated while recrypting the data matches the hash in the ticket provided in `skRecryptBegin`. Returns -1 if any error occurs, e.g. if the hash does not match. #### skSignHash * * * `skSignHash(SHA-1 hash[[11]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompHash-11) * hash, ECC signature[[9]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompEccSig-9) * sig_out)` Signs the given SHA-1 hash using the console's ECC private key and the identity `1` (**note: not the same identity as is used for signing `recrypt.sys`, so signatures are not compatible between the two**). This is used by the iQue Menu to sign save data, although nothing happens if the signature doesn't match when loading the save data. Returns -1 if the hash or signature pointers are invalid. #### skVerifyHash * * * `skVerifyHash(SHA-1 hash * hash, Signature[[12]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompSig-12) * sig, Certificate ** cert_chain, Launch CRLs * crls)` Verifies that the provided hash matches the provided signature. This call supports all of the signature types on the iQue Player (ECC, RSA2048, RSA4096), and so it takes a pointer to a generic signature[\[12\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompSig-12) , rather than a specific signature type. The certificate chain and CRLs are used for RSA2048 and RSA4096 signatures, and follow the same format as in a ticket bundle (i.e. the certificate chain has a maximum length of 5, and shorter chains must be `NULL`\-terminated). Returns various negative numbers for various errors; most errors are represented by -1, but trying to use revoked certificates returns -9, for example. #### skGetConsumption * * * `skGetConsumption(uint16_t * window, uint16_t * counter)` Retrieves the consumption counters for all currently-tracked applications and the TID window. The TID window stores the first ticket ID for which the consumption is being tracked; the consumption counter refers to the number of minutes that have been played of a given application, which is recorded when the ticket specifies that the application is a time-limited trial. This data is stored in Virage0/1[\[13\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-skDecompV01-13) [\[14\]](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_note-iQBV01-14) . Returns -1 if the consumption counter pointer is invalid. #### skAdvanceTicketWindow * * * `skAdvanceTicketWindow(void)` Shifts all consumption counters down to the previous slot, deleting the first counter, and increments the TID window. Since iQue published such a small number of titles during the console's lifespan (just 14 released games), it's unknown whether this SKC has ever been called in a non-test environment. Returns -1 if writing to Virage0/1 fails. #### skSetLimit * * * `skSetLimit(uint16_t limit, uint16_t limit_type)` Overrides the trial limit and trial type stored in the currently-loaded ticket. Unknown purpose in practice. Returns -1 if no ticket is currently loaded. #### skExit * * * `skExit(void)` Exits the application immediately, by jumping to SK's entrypoint. Should never return, but returns 0 unconditionally if exiting somehow fails. #### skKeepAlive * * * `skKeepAlive(void)` When running trial applications, the secure timer triggers a Secure Kernel trap at regular intervals. If the time since the last time this SKC was called is too large, the application is forcefully exited by jumping to SK's entrypoint. This SKC, therefore, updates the variable used to track the last time this call was made. If the call returns, it unconditionally returns 0; however, the timer is also checked during the call, and so it can exit forcefully by jumping to SK's entrypoint instead. Debug calls =========== These are the SKCs known to have existed in debug versions of SK at some point, but for which the operation and function signatures are not known. | | | | --- | --- |Debug SKCs | Number | Name | | 15 | skGetRandomKeyData | | 16 | skDumpVirage | | 17 | skTest2 | | 18 | skTest3 | | 19 | skResetWindow | | 20 | skValidateRls | References ---------- 1. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompTicketBundle_1-0) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L130-L134) , BbTicketBundle 2. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompTicket_2-0) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L123-L126) , BbTicket 3. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-iQBTicket_3-0) iQueBrew, "Ticket" 4. ↑ [4.0](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompCert_4-0) [4.1](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompCert_4-1) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L56-L65) , BbCertBase 5. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompCmd_5-0) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L105-L108) , BbContentMetaData 6. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-iQBCmd_6-0) iQueBrew, "CMD" 7. ↑ [7.0](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompLaunchCrls_7-0) [7.1](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompLaunchCrls_7-1) [7.2](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompLaunchCrls_7-2) [7.3](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompLaunchCrls_7-3) [7.4](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompLaunchCrls_7-4) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L165-L169) , BbAppLaunchCrls 8. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-iQBRecrypt_8-0) iQueBrew, "Recrypt.sys" 9. ↑ [9.0](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompEccSig_9-0) [9.1](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompEccSig_9-1) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L17) , BbEccSig 10. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompEccIdent_10-0) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/src/9FC043B0.c#L11) 11. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompHash_11-0) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L22) , BbShaHash 12. ↑ [12.0](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompSig_12-0) [12.1](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompSig_12-1) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L67-L71) , BbGenericSig 13. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-skDecompV01_13-0) decompals, [iQuePlayer-SecureKernel](https://github.com/decompals/iQuePlayer-SecureKernel/blob/main/include/bbtypes.h#L30-L40) , BbVirage01 14. [↑](https://n64brew.dev/wiki/Secure_Kernel_Calls#cite_ref-iQBV01_14-0) iQueBrew, "Virage0-1" Retrieved from "[https://n64brew.dev/wiki/Secure\_Kernel\_Calls?oldid=5392](https://n64brew.dev/wiki/Secure_Kernel_Calls?oldid=5392) " --- # Konami Dance Pad - N64brew Wiki Konami Dance Pad ================ From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/Konami_Dance_Pad?mobileaction=toggle_view_desktop#mw-head) [Jump to search](https://n64brew.dev/wiki/Konami_Dance_Pad?mobileaction=toggle_view_desktop#searchInput) The **Konami Dance Pad** is used exclusively for _Dance Dance Revolution: Disney Dancing Museum_. It was released only in Japan along with the game in November 2000. The pad consists of a 3x3 grid with an image of Mickey Mouse in the center and giant arrows adjacent. When viewed from above, B is in the upper left and A is in the upper right. The start button is above the A. The bottom row has an image of Donald Duck on the left and Minnie Mouse on the right. Retrieved from "[https://n64brew.dev/wiki/Konami\_Dance\_Pad?oldid=5140](https://n64brew.dev/wiki/Konami_Dance_Pad?oldid=5140) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # File:LH52256CVN.pdf - N64brew Wiki [](https://n64brew.dev/wiki/File:LH52256CVN.pdf#) File:LH52256CVN.pdf =================== * [File](https://n64brew.dev/wiki/File:LH52256CVN.pdf#file) * [File history](https://n64brew.dev/wiki/File:LH52256CVN.pdf#filehistory) * [File usage](https://n64brew.dev/wiki/File:LH52256CVN.pdf#filelinks) [![](https://n64brew.dev/1.45/resources/assets/file-type-icons/fileicon-pdf.png)](https://static.wikitide.net/n64wiki/c/cc/LH52256CVN.pdf) [LH52256CVN.pdf](https://static.wikitide.net/n64wiki/c/cc/LH52256CVN.pdf "LH52256CVN.pdf") (file size: 837 KB, MIME type: application/pdf) Summary ------- The datasheet for Sharp's LH52256CVN 32KByte SRAM chip. File history ------------ Click on a date/time to view the file as it appeared at that time. | | Date/Time | Dimensions | User | Comment | | --- | --- | --- | --- | --- | | current | [06:17, 17 September 2020](https://static.wikitide.net/n64wiki/c/cc/LH52256CVN.pdf) | (837 KB) | [Bigbass](https://n64brew.dev/wiki/User:Bigbass "User:Bigbass")
([talk](https://n64brew.dev/wiki/User_talk:Bigbass?action=edit&redlink=1 "User talk:Bigbass (page does not exist)")
\| [contribs](https://n64brew.dev/wiki/Special:Contributions/Bigbass "Special:Contributions/Bigbass")
) | The datasheet for Sharp's LH52256CVN 32KByte SRAM chip. | You cannot overwrite this file. File usage ---------- There are no pages that use this file. Retrieved from "[https://n64brew.dev/wiki/File:LH52256CVN.pdf?oldid=1461](https://n64brew.dev/wiki/File:LH52256CVN.pdf?oldid=1461) " --- # Libultra/Data Compression - N64brew Wiki [](https://n64brew.dev/wiki/Libultra/Data_Compression#) Libultra/Data Compression ========================= < [Libultra](https://n64brew.dev/wiki/Libultra "Libultra") Compressing your assets will let you fit more data on a cartridge. But which data should you compress, and how? What are the tradeoffs? TODO: Link to decoding libraries / compression tools. Contents -------- * [1 Data Compression](https://n64brew.dev/wiki/Libultra/Data_Compression#Data_Compression) * [2 Images](https://n64brew.dev/wiki/Libultra/Data_Compression#Images) * [3 Audio](https://n64brew.dev/wiki/Libultra/Data_Compression#Audio) * [4 Video](https://n64brew.dev/wiki/Libultra/Data_Compression#Video) Data Compression ================ Any data can be compressed with a general-purpose data compression algorithm, such as Deflate. However, this is generally not as effective for media, like images, audio, or video. Images ====== * Raw * JPEG * HVQ Audio ===== TODO: Calculate bit rates at different sample rates. Find samples for comparison or create a test ROM. The microcode and audio libraries that come with LibUltra support PCM and VADPCM. TODO: What about LibDragon? * PCM: Baseline for comparison: 16 bits per sample. This is raw, uncompressed audio. * VADPCM: Used in many retail Nintendo 64 games. Always 4.5 bits per sample, encoded in blocks of 16 samples. * MP3: "Known to be possible." Used in some retail Nintendo 64 games, such as Conker's Bad Fur Day. * Music can also be stored in MOD or MIDI format, but this is not a type of compression. | Encoding | Sample Rate | Bit rate (kbit/s) | Length per megabyte (s/MiB) | Capacity of 64 MiB cartridge (h:mm:ss) | | --- | --- | --- | --- | --- | | 16-bit PCM | 44.1 kHz | 706 kbit/s | 11.9 s/MiB | 12:41 | | 32 kHz | 512 kbit/s | 16.4 s/MiB | 17:29 | | 22.05 kHz | 353 kbit/s | 23.8 s/MiB | 25:22 | | 16 kHz | 256 kbit/s | 32.7 s/MiB | 34:57 | | VADPCM | 44.1 kHz | 198 kbit/s | 42.3 s/MiB | 45:05 | | 32 kHz | 144 kbit/s | 58.3 s/MiB | 1:02:08 | | 22.05 kHz | 99.2 kbit/s | 84.5 s/MiB | 1:30:11 | | 16 kHz | 72.0 kbit/s | 117 s/MiB | 2:04:17 | Note that 1 MiB is 1024\*1024 bytes, and 1 kbit is 1,000 bits. Video ===== TODO: How easy is it to use these codecs? Are there code samples? * HVQM: "Known to be possible." Used by some retail Nintendo 64 games. * H.264: "Known to be possible." Giovanni Bajo is working on a port of Dragon's Lair, using H.264 for the video. Retrieved from "[https://n64brew.dev/wiki/Libultra/Data\_Compression?oldid=5900](https://n64brew.dev/wiki/Libultra/Data_Compression?oldid=5900) " --- # Libultra/Splitting Assets from Code - N64brew Wiki [](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code#) Libultra/Splitting Assets from Code =================================== < [Libultra](https://n64brew.dev/wiki/Libultra "Libultra") This first page will cover the important aspect of keeping your game **assets** separate from the game **code** (where assets are things like textures, models, sounds, etc...). Depending on how you've structured your project, or more importantly how far you are into it currently, this can be a relatively simple or relatively difficult task. Start by looking at the [original sample](https://github.com/n64brew/N64-Codesplit-Tutorial/tree/main/original) , before any modifications. It is a simple ROM with two rooms that can be switched between by pressing the A button, with a texture that is displayed in the middle. If you take a look at the [makefile](https://github.com/n64brew/N64-Codesplit-Tutorial/blob/main/original/Makefile) , you will see that our textures (`spr_bear` and `spr_burger`) are being compiled as C code and then being linked into the codesegment. It's not a big concern for this small project, but due to the 1MB IPL limit, this will become a problem as the project gets bigger. The idea here is that we want to have the assets elsewhere in the ROM, and we load them from the cartridge only as we need them. So first and foremost, lets move our assets over to ROM. Contents -------- * [1 Moving our assets to ROM](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code#Moving_our_assets_to_ROM) * [2 Loading assets from ROM](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code#Loading_assets_from_ROM) * [3 Having a buffer in the codesegment](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code#Having_a_buffer_in_the_codesegment) * [4 Having a buffer somewhere in RAM](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code#Having_a_buffer_somewhere_in_RAM) * [5 Managing large projects](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code#Managing_large_projects) Moving our assets to ROM ------------------------ For simplification reasons, we're going to convert our textures to binary data, instead of keeping their current form as C arrays. You should be able to do this for pretty much any sort of assets your game will need, such as static display lists. The process of actually converting your data to binary form will not be covered here, as there are plenty of tools to do that for you already. Once your assets are in a binary format, you must remove the original code files from your makefile (as they're redundant) as well as any `#include`'s relating to them. You can leave the array pointers in the code for the time being, as they'll be substituted later. The actual process of putting your assets in ROM depends on your SDK setup: Your Spec file, before adding the assets to it, would look something like this: beginseg name "code" flags BOOT OBJECT entry nuBoot address NU\_SPEC\_BOOT\_ADDR stack NU\_SPEC\_BOOT\_STACK include "codesegment.o" // Microcode includes go here (omitted for simplicity reasons) endseg // Wave's aren't used for anything in the PC SDK, they're just for visual reference beginwave name "original" include "code" endwave Adding in new raw assets is as simple as creating a new segment and specifying the `RAW` flag. For instance, having the data from `spr_bear.c` converted into binary form (with the name `spr_bear.bin`) and linking it to our ROM is as simple as: beginseg name "spr\_bear" // This name is important, and should be unique flags RAW // Specify that this segment is raw data (and not code) after "code" // Specify to put this data in ROM, right after our code segment (Although you can omit this line if you want) include "spr\_bear.bin" // The file to link endseg Do this for all the assets, and you're almost done. The next step is to open a C header file (or better yet, create a new one) and to create some `extern` calls for your new segments: extern u8 \_spr\_bearSegmentRomStart\[\]; extern u8 \_spr\_bearSegmentRomEnd\[\]; Remember that segment name I told you that was important and had to be unique? Whatever you set your segment name to, it needs to match the `extern`'s. Meaning, if you called your segment `NAME`, then you would need to define the `extern`'s as `_NAMESegmentRomStart` and `_NAMESegmentRomEnd` respectively. If you want to know more about Spec files, the online manuals do not contain a lot of information about them. Instead, it is highly recommended that you check out the **Specfile Format** chapter of the _N64 EXEGCC Compiler User Guide_ for more information. TODO Now that our assets are in ROM, we need to DMA them to be able to use them in our game. Loading assets from ROM ----------------------- Having the segment addresses for our data in ROM, it is relatively simple to DMA them from ROM. We know the starting address of our data thanks to the SegmentRomStart, and we can infer the size of the data by subtracting the start address from the end address. With Nusys, the task is relatively trivial: u32 size \= \_spr\_bearSegmentRomEnd \- \_spr\_bearSegmentRomStart; nuPiReadRom((u32)\_spr\_bearSegmentRomStart, (void\*)buffer, size); If you're not using Nusys, then there's a bit more overhead involved: OSIoMesg dmaIoMesgBuf; // The message buffer OSMesgQueue dmaMesgQ; // The message queue // This code already assumes a message queue exists and has been created with 'osCreateMesgQueue' // These variables are just here for reference u32 size \= \_spr\_bearSegmentRomEnd \- \_spr\_bearSegmentRomStart; // Invalidate data cache for the buffer to prevent memory from being destroyed osInvalDCache((void\*)buffer, size); // Start the DMA osPiStartDma(&dmaIoMesgBuf, OS\_MESG\_PRI\_NORMAL, OS\_READ, (u32)\_spr\_bearSegmentRomStart, buffer, size, &dmaMesgQ); // Wait for the DMA to finish osRecvMesg(&dmaMesgQ, NULL, OS\_MESG\_BLOCK); Please note that the code on this wiki page **does not replace the manuals**. Data DMA has very strict requirements in terms of maximum size and buffer alignment, therefore you should look up the manual pages for `nuPiReadRom` or `osPiStartDma` to make sure your DMA operation will succeed. You're probably wondering from the above code what `buffer` is. You probably understand that the data needs to go somewhere (like a variable in your code), but there's two different approaches to having data buffers: Having a buffer in the codesegment ---------------------------------- The easiest method is to simply just have a global variable which will work as a "cache": u8 buffer\[4096\]; // The exact maximum size of a texture in TMEM (4Kilobytes). The idea is, before you start rendering the level, you load any textures you need into your global cache (which can be any size you want, not just 4096 bytes). Then, when the data isn't needed anymore, you mark that part of the buffer as "empty" and you can overwrite it with new data. You're probably wondering: "Hold on, this global buffer variable is part of the code... Won't it be subject to the exact same 1MB restriction we had before?". The answer is no, because initialized global variables are not loaded from ROM (as they're dynamically "created" when your code loads). The downside to this method is that you might not always have full control over where the data is initialized to in memory. The alternative would be: Having a buffer somewhere in RAM -------------------------------- Our game has about 4MB of RAM to work with (8 if the Expansion Pak is available), and roughly 1.5 to 2MB would be occupied by the framebuffers, Z-Buffer, and the code itself. So why don't we instead make use of those 2 to 2.5MB we have free to store our assets there? All we have to do is create a new C file, and in it we place a global buffer just like we did in the previous section. Except this time, we're not going to link this data to our codesegment, rather we'll tell the N64 to reserve this part of RAM for our buffer. We'll need to modify the makefile somewhat, as we'll need to compile our C file into an object file. Lets assume our buffer is in a C file called `texbuf.c`. Your makefile should already have a dedicated `CODEFILES` variable where you put all your C files, and then it gets compiled into one big `codesegment.o` object file via `CODEOBJECTS = $(CODEFILES:.c=.o)` and then `gcc -o $(CODESEGMENT) -r $(CODEOBJECTS)`. The idea now is that you create a new variable, such as `DATAFILES`, and you place your buffer files here. DATAFILES \= texbuf.c DATAOBJECTS \= $(DATAFILES:.c\=.o) Now, you'll want to place the `DATAOBJECTS` in your makefile where it will compile the .o's, **but not link them to the codesegment**. Typically, this will be in the target section (such as `$(TARGETS):` or `default:`). Example: OBJECTS \= $(CODESEGMENT) $(DATAFILES:.c\=.o) $(TARGETS): $(OBJECTS) $(MAKEROM) spec $(MAKEROMFLAGS) \-I$(NUSYSINC) \-r $(TARGETS) \-e $(APP) makemask $(TARGETS) Since we're not linking the object file in our makefile, we'll need to do that manually afterwards: In your spec file, all you need to do is add a new segment, but give it the `OBJECT` flag instead: beginseg name "texbuf" flags OBJECT after "code" // You can use 'address' if you want to specify an exact RAM address to put the buffer at include "texbuf.o" endseg You can use `#include` in your spec file to include a header file with a list of address macros if you want to, and use that macro value in place of raw address numbers or the `after` keyword. TODO Once it's linked, you're all set! You just need to ensure your array is `extern`'d somewhere and you can use it without any other changes to your code. A useful trick, you can `extern` the codesegment as well: extern u8 \_codeSegmentRomStart\[\]; extern u8 \_codeSegmentRomEnd\[\]; You can use this, for instance, to load your assets right after the code segment by loading them into the address at `_codeSegmentRomEnd`. You don't _need_ to create an object file with your buffer as you can just write to any RAM address you seem fit (for instance, you can just define `u8* buffer = 0x80000400` globally, and then treat it as a array/buffer) but it sure helps as the compiler/linker can potentially catch buffer overflows and/or segment overlapping. You can see an implementation of the buffer method described in this section [here](https://github.com/n64brew/N64-Codesplit-Tutorial/tree/main/splitdata-binram) . Managing large projects ----------------------- After going through all of this code, you probably still have a few wiggling doubts: "How do I manage the loading of data in large projects? How can I tell what assets I have loaded, and what needs to be loaded? How do I tell if my caches are full, thus unable to read more data from the cart?". These questions are answered in the next chapter of the [code segmentation guide](https://n64brew.dev/wiki/Libultra/Code_segmentation_guide "Libultra/Code segmentation guide") , which covers file systems. Retrieved from "[https://n64brew.dev/wiki/Libultra/Splitting\_Assets\_from\_Code?oldid=5905](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code?oldid=5905) " --- # Keyboard - N64brew Wiki Keyboard ======== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/Keyboard?mobileaction=toggle_view_desktop#mw-head) [Jump to search](https://n64brew.dev/wiki/Keyboard?mobileaction=toggle_view_desktop#searchInput) The **Nintendo 64 Keyboard** (Nintendo 64 キーボード) is an accessory released for the Nintendo 64DD in Japan. Nintendo released the accessory in conjunction with Randnet DD. Stickers came with the set that you could place over the buttons. With the keyboard, players could chat with people over the internet and send them digital mail. The accessory was never released outside of Japan. It cost ¥4,600. While the keyboard itself was black, the keys were white and dark blue. It seems to be used exclusively by one game, the Randnet Disk, which is a web browser and email client. Key Matrix Map -------------- 1. Converting Joybus to Compressed: #define JBSC2CSC(x, y) ((x - 2) << 4) + (y - 1) 2. Converting Compressed back to Joybus: #define CSC2JBSC(x) ((x >> 4) + 2), (x & 0x0F) + 1 | Key Function | Compressed Scan Code (1 byte) | Joybus Scan Code (2 byte) | | --- | --- | --- | | 0 | 0x45 | \[0x06, 0x06\] | | 1 | 0xA4 | \[0x0C, 0x05\] | | 2 | 0x34 | \[0x05, 0x05\] | | 3 | 0x44 | \[0x06, 0x05\] | | 4 | 0x54 | \[0x07, 0x05\] | | 5 | 0x64 | \[0x08, 0x05\] | | 6 | 0x74 | \[0x09, 0x05\] | | 7 | 0x75 | \[0x09, 0x06\] | | 8 | 0x65 | \[0x08, 0x06\] | | 9 | 0x55 | \[0x07, 0x06\] | | A | 0xB6 | \[0x0D, 0x07\] | | Alt\_L | 0xE7 | \[0x10, 0x08\] | | Asterisk | 0x42 | \[0x06, 0x03\] | | B | 0x57 | \[0x07, 0x08\] | | BackQuote | 0xA5 | \[0x0C, 0x06\] | | BackSlash | 0xE3 | \[0x10, 0x04\] | | BackSpace | 0xB5 | \[0x0D, 0x06\] | | Bar | 0xF4 | \[0x11, 0x05\] | | BracketLeft | 0xA3 | \[0x0C, 0x04\] | | BracketRight | 0x25 | \[0x04, 0x06\] | | C | 0x37 | \[0x05, 0x08\] | | Caps\_Lock | 0xD4 | \[0x0F, 0x05\] | | Control\_L | 0xF6 | \[0x11, 0x07\] | | D | 0x36 | \[0x05, 0x07\] | | Down | 0x14 | \[0x03, 0x05\] | | E | 0x40 | \[0x06, 0x01\] | | End | 0x05 | \[0x02, 0x06\] | | Escape | 0x87 | \[0x0A, 0x08\] | | F | 0x46 | \[0x06, 0x07\] | | F1 | 0x90 | \[0x0B, 0x01\] | | F10 | 0x83 | \[0x0A, 0x04\] | | F11 | 0x02 | \[0x02, 0x03\] | | F12 | 0x95 | \[0x0B, 0x06\] | | F2 | 0x80 | \[0x0A, 0x01\] | | F3 | 0x97 | \[0x0B, 0x08\] | | F4 | 0x86 | \[0x0A, 0x07\] | | F5 | 0x96 | \[0x0B, 0x07\] | | F6 | 0x81 | \[0x0A, 0x02\] | | F7 | 0x91 | \[0x0B, 0x02\] | | F8 | 0x82 | \[0x0A, 0x03\] | | F9 | 0x92 | \[0x0B, 0x03\] | | G | 0x56 | \[0x07, 0x07\] | | Greater | 0x61 | \[0x08, 0x02\] | | H | 0x66 | \[0x08, 0x07\] | | Henkan | 0xC1 | \[0x0E, 0x02\] | | Home | 0xEF | \[0x10, 0x10\] | | I | 0x63 | \[0x08, 0x04\] | | J | 0x76 | \[0x09, 0x07\] | | K | 0x72 | \[0x09, 0x03\] | | Kana\_Lock | 0xE5 | \[0x10, 0x06\] | | L | 0x62 | \[0x08, 0x03\] | | Left | 0x04 | \[0x02, 0x05\] | | Less | 0x71 | \[0x09, 0x02\] | | M | 0x77 | \[0x09, 0x08\] | | Menu | 0x94 | \[0x0B, 0x05\] | | Meta\_L | 0xD6 | \[0x0F, 0x07\] | | Minus | 0x35 | \[0x05, 0x06\] | | Muhenkan | 0xE1 | \[0x10, 0x02\] | | N | 0x67 | \[0x08, 0x08\] | | Next | 0x06 | \[0x02, 0x07\] | | Num\_Lock | 0x84 | \[0x0A, 0x05\] | | O | 0x53 | \[0x07, 0x04\] | | P | 0x43 | \[0x06, 0x04\] | | Plus | 0x52 | \[0x07, 0x03\] | | Prior | 0x07 | \[0x02, 0x08\] | | Q | 0xA0 | \[0x0C, 0x01\] | | QuestionMark | 0x51 | \[0x07, 0x02\] | | QuoteLeft | 0x33 | \[0x05, 0x04\] | | R | 0x50 | \[0x07, 0x01\] | | Return | 0xB3 | \[0x0D, 0x04\] | | Right | 0x24 | \[0x04, 0x05\] | | S | 0xA6 | \[0x0C, 0x07\] | | Shift\_L | 0xC0 | \[0x0E, 0x01\] | | Shift\_R | 0xC5 | \[0x0E, 0x06\] | | Space | 0x41 | \[0x06, 0x02\] | | T | 0x60 | \[0x08, 0x01\] | | Tab | 0xB0 | \[0x0D, 0x01\] | | U | 0x73 | \[0x09, 0x04\] | | Up | 0x03 | \[0x02, 0x04\] | | V | 0x47 | \[0x06, 0x08\] | | W | 0x30 | \[0x05, 0x01\] | | X | 0xA7 | \[0x0C, 0x08\] | | Y | 0x70 | \[0x09, 0x01\] | | Z | 0xB7 | \[0x0D, 0x08\] | | Zenkaku\_Hankaku | 0xB4 | \[0x0D, 0x05\] | Retrieved from "[https://n64brew.dev/wiki/Keyboard?oldid=5638](https://n64brew.dev/wiki/Keyboard?oldid=5638) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Libultra/Development Troubleshooting - N64brew Wiki [](https://n64brew.dev/wiki/Libultra/Development_Troubleshooting#) Libultra/Development Troubleshooting ==================================== < [Libultra](https://n64brew.dev/wiki/Libultra "Libultra") Contents -------- * [1 VR4300 - Main CPU](https://n64brew.dev/wiki/Libultra/Development_Troubleshooting#VR4300_-_Main_CPU) * [2 RDP - Reality Display Processor](https://n64brew.dev/wiki/Libultra/Development_Troubleshooting#RDP_-_Reality_Display_Processor) * [3 RSP - Reality Signal Processor](https://n64brew.dev/wiki/Libultra/Development_Troubleshooting#RSP_-_Reality_Signal_Processor) * [4 A Blank Screen](https://n64brew.dev/wiki/Libultra/Development_Troubleshooting#A_Blank_Screen) * [4.1 SI - Serial Interface for EEPROM and Controllers](https://n64brew.dev/wiki/Libultra/Development_Troubleshooting#SI_-_Serial_Interface_for_EEPROM_and_Controllers) ### VR4300 - Main CPU ### RDP - Reality Display Processor ### RSP - Reality Signal Processor A Blank Screen -------------- A blank or freezing screen usually means the [display list](https://n64brew.dev/wiki/Display_List "Display List") sent to the RSP has an error it and didn't complete. This can be caused by a number of things. Some import details to look out for. * The z buffer and color buffer must be aligned to a 64 byte boundary * Any use of gSPDisplayList must point to a valid display list * Pointers passed to the RSP specify their segment, eg use OS\_K0\_TO\_PHYSICAL when passing pointers * Any display list called with gSPDisplayList must end with gsSPEndDisplayList ### SI - Serial Interface for EEPROM and Controllers Retrieved from "[https://n64brew.dev/wiki/Libultra/Development\_Troubleshooting?oldid=5901](https://n64brew.dev/wiki/Libultra/Development_Troubleshooting?oldid=5901) " --- # Libultra/Code segmentation guide - N64brew Wiki [](https://n64brew.dev/wiki/Libultra/Code_segmentation_guide#) Libultra/Code segmentation guide ================================ < [Libultra](https://n64brew.dev/wiki/Libultra "Libultra") [![](https://static.wikitide.net/n64wiki/9/92/So_your_code_segment_is_over_1MB.png)](https://n64brew.dev/wiki/File:So_your_code_segment_is_over_1MB.png) You've been working hard on your Homebrew title, slowly adding in more content, when suddenly you realize that the game's having weird hanging issues related to any new assets that you've just added to your ROM. What gives? Why does the Code segment size matter? -------------------------------------- When the console boots, the [Initial Program Load](https://n64brew.dev/wiki/Initial_Program_Load "Initial Program Load") which is contained in every ROM **copies the first megabyte (1,048,576 bytes) of code to RAM and executes it**. This means that if you've been linking your assets directly into your code segment, you can very quickly go past this 1MB limit. Code by itself usually has a very small memory footprint (but not negligible footprint, as we'll see later!), therefore, depending on how your engine is set up, you might only be required to split your assets from the main code and not have to worry about anything else. Where to go from here? ---------------------- These pages are aimed at providing users who are new to embedded systems programming to work around this limitation. It is expected that you have **a strong understanding of C**. The code for the example ROMs provided in these pages should all be available [in this repository](https://github.com/n64brew/N64-Codesplit-Tutorial) . The sample code is written for [libultra](https://n64brew.dev/wiki/Libultra "Libultra") , using the makefile and spec file format expected from the library. The knowledge can be easily transferred to other SDKs or build systems. Before you do anything, have a brief look over the code of the [original sample](https://github.com/n64brew/N64-Codesplit-Tutorial/tree/main/original) , as this is our "bad" starting ROM which we will improve upon. As soon as you've done that, look through the following pages (preferably in order): * [libultra/Splitting Assets from Code](https://n64brew.dev/wiki/Libultra/Splitting_Assets_from_Code "Libultra/Splitting Assets from Code") * [libultra/Filesystems](https://n64brew.dev/wiki/Libultra/Filesystems?action=edit&redlink=1 "Libultra/Filesystems (page does not exist)") * [libultra/Data Compression](https://n64brew.dev/wiki/Libultra/Data_Compression "Libultra/Data Compression") After you have read through those three pages (and more importantly, **understood them**), it is also recommended you look into techniques to split your code. It is preferable that you write your game to support at least one of these methods **before** you start adding too much to it, as it might become more challenging to modify such an intricate part of your engine after the fact: * [libultra/Overlays](https://n64brew.dev/wiki/Libultra/Overlays?action=edit&redlink=1 "Libultra/Overlays (page does not exist)") * [libultra/Relocatable Modules](https://n64brew.dev/wiki/Libultra/Relocatable_Modules?action=edit&redlink=1 "Libultra/Relocatable Modules (page does not exist)") * [libultra/Relocatable Overlays](https://n64brew.dev/wiki/Libultra/Relocatable_Overlays?action=edit&redlink=1 "Libultra/Relocatable Overlays (page does not exist)") * [libultra/TLB Mapping](https://n64brew.dev/wiki/Libultra/TLB_Mapping?action=edit&redlink=1 "Libultra/TLB Mapping (page does not exist)") Retrieved from "[https://n64brew.dev/wiki/Libultra/Code\_segmentation\_guide?oldid=5898](https://n64brew.dev/wiki/Libultra/Code_segmentation_guide?oldid=5898) " --- # ares - N64brew Wiki [](https://n64brew.dev/wiki/Ares#) ares ==== **ares** is an open-source, multi-platform emulator for several systems, including the Nintendo 64. It focuses on accurate emulation. The ares N64 core implements a GDB server, allowing debugging of a ROM using a GDB client such as the CLI GDB or most IDEs. [Instructions here](https://github.com/DragonMinded/libdragon/wiki/Debugging-via-gdb) . The ares N64 core implements [emux](https://n64brew.dev/wiki/Emux "Emux") . ares is currently the recommended go-to emulator for homebrew development. Website: [https://ares-emu.net/](https://ares-emu.net/) Retrieved from "[https://n64brew.dev/wiki/Ares?oldid=5837](https://n64brew.dev/wiki/Ares?oldid=5837) " --- # Everdrive 64 - N64brew Wiki [](https://n64brew.dev/wiki/Everdrive_64#) Everdrive 64 ============ A flash cartridge made by [Krikzz](https://krikzz.com/store/) suitable for gaming and development. Everdrive 64 V3 and X7 have a USB port for loading ROMs and performing communication with a host computer. | | | | | | | | | | --- | --- | --- | --- | --- | --- | --- | --- |List of Everdrive 64 Versions | Name | Ultra CIC (II) | USB Support | RTC Support | Storage | Detection register value | OS V2.x | OS V3.x | | Everdrive 64 V1 | No (No) | No | No | SD | | Maybe | No | | Everdrive 64 V2 | No (No) | Optional | No | SD | | Yes | Maybe | | Everdrive 64 V2.5 | Yes (No) | No | No | SD | 0xED640007 | Yes | 3.04 - 3.06 (inclusive) | | [EverDrive-64 v3](https://n64brew.dev/wiki/EverDrive-64_v3 "EverDrive-64 v3") | Yes (No) | Mini USB | Yes (DS1337) | SD | 0xED640008 | Yes | 3.04 - 3.06 (inclusive) | | [EverDrive-64 X5](https://n64brew.dev/wiki/EverDrive-64_X7 "EverDrive-64 X7") | Yes (Yes) | No | No | microSD | 0xED640013 | No | Yes | | [EverDrive-64 X7](https://n64brew.dev/wiki/EverDrive-64_X7 "EverDrive-64 X7") | Yes (Yes) | Micro USB | Yes (DS1337) | microSD | 0xED640013 | No | Yes | * Ultra CIC II allows auto console region detection without using a manual switch. * If the cart supports RTC, it also includes a battery to allow saving without reset. * If the cart is a clone, it generally supports features of a V1 cart, but may have Ultra CIC support or updated storage types. * The Advanced Homebrew header is only supported on OS V3.x. Retrieved from "[https://n64brew.dev/wiki/Everdrive\_64?oldid=5630](https://n64brew.dev/wiki/Everdrive_64?oldid=5630) " --- # Category:Emulators - N64brew Wiki [](https://n64brew.dev/wiki/Category:Emulators#) Category:Emulators ================== Other N64 emulators which do not have their own page: * [RetroArch](https://www.libretro.com/) Pages in category "Emulators" ----------------------------- The following 6 pages are in this category, out of 6 total. ### A * [Ares](https://n64brew.dev/wiki/Ares "Ares") ### B * [BizHawk](https://n64brew.dev/wiki/BizHawk "BizHawk") ### C * [CEN64](https://n64brew.dev/wiki/CEN64 "CEN64") ### G * [Gopher64](https://n64brew.dev/wiki/Gopher64 "Gopher64") ### M * [MAME](https://n64brew.dev/wiki/MAME "MAME") ### P * [Project64](https://n64brew.dev/wiki/Project64 "Project64") Retrieved from "[https://n64brew.dev/wiki/Category:Emulators?oldid=5919](https://n64brew.dev/wiki/Category:Emulators?oldid=5919) " --- # N64brew Wiki:About - N64brew Wiki N64brew Wiki:About ================== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/N64brew_Wiki:About#mw-head) [Jump to search](https://n64brew.dev/wiki/N64brew_Wiki:About#searchInput) There is currently no text in this page. You can [search for this page title](https://n64brew.dev/wiki/Special:Search/About "Special:Search/About") in other pages, or [search the related logs](https://n64brew.dev/wiki/Special:Log?page=N64brew_Wiki:About) , but you do not have permission to create this page. Retrieved from "[https://n64brew.dev/wiki/N64brew\_Wiki:About](https://n64brew.dev/wiki/N64brew_Wiki:About) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # N64brew Wiki:General disclaimer - N64brew Wiki N64brew Wiki:General disclaimer =============================== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/N64brew_Wiki:General_disclaimer#mw-head) [Jump to search](https://n64brew.dev/wiki/N64brew_Wiki:General_disclaimer#searchInput) There is currently no text in this page. You can [search for this page title](https://n64brew.dev/wiki/Special:Search/General_disclaimer "Special:Search/General disclaimer") in other pages, or [search the related logs](https://n64brew.dev/wiki/Special:Log?page=N64brew_Wiki:General_disclaimer) , but you do not have permission to create this page. Retrieved from "[https://n64brew.dev/wiki/N64brew\_Wiki:General\_disclaimer](https://n64brew.dev/wiki/N64brew_Wiki:General_disclaimer) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # User:Polprzewodnikowy - N64brew Wiki User:Polprzewodnikowy ===================== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/User:Polprzewodnikowy#mw-head) [Jump to search](https://n64brew.dev/wiki/User:Polprzewodnikowy#searchInput) AKA korgeaux in some places GitHub: [https://github.com/Polprzewodnikowy](https://github.com/Polprzewodnikowy) Retrieved from "[https://n64brew.dev/wiki/User:Polprzewodnikowy?oldid=4939](https://n64brew.dev/wiki/User:Polprzewodnikowy?oldid=4939) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Checking Integrated Circuit - N64brew Wiki Checking Integrated Circuit =========================== From N64brew Wiki (Redirected from [CIC](https://n64brew.dev/wiki/CIC?redirect=no "CIC") ) [Jump to navigation](https://n64brew.dev/wiki/CIC#mw-head) [Jump to search](https://n64brew.dev/wiki/CIC#searchInput) The **Checking Integrated Circuit**, or **CIC**, in Nintendo 64 game cartridges prevented unlicensed and pirated game cartridges from running and allowed for regional lockout. During game execution, the [PIF](https://n64brew.dev/wiki/PIF "PIF") continuously communicates with the CIC to verify the game is valid. Reference --------- [REcon 2015 - Reversing the Nintendo 64 CIC](https://www.youtube.com/watch?v=HwEdqAb2l50) Open Source Implementations --------------------------- * [https://github.com/mikeryan/UltraCIC](https://github.com/mikeryan/UltraCIC) * [https://github.com/jago85/UltraCIC\_C](https://github.com/jago85/UltraCIC_C) * [https://github.com/perkinsb1024/UltraCIC-II](https://github.com/perkinsb1024/UltraCIC-II) * [https://github.com/ManCloud/UltraCIC-III](https://github.com/ManCloud/UltraCIC-III) * [https://github.com/hcs64/boot\_stub](https://github.com/hcs64/boot_stub) Retrieved from "[https://n64brew.dev/wiki/Checking\_Integrated\_Circuit?oldid=131](https://n64brew.dev/wiki/Checking_Integrated_Circuit?oldid=131) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # ROM Metadata - N64brew Wiki ROM Metadata ============ From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/ROM_Metadata#mw-head) [Jump to search](https://n64brew.dev/wiki/ROM_Metadata#searchInput) Owner N64Brew community (Discord) Version 1.0 - 2025-12-04 Contents -------- * [1 Context](https://n64brew.dev/wiki/ROM_Metadata#Context) * [2 Glossary](https://n64brew.dev/wiki/ROM_Metadata#Glossary) * [3 Overview](https://n64brew.dev/wiki/ROM_Metadata#Overview) * [4 Reader workflow](https://n64brew.dev/wiki/ROM_Metadata#Reader_workflow) * [5 Writer workflow](https://n64brew.dev/wiki/ROM_Metadata#Writer_workflow) * [6 Detailed spec](https://n64brew.dev/wiki/ROM_Metadata#Detailed_spec) * [6.1 Metadata ZIP file](https://n64brew.dev/wiki/ROM_Metadata#Metadata_ZIP_file) * [6.2 External metadata](https://n64brew.dev/wiki/ROM_Metadata#External_metadata) * [6.3 Embedded metadata](https://n64brew.dev/wiki/ROM_Metadata#Embedded_metadata) * [6.4 metadata.ini](https://n64brew.dev/wiki/ROM_Metadata#metadata.ini) * [6.5 INI syntax](https://n64brew.dev/wiki/ROM_Metadata#INI_syntax) * [6.6 metadata.ini extensions](https://n64brew.dev/wiki/ROM_Metadata#metadata.ini_extensions) * [7 FAQ on this spec](https://n64brew.dev/wiki/ROM_Metadata#FAQ_on_this_spec) * [8 Available implementations](https://n64brew.dev/wiki/ROM_Metadata#Available_implementations) Context ------- Flashcart menus (such as [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu) ), ROM managers and other tools that manipulate N64 roms often want to provide to users some information on the ROM such as common metadata (publisher name, date of release, etc.), graphics like boxarts, and even an icon or some screenshot. For commercial games, these metadata are created via a database of entries, that is a feasible approach for a closed set of ROMs like those released during N64 commercial life. However, modern N64 homebrew releases (and even modern commercial releases) do not have a way to provide these information themselves to the menus. This problem is similar to that of configuring properly an emulator or a flashcart for playing the game (eg: save type), and is already solved via the N64 [Homebrew Header](https://n64brew.dev/wiki/ROM_Header#Advanced_Homebrew_ROM_Header) : an extension of the ROM header to provide additional information in unused bytes. This document highlights a further extension of the Homebrew Header to also provide metadata and images, by standardizing a way to ship additional files attached/appended to the ROM. Glossary -------- ROM file A ROM file, in big-endian format (normally saved with extension .Z64) Embedded metadata A ZIP file that is appended to the N64 ROM, containing the metadata for the game. External metadata A ZIP file that sits next to the N64 ROM, for instance on the PC filesystem or on the SD, containing the metadata for the game. Reader A software that wants to read/extract the metadata and images for the ROM Writer A software that wants to prepare metadata for a ROM, and possibly embed it into it. Overview -------- We extend the Homebrew Header as follows: * We use 1 byte in the header to indicate the existence of embedded metadata. * Embedded metadata is stored as an \*\*uncompressed ZIP file\*\*, appended to the ROM. * Alternatively, metadata can be stored externally (eg: on SD or on PC), next to the ROM file, using the same name of the ROM with extension `.meta`. * The ZIP file must contain a file called `metadata.ini`, with a fixed, expandable schema, that contains ROM metadata for menus and tools to display. * `metadata.ini` can also indicate the presence of images for boxarts, screenshots or icons, by specifying their filenames. These files are expected to be present in the ZIP. Reader workflow --------------- This is a description of what a reader is supposed to do to parse the metadata in the ROM: * (If applicable) Check if there is a file next to the ROM with the same name called \`.meta\`. If present, that file is a ZIP that contains the metadata. * Otherwise, verify if the Homebrew Header is present in the ROM. If so, verify if the byte indicating the embedded data is set to 1. If so, the ZIP file is at the end of ROM. * Open the ZIP file. * NOTE: ZIP files are parsed from the end, so if you use a ZIP library and the ROM contains an embedded ZIP, you can just pass the ROM itself to the ZIP library. * Read `metadata.ini` from the ZIP, parse it to extract metadata. * If the metadata mention filenames for images like boxarts or screenshots, search for those files in the ZIP as well to extract them. If you are on PC and you want to manipulate with embedded metadata, you can just treat the ROM as a ZIP file. For instance, from a CLI, you can run `unzip rom.z64` to extract all the embedded metadata, if any. Writer workflow --------------- This is a description of what a writer is supposed to do to embed metadata into a ROM: * Prepare a ZIP file with no compression with the \`metadata.ini\` file and all the associated images. * To embed it into a ROM, just concatenate it to the ROM itself. * If the ROM is not using the [Homebrew Header](https://n64brew.dev/wiki/ROM_Header#Advanced_Homebrew_ROM_Header) , change the game code to \`ED\` to start using it. * Activate bit 0 in byte `0x38` of the ROM. Normally that byte will be 0, so you can just change it to 1. * Otherwise, to keep the metadata external, just rename the zip file as `.meta`, with the same filename of the ROM, and put it next to it. No need to change header in this case. A simple way to do the above manually, without tool, is simply using standard CLI tools: zip -0 metadata.zip metadata/\* cat myrom.z64 metadata.zip >myrom2.z64 printf "\\x01" | dd if=myrom2.z64 seek=$((0x38)) bs=1 conv=notrunc Detailed spec ------------- ### Metadata ZIP file The ZIP file is the container for the metadata. This file follows the official ZIP specs and is supposed to be created with standard tools. Writers **MUST** respect the following constraints while creating the ZIP file: * Files in the ZIP **MUST** be uncompressed (_"stored"_). This helps the readers avoiding having to use deflate/zlib to extract information. Also most of the content are either very small or already compressed, so an additional compression layer isn't useful. Readers **MUST NOT** assume that the ZIP file follows specific conventions not listed in this document. Readers **SHOULD** use a battle-tested ZIP library to make sure they correctly handle ZIP files generated by various, different tools, using different various of features (eg: zip64 headers may or may not be present). Readers **MUST** ignore files in the ZIP that are not needed for metadata parsing. This is required for forward compatibility as future specs might want to add files to the same ZIP for different purposes. ### External metadata The Metadata ZIP can be stored externally from the ROM. This is the preferred solution for ROMs that were not released with metadata already embedded (eg: commercial ROMs), to avoid modifying them. Metadata ZIP file must sit next to the ROM, with the same file and different extension: funny-game.z64 # the actual ROM funny-game.meta # metadata ZIP, renamed as .meta Readers **MUST** give priority to external metadata over embedded metadata, to allow for a customisation point in case embedded metadata must be changed after official ROM release. Writers **MUST NOT** modify the header of the ROM to signal the presence of external metadata. Header changes are needed only for embedded metadata. ### Embedded metadata The metadata ZIP can be embedded in the ROM. This is the preferred solution for romhacks and homebrew productions, where the author decides to take advantage of this spec, so that the ROM and its metadata is self-contained in a single file. The presence of the metadata ZIP embedded in the ROM must be advertised by setting bit `0` in byte `0x38`. This byte is otherwise unused (at the time of writing this spec), so it should normally be 0. For forward compatibility, all other bits should be ignored/preserved. Writers **MUST** set bit 0 in byte 0x38 to 1, when embedding the ZIP in the ROM, preserving the value of other bits. Readers **MUST** check bit 0 (and only bit 0) in byte 0x38 to check whether it is set, before trying to access metadata in the ROM. ### metadata.ini The file `metadata.ini` is the only one that is defined in this specification that must be part of the metadata ZIP. The file **MUST** be written in UTF-8 format using UNIX newlines (`LF`). Readers SHOULD also support DOS newlines (`CRLF`) to facilitate users on Windows. The schema of the INI format is described here: ; Main metadata, in English \[meta\] ; Name of the game (similar to the header name, ; but encoded as UTF-8, so more flexible) name \= Shooter⁶⁴ ; Author of the game (publisher, developer, etc.) author \= Game Publisher Inc. ; Release date of the game. Use YYYY-MM-DD format. release-date \= 2025-11-04 ; Open Source license the game is released under. ; This must be the SPDX code of the license. ; If the key is omitted or empty, it is assumed to ; be a non open-source game osi-license \= GPL-3.0 ; URL of a website for a game. Can be a proper website ; or just a GitHub repo website \= https://example.com ; Self-certified age rating for the game. This is a number ; identifying the minimum suggested age for a player (0-18). ; This allows readers to provide some kind of content filtering ; capability. You can follow ESBR/PEGI guidelines to get ; some suggestions on this. age-rating \= 7 ; Short description of the game, to be displayed in ; menu or ROM managers. Suggested maximum length is ; 120 chars. short-desc \= Shoot your way to the heaven, fighting against birds and aliens ; Long description of the game, stored in an external ; file, to be displayed in menu or ROM managers. ; The file must have a ".txt" extension and must contain ; raw UTF-8 text with no specific markup. Assume it will ; be word-wrapped during display, but newlines will be ; preserved, so keep paragraphs in long lines. long-desc \= description.txt ; Screenshots. If defined, this must be a list of ; comma-separated files in the ZIP that contain ; screenshots of the gameplay. ; Supported formats are PNG or JPG ; Screenshots are expected to be in native ; resolution (eg: 320x240) or smaller. screenshots \= screen1.png, screen2.png, screen3.png ; Localized meta section. The section name uses ; the format \[meta.\], using ISO 639-1 language ; code (two letters). ; You can localize any or all keys of the main section \[meta.es\] short-desc \= Dispara hacia el cielo luchando contra aves y alienígenas \[meta.it\] short-desc \= Spara per raggiungere il paradiso, combattendo uccelli e alieni ; Boxarts. All the keys in this section must be ; associated with values that are filenames in the ; metadata ZIP. ; Not all keys must be specified, leave the others ; commented. ; Supported file formats are PNG and JPG. ; Maximum resolution is 320x240 (or 240x320 for ; vertical pictures). \[boxart\] front \= bx\_front.jpg back \= bx\_back.jpg ;top = ;bottom = ;left = ;right = ; Cartarts (images of the stickers on the cartridge). ; All the keys in this section must be ; associated with values that are filenames in the ; metadata ZIP. ; Not all keys must be specified, leave the others ; commented. ; Supported file formats are PNG and JPG. ; Maximum resolution is 320x240 (or 240x320 for ; vertical pictures). \[cartart\] front \= cx\_front.png back \= cx\_back.png ; It is possible to localize also boxarts if needed \[boxart.es\] front \= bx\_front\_es.jpg back \= bx\_back\_es.jpg Writers **MUST** ensure the INI file is correctly encoded as UTF-8, and refuse to process a file which does not validate as UTF-8. Writers **SHOULD** verify that all reference to files use relative paths (not absolute paths) and do not contain `.` or `..`, so that the paths will work as-is within the ZIP file. Readers **MUST** ignore all keys that they don't know, for forward compatibility. Readers that want to support localizations **SHOULD** use a fallback mechanism: when a key is found in the language-specific section, it will take precedence over the same key in the general section. ### INI syntax The INI file format is underspecified. This spec sticks to a very simple subset that should be supported by most/all INI libraries. In particular: * Commented lines start with `;` * Inline comments are not supported * No escaping or quoting is supported for values * No support for line continuation A parser that supports extended features will likely work as well, but it's not required. ### `metadata.ini` extensions If Readers need to have additional keys added to the spec, and those keys are meant to be of general use, please contact the n64brew server on Discord to modify the spec to add the data. Readers can also decide to extend the spec with custom keys not part of this spec. In this case, Readers **MUST** parse their own custom keys in a custom section (eg: `[foo]`). Reader **SHOULD NOT** add and parse custom additional keys in the official sections, as those might conflict in the future with new versions of this spec. FAQ on this spec ---------------- Why using INI instead of JSON / YAML / XML? A previous iteration of the draft was in fact a JSON file. We discussed a bit about it and decided to switch to INI. The reasons are: * The metadata file is supposed to also be parsed on N64 itself (flashcart menus). Menu authors expressed concerns about parsing JSON in the embedded context. We evaluated a few C libraries but they all feel a bit overbloated for the purpose of parsing a key/value string array, which is what we need. * The file is supposed to be edited manually by developers working on the ROM. They will want to comment some lines, change something, etc. JSON files are a bit unfriendly for manual editing as they have extra quotes and escaping, they're picky about commas, etc. * The metdata file in the spec is supposed to be also the basic default one, with comments explaining the meaning of the various keys. This is useful as people can read it, go through it, change things, comment keys out. JSON doesn't allow for comments. JSON seems more fit as a machine interchange format for rich data structures. Using it as a manually edited configuration file is a bit cumbersome. Why is it required to tweak the header (byte 0x38) in case of an embedded ZIP? Unfortunately, the way ZIP is designed requires backward scanning of up to 64K of data at the end of the file, just to find out whether a ZIP is present or not. This is a bit cumbersome to run on N64, so we feel like adding a bit in the header will avoid useless scanning on many ROMs not having the embedded metadata. Available implementations ------------------------- * [n64metadata](https://github.com/DragonMinded/libdragon/blob/preview/tools/n64metadata.cpp) is a command-line tool developed as part of the Libdragon SDK. It parses an input `metadata.ini`, validates it, and then creates and embeds a metadata ZIP into a provided ROM. It can be used with any ROM (including libultra, romhacks, etc.), as it's not really related to libdragon in any way. It's also a single C++ file with no dependencies, so extremely easy to build in any development environment. * For Libdragon-based projects, everything is integrated in the build system: you just need to define `N64_ROM_METADATA` in your Makefile and make it point to your `metadata.ini` file. The [Brew Volley example](https://github.com/DragonMinded/libdragon/tree/preview/examples/brew-volley) demonstrates that. * A single-file, HTML viewer is distributed by libdragon for [download](https://github.com/DragonMinded/libdragon/blob/gh-pages/static/metadata.html) and is also [hosted online](https://libdragon.dev/static/metadata.html) . * [Romhacking (RHDC)](https://romhacking.com/) supports creation of a metadata ZIP via its API. If you published a romhack there, you can download a `metadata.zip` via `GET https://api.romhacking.com/v3/hacks/metadata/sha1/{hash},` where `{hash}` is the SHA-1 hash of the z64 file. As explained in the spec, you can then rename it as `romname.meta` and put it next to the romhack to make it available to menus and emulators. Retrieved from "[https://n64brew.dev/wiki/ROM\_Metadata?oldid=5793](https://n64brew.dev/wiki/ROM_Metadata?oldid=5793) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Pages using deprecated source tags](https://n64brew.dev/wiki/Category:Pages_using_deprecated_source_tags?action=edit&redlink=1 "Category:Pages using deprecated source tags (page does not exist)") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # N64brew Wiki Main Page ========= From N64brew Wiki [Jump to navigation](https://n64brew.dev/#mw-head) [Jump to search](https://n64brew.dev/#searchInput) | Welcome to the N64brew Wiki! | | --- | | This wiki is a collaboration among the homebrew community, proving accurate documentation of the Nintendo 64, its peripherals, and related software.

**Everyone is [welcome to contribute](https://n64brew.dev/wiki/Help:Editing "Help:Editing")
!**

Find us on [Discord](https://discord.gg/WqFgNWf)
, and be sure to check out [homebrew, hardware, and more](https://n64brew.dev/wiki/Homebrew_Projects "Homebrew Projects")
from community members.
Also take a look at the [Frequently Asked Questions](https://n64brew.dev/wiki/FAQ "FAQ")
. | **Hardware****Software** | Physical Components | I/O Interfaces | | --- | --- | | * [VR4300 CPU](https://n64brew.dev/wiki/VR4300 "VR4300")
* [FPU - CP1](https://n64brew.dev/wiki/COP1 "COP1")

* [SysAD Interface](https://n64brew.dev/wiki/SysAD_Interface "SysAD Interface")

* [Reality Coprocessor - RCP](https://n64brew.dev/wiki/Reality_Coprocessor "Reality Coprocessor")
* [Reality Signal Processor - RSP](https://n64brew.dev/wiki/Reality_Signal_Processor "Reality Signal Processor")

* [Reality Display Processor - RDP](https://n64brew.dev/wiki/Reality_Display_Processor "Reality Display Processor")

* [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM")

Rambus DRAM shared by the console

* [PIF-NUS](https://n64brew.dev/wiki/PIF-NUS "PIF-NUS")

A 4-bit microcomputer used to communicate with the controllers and EEPROM

* [Audio DAC](https://n64brew.dev/wiki/Audio_DAC "Audio DAC")

* [Video DAC](https://n64brew.dev/wiki/Video_DAC "Video DAC") | * [Memory map](https://n64brew.dev/wiki/Memory_map "Memory map")

* [MI - MIPS Interface](https://n64brew.dev/wiki/MIPS_Interface "MIPS Interface")

* [VI - Video Interface](https://n64brew.dev/wiki/Video_Interface "Video Interface")

* [AI - Audio Interface](https://n64brew.dev/wiki/Audio_Interface "Audio Interface")

* [PI - Parallel Interface](https://n64brew.dev/wiki/Parallel_Interface "Parallel Interface")

* [RI - RDRAM Interface](https://n64brew.dev/wiki/RDRAM_Interface "RDRAM Interface")

* [SI - Serial Interface](https://n64brew.dev/wiki/Serial_Interface "Serial Interface")
* [Joybus Protocol](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol")

Communication protocol between the PIF, game cartridge, and connected controllers | | Controllers | Paks | Addons / Miscellaneous | | --- | --- | --- | | * [Tri-Wing Controller](https://n64brew.dev/wiki/Controller "Controller")

* [Train Controller](https://n64brew.dev/wiki/Train_Controller "Train Controller")

Used exclusively for Densha de Go

* [Mouse](https://n64brew.dev/wiki/Mouse "Mouse")

* [Randnet Keyboard](https://n64brew.dev/wiki/Randnet_Keyboard "Randnet Keyboard")

* [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit")

* [Dance Pad](https://n64brew.dev/wiki/Dance_Pad "Dance Pad")

* [Fishing Rod](https://n64brew.dev/wiki/Fishing_Rod "Fishing Rod") | * [Game Pak](https://n64brew.dev/wiki/Game_Pak "Game Pak")
(Cartridge)
* [Controller Pak](https://n64brew.dev/wiki/Controller_Pak "Controller Pak")
(Memory Pak)
* [Rumble Pak](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak")

* [Transfer Pak](https://n64brew.dev/wiki/Transfer_Pak "Transfer Pak")

* [Expansion Pak](https://n64brew.dev/wiki/Expansion_Pak "Expansion Pak")

* [Jumper Pak](https://n64brew.dev/wiki/Jumper_Pak "Jumper Pak") | * [64DD](https://n64brew.dev/wiki/64DD "64DD")
(64 Disk Drive)
* [Doctor V64](https://n64brew.dev/wiki/Doctor_V64 "Doctor V64")

* [Flashcarts](https://n64brew.dev/wiki/Flashcarts "Flashcarts")

* [Partner-N64](https://n64brew.dev/wiki/Partner-N64 "Partner-N64") | | Programming Tools / SDK's | Game Development | | --- | --- | | * [libdragon](https://n64brew.dev/wiki/Libdragon "Libdragon")

Homebrew SDK, Public Domain, 2D & 3D, Audio and Controller support, OpenGL 1.1

* [libultra](https://n64brew.dev/wiki/Libultra "Libultra")

Nintendo's Official SDK, Partial source available

* [iQue SDK](https://n64brew.dev/wiki/IQue_SDK "IQue SDK")

Development library for the iQue Player

* [pseultra](https://n64brew.dev/wiki/Pseultra "Pseultra")

Abandoned, Homebrew SDK, BSD 3 Clause License, 2D & 3D, Controller support but no audio

* [SGI Audio Tools](https://n64brew.dev/wiki/SGI_Audio_Tools "SGI Audio Tools")

* [SGI Workstations using IRIX](https://n64brew.dev/wiki/N64_IRIX "N64 IRIX") | * [Getting Started](https://n64brew.dev/wiki/Getting_Started "Getting Started")

An introduction for beginners who wish to delve into homebrew development

* [Game Jams](https://n64brew.dev/wiki/Category:Game_Jams "Category:Game Jams")

List of homebrew game development events

* [Building GCC](https://n64brew.dev/wiki/Building_GCC "Building GCC")

Guide to building a GCC cross-compiler for N64 development

* [MIPS Assembly](https://n64brew.dev/wiki/MIPS_Assembly "MIPS Assembly")

Notes about MIPS assembly programming

* [Instruction Cheatsheet](https://n64brew.dev/wiki/MIPS_III_instructions "MIPS III instructions")

A breakdown of CPU and FPU instructions and their opcodes | Retrieved from "[https://n64brew.dev/wiki/Main\_Page?oldid=5909](https://n64brew.dev/wiki/Main_Page?oldid=5909) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Display List - N64brew Wiki Display List ============ From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/Display_List#mw-head) [Jump to search](https://n64brew.dev/wiki/Display_List#searchInput) A display list (also known as a command list) is a list of 64-bit GBI commands sent by the CPU. A display list can be nested up to 10 levels deep; an element can be a pointer to another display list. Display lists are read by microcode and ensure that an object is always rendered the same way. The RSP has a display list cache size of 32 elements. Retrieved from "[https://n64brew.dev/wiki/Display\_List?oldid=4330](https://n64brew.dev/wiki/Display_List?oldid=4330) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # BizHawk - N64brew Wiki BizHawk ======= From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/BizHawk#mw-head) [Jump to search](https://n64brew.dev/wiki/BizHawk#searchInput) **BizHawk** is an open-source, multi-platform emulator for several systems, including the Nintendo 64. It mainly sees usage in Tool Assisted Speedruns. BizHawk contains two N64 cores to choose from: ares N64 core and mupen64plus. However it severely lags behind in updating the ares N64 core compared to upstream ares, so despite the quality of ares as a N64 emulator BizHawk is not suitable for homebrew development. Website: [https://tasvideos.org/BizHawk](https://tasvideos.org/BizHawk) Retrieved from "[https://n64brew.dev/wiki/BizHawk?oldid=5831](https://n64brew.dev/wiki/BizHawk?oldid=5831) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Emulators](https://n64brew.dev/wiki/Category:Emulators "Category:Emulators") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # emux - N64brew Wiki emux ==== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/Emux#mw-head) [Jump to search](https://n64brew.dev/wiki/Emux#searchInput) emux: N64 emulator extensions for homebrew developers This document describes a set of extensions to the N64 hardware that emulators can optionally support to help the development of homebrew software. This document is licensed under the CC0 license. Version: 1.1 (released April 29th 2026) To suggest modifications, please contact the n64brew community on Discord. Contents -------- * [1 Extension entrypoint](https://n64brew.dev/wiki/Emux#Extension_entrypoint) * [2 Disassembly](https://n64brew.dev/wiki/Emux#Disassembly) * [3 Extension detection](https://n64brew.dev/wiki/Emux#Extension_detection) * [4 Emulator-level extension toggle](https://n64brew.dev/wiki/Emux#Emulator-level_extension_toggle) * [5 Extension list](https://n64brew.dev/wiki/Emux#Extension_list) * [5.1 0x20: xdetect](https://n64brew.dev/wiki/Emux#0x20:_xdetect) * [5.2 0x21: xbreak](https://n64brew.dev/wiki/Emux#0x21:_xbreak) * [5.3 0x22: xbreakpoint](https://n64brew.dev/wiki/Emux#0x22:_xbreakpoint) * [5.4 0x23: xtracestart](https://n64brew.dev/wiki/Emux#0x23:_xtracestart) * [5.5 0x24: xtracestop](https://n64brew.dev/wiki/Emux#0x24:_xtracestop) * [5.6 0x25: xlog](https://n64brew.dev/wiki/Emux#0x25:_xlog) * [5.7 0x26: xlogregs](https://n64brew.dev/wiki/Emux#0x26:_xlogregs) * [5.8 0x27: xhexdump](https://n64brew.dev/wiki/Emux#0x27:_xhexdump) * [5.9 0x28: xprof](https://n64brew.dev/wiki/Emux#0x28:_xprof) * [5.10 0x29: xprofread](https://n64brew.dev/wiki/Emux#0x29:_xprofread) * [5.11 Metrics list](https://n64brew.dev/wiki/Emux#Metrics_list) * [5.12 0x2A: xexception](https://n64brew.dev/wiki/Emux#0x2A:_xexception) * [5.13 0x2C: xioctl](https://n64brew.dev/wiki/Emux#0x2C:_xioctl) * [5.13.1 xioctl operation: exit](https://n64brew.dev/wiki/Emux#xioctl_operation:_exit) * [5.13.2 xioctl operation: fast/slow](https://n64brew.dev/wiki/Emux#xioctl_operation:_fast/slow) Extension entrypoint -------------------- All extensions use unused COP0 opcodes. On both VR4300 and RSP, unused COP0 opcodes are completely ignored and generate no (known) side effects. COP0 opcodes are identified by the last 6 bits in the 32-bit opcode word. We use values in the range 0x20..0x3F, which are unused. Moreover, bits from 6 to 24 in the opcode word are conventionally allocated as follows: * Bits 24..20: `rd`, refer to a GPR register of the CPU * Bits 19..15: `rt`, refer to a GPR register of the CPU * Bits 14..6: `code`, 9 bit of immediate code Emulators should emulate emux extenions as if they take exactly 1 clock cycle each one, and cause no stalls on input/output registers. Disassembly ----------- All extension mnemonics start with x. The documentation states which input/output each extension uses. Disassembly should conventionally list arguments in this order: xopcode rd, rt, code though if an argument is unused by the opcode, it can be ignored. Some opcodes also have "optional" arguments, that is, they use the special 0 value as default. In that case, the disassembly can also hide the whole argument. For instance, `xlog` uses `code`, optionally uses `rt`, and always uses `rd`. All the following syntaxes are valid disassembly of the same opcode word, but the last form is the suggested one: xlog s7, zero, 0 xlog s7, zero xlog s7 Extension detection ------------------- The guest application might want to detect whether the emulator supports extensions (and which ones are supported, as an emulator might not implement the full specification). To do so, the application can try to run the extension `xdetect` (opcode 0x20). `xdetect` (0x20) is used specifically as a way to detect extensions. The specified `rd` register is used as output of the extension: it will be filled with a 32-bit mask, where each bit reports whether the corresponding extension is supported (`code` must be currently set to 1, see below for more information). Thus, a way to detect emulator extensions is the following: li at, 0 \# clear at (in case emux is not supported) xdetect at, 1 \# run extension detection bnez at, has\_extensions \# if not zero, extensions are supported Notice how the above sequence is completely harmless when run on real hardware. Emulator-level extension toggle ------------------------------- We suggest emulators to add a user-level option to enable or disable emulator homebrew extensions. While extensions have been designed to be completely harmless when run on hardware and on emulators of any accuracy level, we advise against providing a too easy way for a ROM to perform a more general "emulator detection" that cannot be overridden by the user. We value a homebrew ecosystem that targets real hardware first and foremost, and we want to avoid as much as possible homebrew software that explicitly change their behavior when an emulator is detected (at least, not without user consent). By providing a way to disable extension support at the emulator level, we give the users the final choice on whether they want to play "emulator enhancements" or not, and we avoid a fragmented ecosystem where ROMs behave differently on different emulators and on hardware. Extension list -------------- This is the list of all defined extensions. The extension code is the COP0 opcode that should be used when encoding that extension. Emulators should treat all other opcodes as no-op (which is also the correct hardware behavior), to allow for future expansion. ### 0x20: `xdetect` Detect extensions supported by the emulator. Input: * `code`: index of the capability word to get. At the moment, supported values are: * `0x00`: get mask for extensions 0x00 - 0x1F (currently none) * `0x01`: get mask for extensions 0x20 - 0x3F * `0x02`: get mask for operations of `xioctl` (eg: bit 2 checks if `xioctl fast` is supported). Notice that this must return 0 for emulators not supporting `xioctl` at all. Output: * `rd`: 32-bit bitmask where 1s mark supported extensions. For invalid codes, `rd` will be cleared to 0. The specified register is filled with a bitmask, where each bit corresponds to an extension; the bit should be set to 1 if the extension (identified by its opcode) is supported by the emulator, and to 0 otherwise. Since there might be more than 32 extensions, the input `code` value specifies which group to get. Currently, all extensions are mapped in range 0x20-0x3F. `xdetect` returns the list of extensions supported by the CPU on which it is being run (VR4300 or RSP). So the masks might different, if the emulator implements different subsets of the specs on each CPU. _NOTE_: callers are advised to clear `rd` with 0 before calling. This way, if emux is not supported at all, `rd` will keep its zero value, and the following code can assume that no emux extensions are supported. ### 0x21: `xbreak` Immediate break in the debugger. When this opcode is run, the emulator should break into its builtin debugger to allow to inspect the state. ### 0x22: `xbreakpoint` Set/unset a breakpoint/watchpoint within the debugger Input: * `rd`: virtual address of the memory location where the breakpoint should be configured * `code`: Sub function: * 1: Add a breakpoint at the specified address * 2: Remove a breakpoint from the specified address * 3: Add a read watchpoint at the specified address * 4: Remove a read watchpoint at the specified address * 5: Add a write watchpoint at the specified address * 6: Remove a write watchpoint at the specified address ### 0x23: `xtracestart` Start tracing from the current PC until manually stopped. This extension asks the emulator to start tracing opcodes run after it on the current CPU. Traces are emulator dependent, so this specification does not provide any guidance on the actual format of traces, or where they should be written or displayed. Tracing should be activated only on the CPU that has run this opcode (so either the VR4300 or the RSP). Input: * `code`: if not zero, specifies the maximum number of instructions to trace. If zero, it means unlimited (until manually stopped) Running `xtracestart` while another trace is in progress will reset the tracing engine to the new specified configuration. For instance, if a limited trace was in progress, when `xtracestart 0` is run, the trace will switch to limitless mode and keep tracing until manually stopped. Example: xtracestart 100 This extension will cause the emulator to emit a trace of the next 100 opcodes run by this CPU. `xtracestart` itself is never part of the trace. ### 0x24: `xtracestop` Stop tracing. Notice that it is valid to run this extension also while a limited trace is running. For instance, if the ROM has previously run the extension `xtracestart 100` and is currently tracing, if the 60th opcode being run is a `xtracestop`, it should stop tracing there, without tracing the next 40 opcodes. `xtracestop` is always part of the trace, effectively being the last instruction being traced. ### 0x25: `xlog` This extension asks the emulator to display a log message, with a specified content. Each call to `xlog` will provide zero or more characters to display, which might or might not form a single complete message. The emulator should make no assumptions on the formatting of the string, and specifically it must not assume that each extension call is a "complete line of text". The best way to display the log is akin to piping the bytes directly to the console (or writing the bytes into a log file). The emulator should avoid adding newlines, such as displaying the string provided by a single `xlog` extension into a standalone line. On the other hand, it is allowed for the emulator to buffer internally the received data until a newline is received. The text to display is always treated as UTF-8 encoded string. Input: * `code`. If 0, the string is treated as NULL-terminated (`rt` is ignored). If 1, the string length must be put in `rt`. Other values are undefined. * `rd`: virtual address (VR4300) or DMEM address (RSP) of the zero terminated string to log. * `rt`: length of the string in bytes. This is used only if `code` is 1. A zero terminated string is a sequence of bytes whose length is not explicitly declared, but is terminated by the special byte `0x0`. As explained above this string does not necessarily constitute a full message, and should simply be appended to the previous logged contents. Example: MSG: .byte "hello\\n\\0" la t0, MSG xlog t0 this should make the emulator emit a log such as: \[DEBUG\] hello Another example: MSG: .byte "hello\\n" la t0, MSG li t1, 3 xlog t0, t1, 1 la t0, MSG+3 xlog t0, t1, 1 This should also make the emulator emit a log such as: \[DEBUG\] hello Notice that the emulator MUST NOT display this output: \[DEBUG\] hel \[DEBUG\] lo because, as explained, a single `xlog` call is not guaranteed to represent a complete message. ### 0x26: `xlogregs` Log registers of the current CPU. Input: * `rd`: register containing a 32-bit bitmask of which registers must be dumped. If the register has value 0, all (existing) registers should be dumped * `code[0..1]`: a value specifying which register file to dump: * 0: COP0 registers * 1: COP1 registers (valid only on VR4300) * 2: COP2 registers (valid only on RSP) * 3: GPR registers * `code[2]`: if 1, prefer signed decimal representation. If 0, hexadecimal. * `code[3]`: if 1, interpret registers as 64-bit floating point values (valid only for COP1) * `code[4]`: if 1, dump also "extra registers" not covered by the 32-bit bitmasks * For COP1 (VR4300): FCR0/FCR31 * For COP2 (RSP): accumulator, flags * For GPR (VR4300): hi/lo, program counter * For GPR (RSP): program counter Example: #define XDUMP\_GPR 3 xlogregs $0, XDUMP\_GPR this should provide an output such as: GPR: zr: ---- ---- 0000 0000 at: ---- ---- 8080 0000 v0: ---- ---- 800c 0000 v1: ---- ---- 800c 0000 a0: ---- ---- 8006 dd88 a1: ff01 0401 0000 0000 a2: ff01 0401 0000 0000 a3: ff01 0401 0000 0000 t0: ---- ---- 8004 72b0 t1: ---- ---- 0000 0004 t2: ff01 0401 0000 0000 t3: ff01 0401 0000 0000 t4: ff01 0401 0000 0000 t5: ---- ---- 0000 0000 t6: ---- ---- 0000 0000 t7: ---- ---- 807f fdc0 s0: ---- ---- 8006 dd88 s1: ---- ---- 8004 65b0 s2: ---- ---- 8004 6610 s3: ffff ffff 0000 0000 s4: ---- ---- 8004 65b0 s5: ---- ---- 0050 4040 s6: ---- ---- 0000 003f s7: ---- ---- 0000 0001 t8: ---- ---- 0000 0000 t9: ---- ---- 8002 42c0 k0: 8002 56c0 0000 000f k1: ---- ---- 807f fef8 gp: ---- ---- 8004 df60 sp: ---- ---- 807f fe78 s8: ---- ---- 0000 0000 ra: ---- ---- 8001 597c lo: ---- ---- 0000 000f hi: ---- ---- 0000 0080 Example: #define XDUMP\_GPR 3 #define XDUMP\_DECIMAL (1<<2) li t0, (1<<8)|(1<<9)|(1<<10) \# Dump registers t0,t1,t2 xdump t0, XDUMP\_GPR | XDUMP\_DECIMAL this should provide an output such as: t0: 1458 t1: -123 t2: 256 Example: #define XDUMP\_FPU 1 xlogregs $0, XDUMP\_FPU should produce an output such as: FPR: $f0: 40f000007f0834ca $f1: 40f000004cb2d05e $f2: 3faeccfe43044fe1 $f3: 3fc66666434a5eca $f4: 000000004324ee6a $f5: 00000000433a9a62 $f6: 000000004260a23a $f7: 000000004311207f $f8: 0000000043152a03 $f9: 00000000423d34d3 $f10: 0000000042c00000 $f11: 0000000000000000 $f12: 40f00000ff0834ca $f13: 3f84282242800000 $f14: 0000000042d76040 $f15: 0000000042eb426b $f16: 000000003f800000 $f17: 0000000000000000 $f18: 0000000000000000 $f19: 0000000000000000 $f20: 0000000043a00000 $f21: 0000000043700000 $f22: 000000003dcccccd $f23: 000000003f4ccccd $f24: 0000000030000000 $f25: 0000000000000000 $f26: 0000000000000000 $f27: 0000000000000000 $f28: 0000000000000000 $f29: 0000000000000000 $f30: 0000000000000000 $f31: 0000000000000000 Example: li t0, (1<<10) | (1<<20) | (1<<21) xlogregs t0, XDUMP\_FPU | XDUMP\_DOUBLE | XDUMP\_DECIMAL should produce an output such as: $f10: 0.06015772407604892 $f20: 0.17499998365086916 $f21: ### 0x27: `xhexdump` Perform a hexdump of the specified buffer, writing it to the log. Input: * `rd`: address of the buffer to dump. See `code` for how this address is interpreted * `rt`: length of the buffer in bytes. * `code[0]`: how the address is interpreted: * `0`: virtual address (VR4300) or IMEM/DMEM (RSP) * `1`: RCP physical address (can be run on both CPUs) Note that xhexdump should perform memory accesses in a side-effect free way. In particular: * Reads from the RSP semaphore should not set it to 1. This is the only hardware register in N64 that has side effects on read. * Reads through virtual addresses that go through the data cache should report either cache contents (if those addresses are cached) or RAM contents (if they are not), but the data cachelines should not be alterered in any way. ### 0x28: `xprof` This extension family asks the emulator to profile a section of code. A profile is initiated with a "start" command, and terminated with a "stop" command. Profiling collects several metrics, whose list is emulator dependent. The bare minimum is CPU cycles, but that is not very helpful per se as both CPUs are able to read that by themselves via COP0. Other more useful metrics are listed below. Profiling collects metrics in several "slots". Running "start"/"stop" only affects the slot specified via the input register. The application can start multiple slots in parallel, and metrics should be called in all the running slots. The maximum number of available slots is emulator depenent, but we suggest to support 256 slots. Notice that slots must be shared between VR4300 and RSP. That is, VR4300 might start a profile on slot 15 and RSP might then stop profiling on slot 15; they both refer to the same slot. Profile metrics always must cover both VR4300 and RSP, irrespective of which CPU started the profiling. Input: * `rd`: slot index * `code`: sub-function: * 1: start profiling in the specified slot * 2: stop profiling in the specified slot * 3: clear (zero) the specified slot * 4: reset all the slots (ignore `rd`) ### 0x29: `xprofread` Read the current value of a specified metric in a specified slot. Input: * `rd`: slot index (if negative, return global counter since boot) * `rt`: metric ID Output: * `rt`: current value of the metric This function can be used to read back the current value of a metric, that can be used to show realtime profiling data into the running application. It can request either a slot-specific metric, which accounts for the profiling data accumulated between profile start/stop calls in that slot, or the global metric (specifying a negative slot number), that shows total values since boot. Reading a slot that is started is valid and returns the expected value (time passed since start, accumulated with previous starts/stops). If a specified metric does not exist or is not supported, `rt` should be set to 0 in output. ### Metrics list This is a list of all potentially available metrics. An emulator is not required to implement them all, and it should return 0 when reading a non implemented metric via `xprofread`. * 0x00nn: VR4300 metrics * 0x0000: cycle count * 0x0001: cycle count within an exception (EXL=1 or ERL=1) * 0x0002: finished instruction count \[1\] * 0x0003: finished instruction count within an exception (EXL=1 or ERL=1) * 0x0010: icache hits * 0x0011: icache misses * 0x0012: icache writebacks * 0x0020: dcache hits * 0x0021: dcache misses * 0x0022: dcache writebacks * 0x01nn: VR4300 COP0 metrics * 0x0100: TLB lookup hits * 0x0101: TLB lookup misses * 0x0110: Instruction Micro-TLB lookup hits * 0x0111: Instruction Micro-TLB lookup misses * 0x02nn: RSP metrics * 0x0200: cycle count * 0x0201: cycle count while idle (halted) * 0x0210: total number of pipeline stalls * 0x0211: number of pipeline stalls because of vector write/read delay * 0x0212: number of pipeline stalls because of general write/read conflict * 0x03nn: RDRAM metrics * 0x0300: total number of bytes read or written * 0x0301: total number of bytes read * 0x0302: total number of bytes written * 0x0310: number of bytes read or written by VR4300 icache * 0x0311: number of bytes read by VR4300 icache * 0x0312: number of bytes written by VR4300 icache * 0x0320: number of bytes read or written by VR4300 dcache * 0x0321: number of bytes read by VR4300 dcache * 0x0322: number of bytes written by VR4300 dcache * 0x0330: number of bytes read or written by VR4300 uncached load/store * 0x0331: number of bytes read by VR4300 uncached load/store * 0x0332: number of bytes written by VR4300 uncached load/store * 0x0340: number of bytes read or written by RSP DMA * 0x0341: number of bytes read by RSP DMA * 0x0342: number of bytes written by RSP DMA * 0x0350: number of bytes read or written by PI DMA * 0x0351: number of bytes read by PI DMA * 0x0352: number of bytes written by PI DMA * 0x0360: number of bytes read or written by SI DMA * 0x0361: number of bytes read by SI DMA * 0x0362: number of bytes written by SI DMA * 0x0370: number of bytes read or written by RDP (while drawing) * 0x0371: number of bytes read by RDP (while drawing) * 0x0372: number of bytes written by RDP (while drawing) * 0x0380: number of bytes read or written by AI DMA * 0x0381: number of bytes read by AI DMA * 0x0382: number of bytes written by AI DMA (always 0) * 0x0390: number of bytes read or written by VI * 0x0391: number of bytes read by VI * 0x0392: number of bytes written by VI (always 0) * 0x03A0: number of bytes read or written by RDP DMA * 0x03A1: number of bytes read by RDP DMA * 0x03A2: number of bytes written by RDP DMA (always 0) \[1\] With "finished", we mean instructions that have fully completed their execution through the CPU pipeline. For instance, an instruction that causes an exception is not finished. ### 0x2A: `xexception` This extension allows to configure (enable or disable) a special emux-specific exception that the emulator can trigger in the emulated R4300 core. Nintendo 64 hardware can crash in various ways. Known crashes include: * VI crash -> the video output is permanently garbled until power cycle. * RDP crash -> the RDP will stop processing commands until power cycle. * CPU SysAD freeze -> the CPU will be stalled (frozen) until reset. These situations, when accurately emulated, result in the game freezing. The emulator can maybe warn the user/developer that something is going on, but in general the developer will need context to debug the issue. `xexception` allows the game to request the emulator to trigger a VR4300 exception when one of these events happen, instead of accurately freezing the game with no feedback. The exception can be intercepted by the game or SDK to provide a nicer developer experience (error screen, dump of registers, stack trace, etc.). In addition to these, there could also be unusual events which do not necessarily cause the hardware to freeze, but could still be notified to developers. For instance, cache coherency errors might benefit from triggering a (resumable) exception to provide nicer diagnostics (eg: a stack trace). In this case, the exception is meant to be resumable, meaning that the game should probably continue after handling the exception. The special emux exception is triggered using the COP0 CAUSE exception code 24, which is unused by the hardware. The exception vector is the same as for all the other standard exceptions (so `0x8000'00180` normally, or `0xBFC0'0380` if BEV=1). When the emulator triggers this exception, it must also insert a sub-cause code into the COP0 `CacheErr` register (register 27), which is an unused COP0 register. The sub-cause specifies the actual reason why the exception was triggered. Values 0-31 are reserved for exceptions that indicate freezes, while values 32-63 are reserved for resumable exceptions. Writing any value to COP0 `CacheErr` makes it reset to 0. This allows an application to handle resumable exceptions in a way not to leave traces. Sub-cause values: * 0: Unused / invalid. This is the fixed value of COP0 PARITY\_ERROR on hardware, so it is left unused in this spec. * 1: CPU cached access to non-RDRAM area (SysAD freeze) * 2: 64-bit read from non-RDRAM area (SysAD freeze) * 3: CPU access to memory area unmapped by RCP (SysAD freeze) Input: * `rt`: bitmask of emux exceptions to enable. This specifies which exceptions the application would like to receive. For instance, bit 3 (0x8) activates the exception for "CPU access to memory area unmapped by RCP": Check the list of sub-cause values above. Activating all bits in the 64-bit register activates all possible present and future exceptions ### 0x2C: `xioctl` This extension allows the running application to affect the emulator itself. Input: * `code`: operation to perform: * 0x1: `exit`: The emulator must exit. * 0x2: `fast`: The emulator must switch to frame-unlimited mode (running as fast as possible) * 0x3: `slow`: The emulator must switch back to frame-limited mode. * 0x4: `pause`: The emulator should pause emulation. The user will have to unpause it themselves if they want to continue running the ROM. Not all `xioctl` operations must be supported by a conforming implementation. Emulators must provide the mask of supported operations in `xdetect`. #### `xioctl` operation: `exit` Through this control command, some testsuites could be designed to not draw anything on screen, and instead display results via the `xlog` extension, and then fully shutdown the emulator, so that they can be run in a fully non interactive mode (eg: even on a headless CI). NOTE: an emulator that implementing this command is expected to fully exit itself. Just stopping emulation or even closing the ROM while keeping the emulator window open and active is not considered a conforming implementation. #### `xioctl` operation: `fast`/`slow` Request the emulator to run at maximum unbounded speed, ignoring vertical sync or other real time concerns. This can be useful while debugging code, as in that case the developer might want to do an edit-compile-run cycle and get as soon as possible to a breakpoint or to some register dump. Unbound speed should only affect how fast the program is executing on the host machine. To the emulated system, the higher speed should not be perceivable. Retrieved from "[https://n64brew.dev/wiki/Emux?oldid=5809](https://n64brew.dev/wiki/Emux?oldid=5809) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # CEN64 - N64brew Wiki CEN64 ===== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/CEN64#mw-head) [Jump to search](https://n64brew.dev/wiki/CEN64#searchInput) **CEN64** is an open-source, multi-platform emulator for the Nintendo 64. CEN64 once spearheaded accurate emulation of the N64, but has since fell behind due to development stalling and is mostly considered as discontinued today. CEN64 has debugging tools, such as GDB support. Website: [https://github.com/n64dev/cen64](https://github.com/n64dev/cen64) Retrieved from "[https://n64brew.dev/wiki/CEN64?oldid=5832](https://n64brew.dev/wiki/CEN64?oldid=5832) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Emulators](https://n64brew.dev/wiki/Category:Emulators "Category:Emulators") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # MAME - N64brew Wiki MAME ==== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/MAME#mw-head) [Jump to search](https://n64brew.dev/wiki/MAME#searchInput) **MAME** is an open-source, multi-platform emulator for several systems, including the Nintendo 64. Website: [https://www.mamedev.org/](https://www.mamedev.org/) Retrieved from "[https://n64brew.dev/wiki/MAME?oldid=5833](https://n64brew.dev/wiki/MAME?oldid=5833) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Emulators](https://n64brew.dev/wiki/Category:Emulators "Category:Emulators") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Gopher64 - N64brew Wiki Gopher64 ======== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/Gopher64#mw-head) [Jump to search](https://n64brew.dev/wiki/Gopher64#searchInput) **Gopher64** is an open-source, multi-platform emulator for the Nintendo 64. It is currently accurate enough for running libdragon ROMs, but homebrew development features (like [emux](https://n64brew.dev/wiki/Emux "Emux") , GDB) is considered out of scope for it. Website: [https://loganmc10.itch.io/gopher64](https://loganmc10.itch.io/gopher64) Retrieved from "[https://n64brew.dev/wiki/Gopher64?oldid=5836](https://n64brew.dev/wiki/Gopher64?oldid=5836) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Emulators](https://n64brew.dev/wiki/Category:Emulators "Category:Emulators") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # EverDrive-64 v3 - N64brew Wiki EverDrive-64 v3 =============== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/EverDrive-64_v3#mw-head) [Jump to search](https://n64brew.dev/wiki/EverDrive-64_v3#searchInput) The EverDrive-64 v3 is a flash cart made by [Krikzz](https://krikzz.com/store/) . Contents -------- * [1 Basic information](https://n64brew.dev/wiki/EverDrive-64_v3#Basic_information) * [2 Registers](https://n64brew.dev/wiki/EverDrive-64_v3#Registers) * [2.1 0x08040000 (REG\_CFG)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040000_(REG_CFG)) * [2.2 0x08040004 (REG\_STATUS)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040004_(REG_STATUS)) * [2.3 0x08040008 (REG\_DMA\_LEN)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040008_(REG_DMA_LEN)) * [2.4 0x0804000C (REG\_DMA\_RAM\_ADDR)](https://n64brew.dev/wiki/EverDrive-64_v3#0x0804000C_(REG_DMA_RAM_ADDR)) * [2.5 0x08040010 (REG\_MSG)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040010_(REG_MSG)) * [2.6 0x08040014 (REG\_DMA\_CFG)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040014_(REG_DMA_CFG)) * [2.7 0x08040018 (REG\_SPI)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040018_(REG_SPI)) * [2.8 0x0804001C (REG\_SPI\_CFG)](https://n64brew.dev/wiki/EverDrive-64_v3#0x0804001C_(REG_SPI_CFG)) * [2.9 0x08040020 (REG\_KEY)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040020_(REG_KEY)) * [2.10 0x08040024 (REG\_SAV\_CFG)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040024_(REG_SAV_CFG)) * [2.11 0x08040028 (REG\_SEC)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040028_(REG_SEC)) * [2.12 0x0804002C (REG\_VER)](https://n64brew.dev/wiki/EverDrive-64_v3#0x0804002C_(REG_VER)) * [2.13 0x08040030 (I2C/RTC)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040030_(I2C/RTC)) * [2.14 0x08040040 (REG\_CFG\_CNT)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040040_(REG_CFG_CNT)) * [2.15 0x08040044 (REG\_CFG\_DAT)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040044_(REG_CFG_DAT)) * [2.16 0x08040048 (REG\_MAX\_MSG)](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040048_(REG_MAX_MSG)) * [2.17 0x0804004C (REG\_CRC)](https://n64brew.dev/wiki/EverDrive-64_v3#0x0804004C_(REG_CRC)) * [2.18 0x08040050](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040050) * [2.19 0x08040054](https://n64brew.dev/wiki/EverDrive-64_v3#0x08040054) * [3 ED64 Boot ROM Header](https://n64brew.dev/wiki/EverDrive-64_v3#ED64_Boot_ROM_Header) * [4 USB Serial Communication](https://n64brew.dev/wiki/EverDrive-64_v3#USB_Serial_Communication) * [4.1 Common Code](https://n64brew.dev/wiki/EverDrive-64_v3#Common_Code) * [4.2 Sending Data to the Host](https://n64brew.dev/wiki/EverDrive-64_v3#Sending_Data_to_the_Host) * [4.3 Reading Data from the Host](https://n64brew.dev/wiki/EverDrive-64_v3#Reading_Data_from_the_Host) Basic information ----------------- ROM: Max 64MiB RTC: supported Save: 4ki/16ki bit EEPROM, 32KiB/128KiB SRAM, 128KiB Flash USB Serial: 512-bytes block read-write SD: SD or SDHC Registers --------- All registers must be accessed by 32bit word, but it seems only lower 16bits are valid. High 16bits of all registers seems REG\_STATUS on read. Base PI address for registers is `0x08040000` (Cartridge Domain 2, SRAM area +0x40000) You should access these registers from CPU through no-cached segment, i.e. `+0xA0000000`, ex. `0xA8040020` for REG\_KEY. | PI address | name | R/W | description | | --- | --- | --- | --- | | 0x08040000 | REG\_CFG | R/W | ED64 general configurations | | 0x08040004 | REG\_STATUS | R | ED64 registers, SPI, DMA statuses | | 0x08040008 | REG\_DMA\_LEN | W | SD/USB DMA length in 512bytes blocks - 1 (ex. 0 = 0x200 bytes) | | 0x0804000C | REG\_DMA\_RAM\_ADDR | W | SD/USB Cart address in 2048bytes blocks (ex. 1 = 0x00000800) | | 0x08040010 | REG\_MSG | R/W | 16bit general storage? used to remember last used save type by ED64 OS (menu) | | 0x08040014 | REG\_DMA\_CFG | W | Invoke SD/USB DMA | | 0x08040018 | REG\_SPI | R/W | SPI (SD card) DAT/CMD (write invoke CLK) | | 0x0804001C | REG\_SPI\_CFG | R/W | SPI (SD card) configurations | | 0x08040020 | REG\_KEY | W | Enable or disable ED64 registers | | 0x08040024 | REG\_SAV\_CFG | R/W | Save configurations (EEPROM/SRAM/FLASH) | | 0x08040028 | REG\_SEC | ? | ? | | 0x0804002C | REG\_VER | R | Firmware version | | 0x08040030 | ? | R/W | I2C to access RTC | | 0x08040034 | ? | ? | ? | | 0x08040038 | ? | ? | ? | | 0x0804003C | ? | ? | ? | | 0x08040040 | REG\_CFG\_CNT | R/W | FPGA configuration control | | 0x08040044 | REG\_CFG\_DAT | W | FPGA configuration data | | 0x08040048 | MAX\_MSG | R/W | Some configurations | | 0x0804004C | REG\_CRC | ? | ? | | 0x08040050 | ? | ? | Flash? | | 0x08040054 | ? | ? | Flash? | ### 0x08040000 (REG\_CFG) Read/Write | bit from lsb | description | | --- | --- | | 15 | 1=finish FPGA configuration? (after REG\_CFG\_\* writes) | | 9-8 | D64 specific?? | | 6-5 | RTC (00=disable, 01=RTC emulation enabled through Cart EEPROM command 06/07, 11=RTC access enabled via I2C) | | 3 | WR\_ADDR\_MASK?? | | 2 | WR\_MOD?? | | 1 | 1=16bit swap on SD DMA read | | 0 | 1=enable SDRAM on cart (usually set to 1) | ### 0x08040004 (REG\_STATUS) Read only | bit from lsb | description | | --- | --- | | 15 | 1=SDRAM enabled? | | 4 | 1=SPI is busy (REG\_SPI <-> SD is transferring) | | 3 | RXF# on USB FIFO (0=some data from Host exists) | | 2 | TXE# on USB FIFO (0=able to send some data to Host) | | 1 | DMATOUT (1=last DMA was timed out, i.e. last transfer is less than 512bytes) | | 0 | DMABUSY (1=DMA is ongoing) | ### 0x08040008 (REG\_DMA\_LEN) Write only | bit from lsb | description | | --- | --- | | 15-0 | Next SD/USB DMA transfer size in 512 bytes step, minus 1.

ex. 0 = 0x200 bytes, 1 = 0x400 bytes, 2 = 0x600 bytes... | ### 0x0804000C (REG\_DMA\_RAM\_ADDR) Write only | bit from lsb | description | | --- | --- | | 15-0 | Next SD/DMA DMA transfer size in 2048 bytes step.

ex. 0 = 0x00000000, 1 = 0x00000800, 2 = 0x00001000...

This is in ED64 ROM address space, ex. 0xB0000000 on CPU = 0x10000000 on PI = 0x00000000 on ED64 ROM. | ### 0x08040010 (REG\_MSG) Read/Write It seems generic 16bit storage. In ED64 OS v2.12, this register used as following: | bit from lsb | description | | --- | --- | | 4 | last ROM is 64DD image | | 3-0 | last ROM's savetype (0=none, 1=EEPROM 4kibit, ... same as /ED64/save\_db.txt) | ### 0x08040014 (REG\_DMA\_CFG) Write only | value | description | | --- | --- | | 1 | SD -> Cart | | 2 | Cart -> SD | | 3 | USB -> Cart | | 4 | Cart -> USB | Note that above table numbers are value, not "bit from lsb". You must set REG\_DMA\_LEN and REG\_DMA\_RAM\_ADDR before writing this register. ### 0x08040018 (REG\_SPI) Read/Write | bit from lsb | description | | --- | --- | | 7-0 | dat/cmd value. Bits are shifted in from lsb if 1bit mode. | Writing value to this invokes clock CLK on SD card. You should also write this to read. (write value is ignored) ### 0x0804001C (REG\_SPI\_CFG) Read/Write | bit from lsb | description | | --- | --- | | 5 | Bits transferred on writing REG\_SPI: 0=8bits 1=1bit | | 4 | Which line to R/W: 0=CMD 1=DAT | | 3 | Read or write (on writing REG\_SPI): 0=write 1=read | | 2 | Slave Select line on SD card | | 1-0 | CLK speed: 10=init 01=25MHz 00=50MHz | ### 0x08040020 (REG\_KEY) Write only | bit from lsb | description | | --- | --- | | 15-0 | Enable or disable ED64 registers: 0=disable, 0x1234=enable | Note that you should dummy-read REG\_CFG before writing to REG\_KEY, or it may be ignored (glitch). ### 0x08040024 (REG\_SAV\_CFG) Read/Write | bit from lsb | description | | --- | --- | | 15 | 1=enable ED64 save page | | 7 | 1=enable game save page (have priority than ED64 page) | | 3 | SRAM size: 0=32KiB 1=128KiB | | 2 | EEPROM size: 0=4kibit 1=16kibit | | 1 | SRAM ON: 1=SRAM | | 0 | EEPROM ON: 1=EEPROM | But this register should be treated as a value not bitfields (like REG\_DMA\_CFG) | value | description | | --- | --- | | 0x0080 | Save is disabled | | 0x0082 | 32KiB SRAM enabled | | 0x008A | 128KiB SRAM enabled | | 0x0081 | 4kibit EEPROM enabled | | 0x0085 | 16kibit EEPROM enabled | | 0x0088 | 128KiB Flash enabled (!?) | | 0x800A | 128KiB ED64 save page enabled (mainly from 0x1E000) | ### 0x08040028 (REG\_SEC) Write only? ### 0x0804002C (REG\_VER) Read only | bit from lsb | description | | --- | --- | | 15-8 | Firmware major version | | 7-0 | Firmware minor version | ex. 0x0304 for Firmware v3.04 ### 0x08040030 (I2C/RTC) Read/Write | bit from lsb | description | | --- | --- | | 2 | CLK | | 0 | DAT | You must set DAT=1 before read, or you'll get always DAT=0 (because of I2C's open-drain). DS1337 (RTC) at address 0x68. ### 0x08040040 (REG\_CFG\_CNT) Read/Write on REG\_CFG&1==0 | bit from lsb | description | | --- | --- | | 3 | 1=REG\_CFG\_DAT is transferring \[R\] | | 2 | 1=FPGA is configuring? \[R\] | | 0 | 0=unconfigure FPGA? \[W\] | SDRAM must be disabled (REG\_CFG & 1 must be zero). ### 0x08040044 (REG\_CFG\_DAT) Write only? on REG\_CFG&1==0 | bit from lsb | description | | --- | --- | | 15-0 | configuration data (part of bitstream) | SDRAM must be disabled (REG\_CFG & 1 must be zero). ### 0x08040048 (REG\_MAX\_MSG) Read/Write on REG\_CFG&1==0 | bit from lsb | description | | --- | --- | | 14 | 1? | | 13 | 1=SDHC | | 12 | 1=SD card initialized | | 11-10 | tvtype (value that IPL3 sets) | | 9 | 1=tvtype in REG\_MAX\_MSG is set | | 8 | 1=FPGA configured | SDRAM must be disabled (REG\_CFG & 1 must be zero). ### 0x0804004C (REG\_CRC) Read only? on REG\_CFG&1==0 | bit from lsb | description | | --- | --- | | 15-12 | HW major version? | | 11-0 | ? | SDRAM must be disabled (REG\_CFG & 1 must be zero). This register seems not related to "Cyclic Redundancy Check". ### 0x08040050 Write only? on REG\_CFG&1==0 TBA SDRAM must be disabled (REG\_CFG & 1 must be zero). ### 0x08040054 Write only on REG\_CFG&1==0 ? TBA SDRAM must be disabled (REG\_CFG & 1 must be zero). ED64 Boot ROM Header -------------------- You can read boot ROM header by: * set `REG_CFG &= ~1` (disable SDRAM) * read from cart usually (by `*(uint32_t*)0xB0000000` etc, or use PI DMA) | PI address | bytes | description | | --- | --- | --- | | 0x10000020 | 12 | "ED64 SD boot", can be used to identify SD is usable or not (=SPI) | | 0x10000038 | 2 | assembly date in FAT style | | 0x1000003A | 2 | assembly time in 2-seconds from 00:00:00, ex. 0x5878 for 12:34:56 | | 0x1000003C | 2 | serial number (16bit binary, hex) | They are, of course, big endian. | | | | --- | --- |Date in FAT Style | bit from lsb | description | | 15-9 | year - 1980 | | 8-5 | month (start from 1) | | 4-0 | day (start from 1) | USB Serial Communication ------------------------ ### Common Code Before communication, you must enable ED64 registers. #include \*(volatile uint32\_t \*)0xA8040000; /\* dummy read \*/ \*(volatile uint32\_t \*)0xA8040020 \= 0x1234; /\* enable ED64 registers \*/ ### Sending Data to the Host You should transfer data from RDRAM to some area on cart "ROM" area first, then use Cart->USB DMA on ED64. /\* polling style \*/ void rdram\_to\_host( void \*src, /\* pointer for data aligned by 64 bits (8 bytes) \*/ uint32\_t tmp\_cart\_addr, /\* somewhere aligned by 2048 bytes \*/ uint32\_t len /\* length aligned 512 bytes \*/ ) { uintptr\_t i; /\* flush D-cache (primary data hit writeback invalidate; 16 bytes per D-cache-line on N64) \*/ for(i \= (uintptr\_t)src & ~15; i < (uintptr\_t)src + (uintptr\_t)len; i += 16) { \_\_asm volatile("cache 0x15, %0" :: "m"(\*(uint32\_t\*)i)); } \*(volatile uint32\_t \*)0xA4600010 \= 3; /\* reset PI and clear interrupt \*/ /\* transfer (PI DMA) RDRAM to Cart \*/ \*(volatile uint32\_t \*)0xA4600000 \= (uint32\_t)src & 0x00FFffff; /\* XXX: assuming src points to kseg0 or kseg1 \*/ \*(volatile uint32\_t \*)0xA4600004 \= tmp\_cart\_addr; \*(volatile uint32\_t \*)0xA4600008 \= len \- 1; while(\*(volatile uint32\_t \*)0xA4600010 & 1) /\* busyloop \*/ ; /\* wait for PI DMA done \*/ /\* Cart->USB DMA on ED64 \*/ while(\*(volatile uint32\_t \*)0xA8040004 & 4) /\* busyloop \*/ ; /\* wait until REG\_STATUS & TXE# becomes zero = able to tx \*/ \*(volatile uint32\_t \*)0xA8040008 \= len / 512 \- 1; /\* set REG\_DMA\_LEN \*/ \*(volatile uint32\_t \*)0xA8040000; /\* dummy read for previous PI write is done (REG\_CFG is just because) \*/ \*(volatile uint32\_t \*)0xA804000C \= tmp\_cart\_addr / 2048; /\* set REG\_DMA\_ADDR \*/ \*(volatile uint32\_t \*)0xA8040000; /\* dummy read for previous PI write is done (REG\_CFG is just because) \*/ \*(volatile uint32\_t \*)0xA8040014 \= 4; /\* set REG\_DMA\_CFG to Cart->USB invokes DMA \*/ while(\*(volatile uint32\_t \*)0xA8040004 & 1) /\* busyloop \*/ ; /\* wait for Cart->USB DMA done \*/ } ... uint8\_t \_\_attribute((aligned(8))) buffer\[512\]; /\* ... some modification to "buffer" \*/ rdram\_to\_host(buffer, 0x00400000 \- 2048, 512); /\* using last ROM area for communication \*/ ### Reading Data from the Host You should use USB->Cart DMA on ED64 first, then transfer data from cart ROM area to RDRAM. At least on HW v3.04, it seems that USB->Cart transfer is done at 16-bit halfwords. If received data is NOT aligned at 16-bit, that last byte will be lost. Of course if received data is shorter than "len", DMA will be timed out, cause waiting ~500ms to DMA done until timeout, so you should send data from host by 512 bytes block for faster communication. /\* polling style \*/ int host\_to\_rdram( void \*dst, /\* pointer for data aligned by 64 bits (8 bytes) \*/ uint32\_t tmp\_cart\_addr, /\* somewhere aligned by 2048 bytes \*/ uint32\_t len /\* length aligned 512 bytes \*/ ) { uintptr\_t i; /\* REG\_STATUS & RXF# is zero = data is arriving, not zero = not arrived \*/ if(\*(volatile uint32\_t \*)0xA8040004 & 8) { /\* no data is arrived now \*/ return 0; } /\* invalidate D-cache (primary data hit invalidate; 16 bytes per D-cache-line on N64) \*/ for(i \= (uintptr\_t)dst & ~15; i < (uintptr\_t)dst + (uintptr\_t)len; i += 16) { \_\_asm volatile("cache 0x11, %0" :: "m"(\*(uint32\_t\*)i)); } \*(volatile uint32\_t \*)0xA4600010 \= 3; /\* reset PI and clear interrupt \*/ /\* USB->Cart DMA on ED64 \*/ \*(volatile uint32\_t \*)0xA8040008 \= len / 512 \- 1; /\* set REG\_DMA\_LEN \*/ \*(volatile uint32\_t \*)0xA8040000; /\* dummy read for previous PI write is done (REG\_CFG is just because) \*/ \*(volatile uint32\_t \*)0xA804000C \= tmp\_cart\_addr / 2048; /\* set REG\_DMA\_ADDR \*/ \*(volatile uint32\_t \*)0xA8040000; /\* dummy read for previous PI write is done (REG\_CFG is just because) \*/ \*(volatile uint32\_t \*)0xA8040014 \= 3; /\* set REG\_DMA\_CFG to USB->Cart invokes DMA \*/ while(\*(volatile uint32\_t \*)0xA8040004 & 1) /\* busyloop \*/ ; /\* wait for USB->Cart DMA done \*/ if(\*(volatile uint32\_t \*)0xA8040004 & 2) { /\* last DMA timed out... you may be treat this as error (or you can read partially transferred data, comment out next return) \*/ return \-1; } /\* transfer (PI DMA) Cart to RDRAM \*/ \*(volatile uint32\_t \*)0xA4600000 \= (uint32\_t)dst & 0x00FFffff; /\* XXX: assuming dst points to kseg0 or kseg1 \*/ \*(volatile uint32\_t \*)0xA4600004 \= tmp\_cart\_addr; \*(volatile uint32\_t \*)0xA460000C \= len \- 1; while(\*(volatile uint32\_t \*)0xA4600010 & 1) /\* busyloop \*/ ; /\* wait for PI DMA done \*/ /\* success \*/ return 1; } ... uint8\_t \_\_attribute((aligned(8))) buffer\[512\]; int result; result \= host\_to\_rdram(buffer, 0x00400000 \- 2048, 512); /\* using last ROM area for communication \*/ /\* "buffer" is modified if 0 < result \*/ Retrieved from "[https://n64brew.dev/wiki/EverDrive-64\_v3?oldid=5569](https://n64brew.dev/wiki/EverDrive-64_v3?oldid=5569) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Flash Carts](https://n64brew.dev/wiki/Category:Flash_Carts "Category:Flash Carts") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Project64 - N64brew Wiki Project64 ========= From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/Project64#mw-head) [Jump to search](https://n64brew.dev/wiki/Project64#searchInput) **Project64** is an open-source emulator, for Windows, for the Nintendo 64. It features very good [debugging tools](https://hack64.net/docs/pj64d/) and a [JavaScript API](https://htmlpreview.github.io/?https://github.com/project64/project64/blob/develop/JS-API-Documentation.html) , but is otherwise not accurate enough for homebrew development. Website: [https://www.pj64-emu.com/](https://www.pj64-emu.com/) Retrieved from "[https://n64brew.dev/wiki/Project64?oldid=5834](https://n64brew.dev/wiki/Project64?oldid=5834) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Emulators](https://n64brew.dev/wiki/Category:Emulators "Category:Emulators") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # EverDrive-64 X7 - N64brew Wiki EverDrive-64 X7 =============== From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/EverDrive-64_X7#mw-head) [Jump to search](https://n64brew.dev/wiki/EverDrive-64_X7#searchInput) The EverDrive-64 X5/X7 is a [flash cart](https://n64brew.dev/wiki/Category:Flash_Carts "Category:Flash Carts") made by [Krikzz](https://krikzz.com/store/) . This page documents the low-level hardware interface. X5 is a stripped down version of X7, with no USB and no RTC (see [Everdrive 64](https://n64brew.dev/wiki/Everdrive_64 "Everdrive 64") for a better comparison). In general, hardware interface is similar or identical between the two. Contents -------- * [1 Registers](https://n64brew.dev/wiki/EverDrive-64_X7#Registers) * [1.1 Register access](https://n64brew.dev/wiki/EverDrive-64_X7#Register_access) * [2 Serial Communication](https://n64brew.dev/wiki/EverDrive-64_X7#Serial_Communication) * [2.1 Serial Registers](https://n64brew.dev/wiki/EverDrive-64_X7#Serial_Registers) * [2.2 Initializing EverDrive](https://n64brew.dev/wiki/EverDrive-64_X7#Initializing_EverDrive) * [2.3 Sending Data to the Host](https://n64brew.dev/wiki/EverDrive-64_X7#Sending_Data_to_the_Host) * [2.4 Reading Data from the Host](https://n64brew.dev/wiki/EverDrive-64_X7#Reading_Data_from_the_Host) * [3 I2C peripheral](https://n64brew.dev/wiki/EverDrive-64_X7#I2C_peripheral) * [4 RTC](https://n64brew.dev/wiki/EverDrive-64_X7#RTC) Registers --------- | Name | PI Address | Description | | --- | --- | --- | | REG\_USB\_CFG | 0x1F80'0004 | | | REG\_BOOT\_CFG | 0x1F80'0010 | | | REG\_EDID | 0x1F80'0014 | Contains | | REG\_I2C\_CMD | 0x1F80'0018 | I2C bus command (to access RTC and EEPROM) | | REG\_I2C\_DAT | 0x1F80'001C | I2C bus data (to access RTC and EEPROM) | | REG\_USB\_DATA | 0x1F80'0400 | | | REG\_SYS\_CFG | 0x1F80'8000 | | | REG\_KEY | 0x1F80'8004 | Unlock access to hardware registers (must be written first) | | REG\_DMA\_ADDR | 0x1F80'8008 | DMA to access the SD. When read, it's a status register. When written, it is the offset in SDRAM for DMA | | REG\_DMA\_LEN | 0x1F80'800C | DMA length for transfer to/from SD | | REG\_RTC\_CACHE | 0x1F80'8010 | Buffer of 8 bytes holding a copy of the current time (as read from the RTC) | | REG\_SD\_CMD\_RD | 0x1F80'8020 | Read a command reply from SD | | REG\_SD\_CMD\_WR | 0x1F80'8024 | Write a command to SD | | REG\_SD\_DAT\_RD | 0x1F80'8028 | Read command data from SD | | REG\_SD\_DAT\_WR | 0x1F80'802C | Write command data to SD | | REG\_SD\_BUFFER | 0x1F80'8200 | 512-bytes buffer for SD DMA | ### Register access The following code is used in the Everdrive reference code for both reading and writing hardware registers. Reference code seems to always use PI DMA to read and write hardware registers, even though most of them are 32-bit in size and could simply be accessed via direct CPU read/write in the memory mapped area (using the PI address + 0xA000'0000 to access it in the uncached segment). Notice that a PI peripheral (the flashcart in this case) is not aware whether an access is made via DMA or direct I/O, as the access is identical on the bus, so there is no way for the peripheral to behave differently and actually "require" DMA. void sysPI\_rd(void \*ram, unsigned long pi\_address, unsigned long len) { pi\_address &= 0x1FFFFFFF; // invalidates the cache so the new values in ram after reading // wont mismatch what is in the cache // this is part of libdragon, for the libultra equivalent use osInvalDCache data\_cache\_hit\_writeback\_invalidate(ram, len); // the rest of the code is for a DMA transfer from the cartridge into RAM. // use osEPiStartDma for libultra disable\_interrupts(); while (dma\_busy()); IO\_WRITE(PI\_STATUS\_REG, 3); PI\_regs\->ram\_address \= ram; PI\_regs\->pi\_address \= pi\_address; //(pi\_address | 0x10000000) & 0x1FFFFFFF; PI\_regs\->write\_length \= len \- 1; while (dma\_busy()); enable\_interrupts(); } void sysPI\_wr(void \*ram, unsigned long pi\_address, unsigned long len) { pi\_address &= 0x1FFFFFFF; // Writes the cache back to ram so the DMA gets the correct value // this is part of libdragon, for the libultra equivalent use osWritebackDCache data\_cache\_hit\_writeback(ram, len); // the rest of the code is for a DMA transfer from the RAM into the cartridge. // use osEPiStartDma for libultra disable\_interrupts(); while (dma\_busy()); IO\_WRITE(PI\_STATUS\_REG, 3); PI\_regs\->ram\_address \= ram; PI\_regs\->pi\_address \= pi\_address; //(pi\_address | 0x10000000) & 0x1FFFFFFF; PI\_regs\->read\_length \= len \- 1; while (dma\_busy()); enable\_interrupts(); } // Writes a given value to the given register on the EverDrive void bi\_reg\_wr(u16 reg, u32 val) { sysPI\_wr(&val, REG\_ADDR(reg), 4); } // Reads the given register value u32 bi\_reg\_rd(u16 reg) { u32 val; sysPI\_rd(&val, REG\_ADDR(reg), 4); return val; } Serial Communication -------------------- The EverDrive-64 X7 supports serial over USB communication to a host PC. This can be used to load ROMs and allows running ROMs to send an receive data from the host PC. Krikzz provides a reference implementation [here](http://krikzz.com/pub/support/everdrive-64/x-series/dev/) . ### Serial Registers The EverDrive-64 X7 provides various registers starting at the address `0x1F800000` in the cartridge address space. The following registers are used for USB communcation. | | | | | | --- | --- | --- | --- |List of Registers | Name | Description | Address relative to 0x1F800000 | Size (bytes) | | REG\_USB\_CFG | Reads USB status and sends read and write commands | 0x0004 | 4 | | REG\_USB\_DATA | The temporary data buffer used to read and write to USB | 0x0400 | 512 | | REG\_KEY | Set to 0xAA55 on initialization | 0x8004 | 4 | | REG\_SYS\_CFG | Set to 0 on initialization | 0x8000 | 4 | The `REG_USB_CFG` register contains various bits as described below | | | | | | --- | --- | --- | --- |REG\_USB\_CFG bits | Name | Description | Bits (15-0) | Hex Mask | | USB\_LE\_CFG | Set to a 1 when reading and writing data | 15 | 0x8000 | | USB\_LE\_CTR | Set to a 1 when reading and writing data | 14 | 0x4000 | | USB\_STA\_BSY | Behavior not known | 13 | 0x2000 | | USB\_STA\_PWR | Set to when 1 when data can be read or written | 12 | 0x1000 | | USB\_STA\_TXE | Set to 0 when data can be written | 11 | 0x0800 | | USB\_CFG\_RD/USB\_STA\_RXF | Writing a 1 switches the controller to read mode. Writing a 0 switches the controller to write mode.

Reads as a 0 when data is available to be read. | 10 | 0x0400 | | USB\_CFG\_ACT/USB\_STA\_ACT | Writing a 1 starts reading or writing to REG\_USB\_DATA. The direction of data is specified by the value of USB\_CFG\_RD. The amount of data written is specified by baddr.

When reading this bit, a 1 indicates the USB port is busy reading or writing data. | 9 | 0x0200 | | baddr | These 5 bits give the address into REG\_USB\_DAT where USB data is read from and written to. Data is read starting at `baddr` offset until the end of the REG\_USB\_DAT buffer. This means the amount of data read is `512 - baddr`. Because of this, when copying data to and from REG\_USB\_DAT, you don't always start copying at the beginning of the buffer. | 8..0 | 0x01FF | ### Initializing EverDrive Below is the code used to initialize the EverDrive to be used for USB IO from krikzz's reference code with comments /\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* \* Peripheral Interface (PI) Registers \*/ #define PI\_BASE\_REG 0x04600000 /\* PI dom1 latency (R/W): \[7:0\] domain 1 device latency \*/ #define PI\_BSD\_DOM1\_LAT\_REG (PI\_BASE\_REG+0x14) /\* PI dom1 pulse width (R/W): \[7:0\] domain 1 device R/W strobe pulse width \*/ #define PI\_BSD\_DOM1\_PWD\_REG (PI\_BASE\_REG+0x18) #define IO\_WRITE(addr,data) (\*(vu32 \*)PHYS\_TO\_K1(addr)=(u32)(data)) void bi\_init() { // sets the PI manager latency IO\_WRITE(PI\_BSD\_DOM1\_LAT\_REG, 0x04); // sets the PI manager pulse IO\_WRITE(PI\_BSD\_DOM1\_PWD\_REG, 0x0C); // the specific value of 0xAA55 needs to be written here to enable USB IO bi\_reg\_wr(REG\_KEY, 0xAA55); bi\_reg\_wr(REG\_SYS\_CFG, 0); // this flushes serial buffer of any pending data bi\_usb\_init(); // sets the save type. Not needed for USB IO bi\_set\_save\_type(SAVE\_OFF); } The example code sets latency and pulse for the values expected for USB communication. Different devices such as flash, sram, or cartridge would expect different values. You should change these to the appropriate values for the device before doing a DMA to that device. If you are using libultra there is a function designed to configure the PI manager for you before starting a DMA using `osEPiStartDma`. The first parameter of this function accepts a pointer to a `OSPiHandle` which contains the latency and pulse values that should be used by the PI manager. ### Sending Data to the Host Below is the example code for sending data to the host PC in the function `bi_usb_wr`. Keep in mind that `src` should be aligned on 8 byte boundaries since it is used in a DMA transfer. For the same reason the length should be a multiple of 2. #define USB\_LE\_CFG 0x8000 #define USB\_LE\_CTR 0x4000 #define USB\_CFG\_ACT 0x0200 #define USB\_CFG\_RD 0x0400 #define USB\_CFG\_WR 0x0000 #define USB\_STA\_ACT 0x0200 #define USB\_STA\_RXF 0x0400 #define USB\_STA\_TXE 0x0800 #define USB\_STA\_PWR 0x1000 #define USB\_STA\_BSY 0x2000 #define USB\_CMD\_RD\_NOP (USB\_LE\_CFG | USB\_LE\_CTR | USB\_CFG\_RD) #define USB\_CMD\_RD (USB\_LE\_CFG | USB\_LE\_CTR | USB\_CFG\_RD | USB\_CFG\_ACT) #define USB\_CMD\_WR\_NOP (USB\_LE\_CFG | USB\_LE\_CTR | USB\_CFG\_WR) #define USB\_CMD\_WR (USB\_LE\_CFG | USB\_LE\_CTR | USB\_CFG\_WR | USB\_CFG\_ACT) #define REG\_BASE 0x1F800000 #define KSEG1 0xA0000000 #define REG\_ADDR(reg) (KSEG1 | REG\_BASE | (reg)) u8 bi\_usb\_busy() { u32 tout \= 0; // wait for the USB\_STA\_ACT bit to clear in REG\_USB\_CFG // indicating the USB port is no longer busy while ((bi\_reg\_rd(REG\_USB\_CFG) & USB\_STA\_ACT) != 0) { // check to see if the operation has timed out if (tout++ != 8192)continue; // Turn off USB serial communication bi\_reg\_wr(REG\_USB\_CFG, USB\_CMD\_RD\_NOP); return BI\_ERR\_USB\_TOUT; } return 0; } u8 bi\_usb\_wr(void \*src, u32 len) { u8 resp \= 0; u16 blen, baddr; // switch USB to write mode but don't start the transfer yet bi\_reg\_wr(REG\_USB\_CFG, USB\_CMD\_WR\_NOP); while (len) { // data can only be sent in chunks of 512 bytes blen \= 512; if (blen \> len)blen \= len; // calculate the offset into REG\_USB\_DAT from the length of data baddr \= 512 \- blen; // DMA the data from ram into the temporary buffer on the cartridge // Note that sending less than 512 bytes will result it baddr being // a positive value making the start of the DMA copy be somewhere // in the middle of REG\_USB\_DAT instead of always starting at the // beginning sysPI\_wr(src, REG\_ADDR(REG\_USB\_DAT + baddr), blen); src += 512; // Start sending data from REG\_USB\_DAT over the usb starting at // baddr and ending at the end of the 512 byte buffer. bi\_reg\_wr(REG\_USB\_CFG, USB\_CMD\_WR | baddr); // Wait for the data to finish sending over USB resp \= bi\_usb\_busy(); if (resp)break; len \-= blen; } return resp; } ### Reading Data from the Host Below is an example of reading data from the host PC. Since `dst` is used in a DMA transfer it must be aligned to 8 bytes. For the same reason, len should be multiple of 2. The EverDrive-64 X7 also has the limitation that at least 16 bytes must be read. For this reason you should pad data sent to the EverDrive to a multiple of 16 bytes. You also must not read more data than what as been send from the host PC otherwise the read operation will time out while waiting for data that never comes. To solve this you can either prefix data sent to the EverDrive with a header indicating the amount of data being sent or you read data 16 bytes at a time calling `bi_usb_can_wr` each time to check if there is more data. // returns 1 if there is data waiting to be read u8 bi\_usb\_can\_wr() { // check if USB\_STA\_PWR is 1 and USB\_STA\_TXE is 0 in REG\_USB\_CFG u32 status \= bi\_reg\_rd(REG\_USB\_CFG) & (USB\_STA\_PWR | USB\_STA\_TXE); if (status \== USB\_STA\_PWR)return 1; return 0; } u8 bi\_usb\_rd(void \*dst, u32 len) { u8 resp \= 0; u16 blen, baddr; while (len) { // data cannot be read in chunks larger than 512 bytes blen \= 512; if (blen \> len)blen \= len; baddr \= 512 \- blen; // tell the EverDrive to write data received from USB to // to REG\_USB\_DAT starting at address baddr. It will continue // to write data until the end of the 512 byte buffer. If // there is not enough data to fill the buffer it will wait // until that data is received bi\_reg\_wr(REG\_USB\_CFG, USB\_CMD\_RD | baddr); // wait for the serial data to be copied into REG\_USB\_DAT // if there is not enough data to write to the end of the // buffer this operation will time out resp \= bi\_usb\_busy(); if (resp)break; // copy data from REG\_USB\_DAT into ram sysPI\_rd(dst, REG\_ADDR(REG\_USB\_DAT + baddr), blen); dst += blen; len \-= blen; } return resp; } I2C peripheral -------------- Everdrive64 X7 contains a I2C peripheral to simplify communicating on the I2C. There are two peripherals in the bus: * RTC: [Analog Devices DS1337](https://www.analog.com/media/en/technical-documentation/data-sheets/ds1337-ds1337c.pdf) available on bus address 0x68. This is battery-backed and stores the real-time. See below in the RTC section for more information. NOTE: this is only available on Everdrive64 X7, not Everdrive64 X5. * EEPROM: [Microchip 24AA025UID](https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/20005202A.pdf) available on bus address 0x51. This EEPROM has a 256 byte address space: 128 bytes are writable (Everdrive OS seems to write the first 20 bytes, possibly for stats?), and the second 128 bytes are read-only and contain an unique ID for each chip, that is used as serial number. Everdrive OS reads 8 bytes from address 0xF8, with the last 4 possibly being the 32-bit unique ID according to the data sheet. The I2C hardware peripheral allows to access I2C without bit banging, transferring a single byte at a time, in sequences of 8 (8 bytes is also the maximum transfer length allowed by the RTC chip). The following code from libdragon demonstrates how to use the peripheral to perform a I2C write: #define DS1337\_DEVICE\_ADDR 0x68 ///< DS1337 I2C device address static const uint32\_t ED64X\_REG\_I2C\_CMD \= 0x1F800018; static const uint32\_t ED64X\_REG\_I2C\_DAT \= 0x1F80001C; static int ed64x\_i2c\_status(void) { return io\_read(ED64X\_REG\_I2C\_CMD) & 1; } static int ed64x\_i2c\_cmd(uint8\_t cmd) { io\_write(ED64X\_REG\_I2C\_DAT, cmd); while (io\_read(ED64X\_REG\_I2C\_CMD) & 0x80) {} return ed64x\_i2c\_status(); } \_\_attribute\_\_((used)) static uint8\_t ed64x\_i2c\_dat(uint8\_t cmd) { io\_write(ED64X\_REG\_I2C\_DAT, cmd); while (io\_read(ED64X\_REG\_I2C\_CMD) & 0x80) {} return io\_read(ED64X\_REG\_I2C\_DAT); } static void ed64x\_i2c\_start(void) { uint8\_t val \= io\_read(ED64X\_REG\_I2C\_CMD); io\_write(ED64X\_REG\_I2C\_CMD, 0x20); io\_write(ED64X\_REG\_I2C\_DAT, 0xFF); while (io\_read(ED64X\_REG\_I2C\_CMD) & 0x80) {} io\_write(ED64X\_REG\_I2C\_CMD, val | 0x11); // set write mode } static void ed64x\_i2c\_end(void) { io\_write(ED64X\_REG\_I2C\_CMD, 0x30); io\_write(ED64X\_REG\_I2C\_DAT, 0xFF); while (io\_read(ED64X\_REG\_I2C\_CMD) & 0x80) {} } \_\_attribute\_\_((used)) static void ed64x\_i2c\_setwr(void) { io\_write(ED64X\_REG\_I2C\_CMD, io\_read(ED64X\_REG\_I2C\_CMD) | 0x11); } \_\_attribute\_\_((used)) static void ed64x\_i2c\_setrd(void) { io\_write(ED64X\_REG\_I2C\_CMD, io\_read(ED64X\_REG\_I2C\_CMD) | 0x10); } static int ed64x\_i2c\_write(uint16\_t addr, const uint8\_t\* data, int len) { uint8\_t bus\_addr \= addr \>> 8; uint8\_t dev\_addr \= addr & 0xFF; for (int i\=0; i
Pokémon: Yellow Version | | Pokémon Stadium 2 | | Pokémon: Red & Blue Version

Pokémon: Yellow Version

Pokémon: Gold & Silver Version

Pokémon: Crystal Version | Voice Recognition Unit (VRU) ============================ ### [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit") (VRU) ([NUS-020](https://n64brew.dev/wiki/NUS-020 "NUS-020") ) [![](https://upload.wikimedia.org/wikipedia/commons/thumb/b/bc/N64_VRU.jpg/960px-N64_VRU.jpg)](https://n64brew.dev/wiki/File:N64_VRU.jpg) The VRU (Voice Recognition Unit). The [VRU](https://n64brew.dev/wiki/VRU "VRU") (Voice Recognition Unit) is compatible with only two games: Hey You, Pikachu! and Densha de Go! 64. A VRU is included with every factory package of Hey You, Pikachu! and is required to play the game. Densha de Go! 64 does not require the VRU, and as such, they are sold separately. The peripheral consists of a ballast ([NUS-020](https://n64brew.dev/wiki/NUS-020 "NUS-020") ) connected to controller port 4 of the system, a microphone ([NUS-021](https://n64brew.dev/wiki/NUS-021 "NUS-021") ), a yellow foam cover for the microphone, and a clip for clipping the microphone to the controller ([NUS-025](https://n64brew.dev/wiki/NUS-025 "NUS-025") , bundled with Hey You, Pikachu!) or a plastic neck holder for hands free usage ([NUS-022](https://n64brew.dev/wiki/NUS-022 "NUS-022") , bundled with Densha de Go! 64). The VRU is calibrated for best recognition of a high-pitched voice, such as a child's voice. As a result, the voices of adults and teenagers are less likely be recognized properly by the VRU. VRUs are region dependent, and a USA region VRU cannot be used with Japanese games and vice versa (foreign region VRUs are not detected by the games). No VRU compatible game was launched in the EUR region (PAL, Europe), so there is no EUR-region VRU. A similar device was also released for the Wii called the Wii Speak. VRU Accessories --------------- ### Microphone ([NUS-021](https://n64brew.dev/wiki/NUS-021 "NUS-021") ) It is a common Microphone. It is connected to "Voice Recognition Unit([NUS-020](https://n64brew.dev/wiki/NUS-020 "NUS-020") )" etc.. It is bundled with Voice Recognition System Unit. ### Microphone Holder ([NUS-022](https://n64brew.dev/wiki/NUS-022 "NUS-022") ) It is the Microphone Holder of the type hung to a neck. It is bundled with Voice Recognition System Unit. ### Microphone Strap ([NUS-025](https://n64brew.dev/wiki/NUS-025 "NUS-025") ) It is the Microphone holder of the type fixed to a controller. It is bundled with Voice Recognition System Unit. ### Microphone Foam Ball ([NUS-026](https://n64brew.dev/wiki/NUS-026 "NUS-026") ) The yellow-cover for the sound of a breath not being taken in by the microphone. It is bundled with Voice Recognition System Unit. Cleaning Kits ============= ### Control Deck Cleaner ([NUS-014](https://n64brew.dev/wiki/NUS-014 "NUS-014") ) Nintendo released a first party cleaning kit for the Nintendo 64. It contains everything required to clean the connectors of the control deck, controllers, Game Paks, Rumble Paks, and Controller Paks. ### Controller Cleaner ([NUS-015](https://n64brew.dev/wiki/NUS-015 "NUS-015") ) It is inserted instead of a Controller Pak ([NUS-004](https://n64brew.dev/wiki/NUS-004 "NUS-004") etc.) and cleans a connector. It's contained in "Cleaning Kit". "Cleaning Kit" was released in foreign countries. not released in Japan. ### Cleaning Wand ([NUS-016](https://n64brew.dev/wiki/NUS-016 "NUS-016") ) It cleans a metal terminal such as Game Pak ([NUS-006](https://n64brew.dev/wiki/NUS-006 "NUS-006") ) and Controller Pak ([NUS-004](https://n64brew.dev/wiki/NUS-004 "NUS-004") ). It's contained in "Cleaning Kit". "Cleaning Kit" was released in foreign countries. not released in Japan. Unused NUS Codes ================ ### ??? (NUS-018) Unknown - Not Used / Unreleased ### ??? (NUS-024) Unknown - Not Used / Unreleased ### ??? (NUS-027) Unknown - Not Used / Unreleased Retrieved from "[https://n64brew.dev/wiki/Parts\_and\_Accessories?oldid=1443](https://n64brew.dev/wiki/Parts_and_Accessories?oldid=1443) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # 64drive - N64brew Wiki 64drive ======= From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/64drive#mw-head) [Jump to search](https://n64brew.dev/wiki/64drive#searchInput) A flash cartridge made by [marshallh](http://64drive.retroactive.be/) suitable for gaming and development, but specifically targeted at developers. 64drive features a USB port for loading ROMs and performing communication with a host computer. In addition, it has an ESP8266 Wi-Fi module addressable from development ROMs. | | | | | --- | --- | --- |List of 64drive Versions | Name | USB Support | Storage | | 64drive HW1 | Mini USB? | SD and CompactFlash | | 64drive HW2 | Micro USB | microSD | Retrieved from "[https://n64brew.dev/wiki/64drive?oldid=106](https://n64brew.dev/wiki/64drive?oldid=106) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Flash Carts](https://n64brew.dev/wiki/Category:Flash_Carts "Category:Flash Carts") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # 64DD - N64brew Wiki 64DD ==== From N64brew Wiki (Redirected from [NUS-010](https://n64brew.dev/wiki/NUS-010?redirect=no "NUS-010") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-010#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-010#searchInput) The **64DD** is a magnetic disk drive peripheral for the [Nintendo 64](https://n64brew.dev/wiki/Nintendo_64 "Nintendo 64") game console developed by Nintendo. It was announced in 1995, prior to the Nintendo 64's 1996 launch, and after numerous delays was released only in Japan on December 1, 1999. The "64" references both the Nintendo 64 console and the 64 MB storage capacity of the disks, and "DD" is short for "disk drive" or "dynamic drive". The 64DD was codenamed "Leo" during development. Physical Memory Map ------------------- The following is the physical memory map for the 64DD hardware: | Address Range | | Name | Description | | --- | --- | --- | --- | | 0x05000000 | 0x050003FF | ASIC\_C2\_BUFF | C2 Sector Buffer | | 0x05000400 | 0x050004FF | ASIC\_SECTOR\_BUFF | User Sector Buffer | | 0x05000500 | 0x0500057F | Registers | [64DD interface](https://n64brew.dev/wiki/NUS-010#64DD_interface) | | 0x05000580 | 0x050005BF | MSEQ\_RAM\_ADDR | Micro Sequencer RAM | | 0x06000000 | 0x063FFFFF | DDROM | 64DD IPL ROM | It is usually accessed under the KSEG1 memory segment as uncached direct access to those registers. 64DD interface -------------- Main article: [64DD/Interface](https://n64brew.dev/wiki/64DD/Interface "64DD/Interface") The 64DD interface is made of several 16-bit memory-mapped registers allowing the CPU to control the 64DD hardware. 64DD commands ------------- Main article: [64DD/Commands](https://n64brew.dev/wiki/64DD/Commands "64DD/Commands") One of the main use of the 64DD is through commands. Commands are a single byte, with sometimes a 16-bit argument passed as data. Retrieved from "[https://n64brew.dev/wiki/64DD?oldid=5543](https://n64brew.dev/wiki/64DD?oldid=5543) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Nintendo 64 - N64brew Wiki Nintendo 64 =========== From N64brew Wiki (Redirected from [NUS-001](https://n64brew.dev/wiki/NUS-001?redirect=no "NUS-001") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-001#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-001#searchInput) The Nintendo 64 (officially abbreviated as N64, hardware model number pre-term: NUS, stylized as NINTENDO64) is a home video game console developed and marketed by Nintendo. Named for its 64-bit central processing unit, it was released in June 1996 in Japan, September 1996 in North America, and March 1997 in Europe and Australia. It was the last major home console to use the ROM cartridge as its primary storage format until the Switch in 2017. The Nintendo 64 was discontinued in 2002 following the launch of its successor, the GameCube, in 2001. Retrieved from "[https://n64brew.dev/wiki/Nintendo\_64?oldid=192](https://n64brew.dev/wiki/Nintendo_64?oldid=192) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # NUS-011 - N64brew Wiki NUS-011 ======= From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/NUS-011#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-011#searchInput) Redirect to: * [64DD Disk](https://n64brew.dev/wiki/64DD_Disk?action=edit&redlink=1 "64DD Disk (page does not exist)") Retrieved from "[https://n64brew.dev/wiki/NUS-011?oldid=223](https://n64brew.dev/wiki/NUS-011?oldid=223) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Jumper Pak - N64brew Wiki Jumper Pak ========== From N64brew Wiki (Redirected from [NUS-008](https://n64brew.dev/wiki/NUS-008?redirect=no "NUS-008") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-008#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-008#searchInput) The **Jumper Pak** is a filler that plugs into the console's memory expansion port. It serves no functional purpose other than to terminate the RAMBUS bus in the absence of the [Expansion Pak](https://n64brew.dev/wiki/Expansion_Pak "Expansion Pak") . If neither the Expansion Pak nor Jumper Pak are in the expansion port, the N64 will not boot. Retrieved from "[https://n64brew.dev/wiki/Jumper\_Pak?oldid=5142](https://n64brew.dev/wiki/Jumper_Pak?oldid=5142) " [Categories](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") * [Paks](https://n64brew.dev/wiki/Category:Paks "Category:Paks") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Controller - N64brew Wiki Controller ========== From N64brew Wiki (Redirected from [Nintendo 64 controller](https://n64brew.dev/wiki/Nintendo_64_controller?redirect=no "Nintendo 64 controller") ) [Jump to navigation](https://n64brew.dev/wiki/Nintendo_64_controller#mw-head) [Jump to search](https://n64brew.dev/wiki/Nintendo_64_controller#searchInput) [![](https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/N64-Controller-Gray.jpg/960px-N64-Controller-Gray.jpg)](https://n64brew.dev/wiki/File:N64-Controller-Gray.jpg) The N64 controller, with it's tri-wing design, featured 14 button inputs, and an optical encoder based analog stick. The controller connects to the console over three wires, power, data, and ground. On the bottom, it also had a 32 pin edge board connector for additional accessory Paks, such as the [Controller Pak](https://n64brew.dev/wiki/Controller_Pak "Controller Pak") and [Rumble Pak](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak") . Contents -------- * [1 Design](https://n64brew.dev/wiki/Nintendo_64_controller#Design) * [2 Operation](https://n64brew.dev/wiki/Nintendo_64_controller#Operation) * [2.1 Analog Stick](https://n64brew.dev/wiki/Nintendo_64_controller#Analog_Stick) * [2.2 Accessory Port](https://n64brew.dev/wiki/Nintendo_64_controller#Accessory_Port) * [2.2.1 Pak Detection](https://n64brew.dev/wiki/Nintendo_64_controller#Pak_Detection) * [3 Homebrew Controllers](https://n64brew.dev/wiki/Nintendo_64_controller#Homebrew_Controllers) Design ------ The controller's design was unique in that it had three handles, which allowed the player to hold the controller in multiple ways. However, typically the right hand would be on the right-most handle, and the left would alternate between the middle or left depending on whether the game utilized the D-pad or analog stick. The standard controller used rubber-like contacts underneath plastic button caps. When pressed, these contacts would close a circuit printed onto the PCB inside the controller; this is different from a clicky push button, but not uncommon for game controllers. Operation --------- The controller is managed by a single IC chip that works as an interface between the console and the other hardware components. The chip is proprietary and has a generic label "Nintendo NUS-CNT" plus a model number. There are three small PCBs connected via wires for the L, R, and Z buttons. Eleven pads on the main PCB are used for the other buttons. The analog stick is a self contained module that is connected via a 6-pin, JST-PH compatible connector. A 32-pin port connector is used to interface with different accessories. ### Analog Stick The analog stick uses a pair of optical encoding disks to determine its relative position. This works similar to linear encoders found in printers, except that instead of being a strip of tape/film, the N64 uses a rotating disk for each axis. Each disk has small holes along the circumference of the disk. These holes allow light to pass through to an optical sensor on the opposite side. When the stick is moved, there are two signals generated per axis, for a total of four, which are slightly offset from each other. One of the two signals is used as the interrupt signal (`XA` and `YA` for the x-axis and y-axis respectively), and the other (`XB` and `YB`) is compared with the state of the first in order to determine which direction (positive or negative) that analog stick is moving for that axis. If the stick is moving in the positive direction at a constant speed, the two signals will look similar to this: [![](https://static.wikitide.net/n64wiki/8/8a/Analogstick-xaxb-positive.svg)](https://n64brew.dev/wiki/File:Analogstick-xaxb-positive.svg) If the stick is moving in the negative direction, they will look similar to this: [![](https://static.wikitide.net/n64wiki/b/b8/Analogstick-xaxb-negative.svg)](https://n64brew.dev/wiki/File:Analogstick-xaxb-negative.svg) [![](https://static.wikitide.net/n64wiki/thumb/9/91/Analogstick-pinout-diagram.png/384px-Analogstick-pinout-diagram.png)](https://n64brew.dev/wiki/File:Analogstick-pinout-diagram.png) Analog stick connector pinout To determine the direction along a particular axis and how far the stick has moved, the NUS-CNT chip will listen for any changing edge of that axis' interrupt signal (e.g. `XA` for the x-axis). _A changing edge means any time the signal goes from LOW to HIGH, or HIGH to LOW._ When an edge change is detected, the state of `XA` is compared against `XB`. If `input`, then the stick has moved +1 units. If `input`, then the stick has moved -1 units. While the analog stick reports _relative_ movement whenever it moves, the N64 console requires the current position, not relative. In order to provide that, the NUS-CNT chip keeps track of these movements. The initial values for each axis is zero. _Although not tested/quantified, it may be possible to extrapolate the speed at which the user is moving the stick. The signals produced vary in length between each edge change. The time between edge changes may be able to be used to indicate speed. While speed isn't needed by the console, this could still offer some additional data in a custom controller or interface._ The analog stick has a 6-pin connector: `VCC` is 3.3V. `GND` is common ground. `XA` and `XB` are the signal pins for the x-axis. `YA` and `YB` are for the y-axis. ### Accessory Port [![](https://static.wikitide.net/n64wiki/6/6f/AccessoryPortPinout.png)](https://n64brew.dev/wiki/File:AccessoryPortPinout.png) Pinout of the accessory port connector with respect to the pak's orientation Pak devices could be plugged into this port to expand the console's capabilities or to enhance the player's experience. The console communicates with these devices via the [Joybus Protocol](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") , however a microchip is used on the controller to interpret the Joybus commands and interface with the paks. #### Pak Detection If the `DETECT` pin is pulled HIGH, then a pak is inserted in the connector. Paks do this by directly connecting the pin to 3.3V. When the console sends the [0x00 Joybus](https://n64brew.dev/wiki/Joybus_Protocol#Additional_Command_Details "Joybus Protocol") command, the controller will return its hardware ID and then one additional byte that tells the console whether a pak is inserted (`0x01`) or not (`0x02`). If a game chooses to do so, it is possible to detect what kind of pak is inserted. _\*WIP Section\*_ Homebrew Controllers -------------------- While most of the official controller uses fairly common connectors and buttons, the port connector and the NUS-CNT chips are proprietary and hard to source. The vast majority of custom controllers use a salvaged NUS-CNT IC chip as the brain, to interface with the console. However, [Bigbass](https://n64brew.dev/wiki/User:Bigbass "User:Bigbass") has developed an open source alternative that utilizes a PIC microcontroller as the brain, and other modern components. More details are on the [project's Github page](https://github.com/bigbass1997/PIC-CNT64) . The [TAStm32](https://github.com/Ownasaurus/TAStm32) is another alternative designed as a replay device for Tool-Assisted Speedruns. The port connector uses an extremely uncommon pitch of 1.5mm, with a board thickness of 1.2mm. No sources are known at this time for purchasing these connectors. Retrieved from "[https://n64brew.dev/wiki/Controller?oldid=1485](https://n64brew.dev/wiki/Controller?oldid=1485) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Mouse - N64brew Wiki Mouse ===== From N64brew Wiki (Redirected from [NUS-017](https://n64brew.dev/wiki/NUS-017?redirect=no "NUS-017") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-017#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-017#searchInput) This a standard 2-button ball mouse. A few 64DD games support it: * Mario Artist: Paint Studio * Mario Artist: Talent Studio * Mario Artist: Polygon Studio * Mario Artist: Communication Kit Most other N64 games see the mouse as a controller, and a few of them are playable like this. Mouse movements register as analog stick movements. The left button maps to the A button, and the right mouse button maps to B. There is a single microcontroller that handles everything. This mouse appears to be based on contemporary Mitsumi PS/2 mice, which have nearly identical guts. Closest match is the ECM-S3902 Retrieved from "[https://n64brew.dev/wiki/Mouse?oldid=239](https://n64brew.dev/wiki/Mouse?oldid=239) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # VRU - N64brew Wiki VRU === From N64brew Wiki (Redirected from [NUS-020](https://n64brew.dev/wiki/NUS-020?redirect=no "NUS-020") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-020#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-020#searchInput) Redirect to: * [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit") Retrieved from "[https://n64brew.dev/wiki/VRU?oldid=4352](https://n64brew.dev/wiki/VRU?oldid=4352) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Expansion Pak - N64brew Wiki Expansion Pak ============= From N64brew Wiki (Redirected from [NUS-007](https://n64brew.dev/wiki/NUS-007?redirect=no "NUS-007") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-007#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-007#searchInput) The **Expansion Pak** consists of 4 MB (4,194,304 bytes) of random access memory (RAM)—which is [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM") , the same type of memory used inside the console itself—increasing the Nintendo 64 console's RAM from 4 MB to 8 MB of contiguous main memory. It is installed in a port on top of the console and replaces the pre-installed [Jumper Pak](https://n64brew.dev/wiki/Jumper_Pak "Jumper Pak") , which is simply a RAMBUS terminator. The Expansion Pak is required for two retail games: _Donkey Kong 64_ and _The Legend of Zelda: Majora’s Mask._ Some other games, like _Rayman 2: The Great Escape_ are capable of using the Expansion Pak if available, but do not require it. Details of how the Expansion Pak is detected are in the [RDRAM](https://n64brew.dev/wiki/RDRAM "RDRAM") article. Contents -------- * [1 Hardware](https://n64brew.dev/wiki/NUS-007#Hardware) * [1.1 Variations](https://n64brew.dev/wiki/NUS-007#Variations) * [1.1.1 Version 1 - (No extra resistor, no bodge)](https://n64brew.dev/wiki/NUS-007#Version_1_-_(No_extra_resistor,_no_bodge)) * [1.1.2 Version 2 - (Bodge from Nintendo)](https://n64brew.dev/wiki/NUS-007#Version_2_-_(Bodge_from_Nintendo)) * [1.1.3 Version 3 - (Resistor added to PCB design)](https://n64brew.dev/wiki/NUS-007#Version_3_-_(Resistor_added_to_PCB_design)) * [1.2 Open Source Recreation](https://n64brew.dev/wiki/NUS-007#Open_Source_Recreation) Hardware -------- ### Variations The Nintendo Expansion Pak went through several PCB revisions. The most notable change was to add in an extra termination resistor to Pin 20 (SIn - Initialization Daisy Chain Input). ##### Version 1 - (No extra resistor, no bodge) * [![Front side of a Version 1 Expansion Pak (No resistor, no bodge)[1]](https://static.wikitide.net/n64wiki/thumb/7/7e/V1-Front.jpg/500px-V1-Front.jpg)](https://n64brew.dev/wiki/File:V1-Front.jpg "Front side of a Version 1 Expansion Pak (No resistor, no bodge)[1]") Front side of a Version 1 Expansion Pak (No resistor, no bodge)[\[1\]](https://n64brew.dev/wiki/NUS-007#cite_note-1) * [![Back side of a Version 1 Expansion Pak (No resistor, no bodge)[2]](https://static.wikitide.net/n64wiki/thumb/4/43/V1-Back.jpg/500px-V1-Back.jpg)](https://n64brew.dev/wiki/File:V1-Back.jpg "Back side of a Version 1 Expansion Pak (No resistor, no bodge)[2]") Back side of a Version 1 Expansion Pak (No resistor, no bodge)[\[2\]](https://n64brew.dev/wiki/NUS-007#cite_note-2) ##### Version 2 - (Bodge from Nintendo) * [![Front side of a Bodged Expansion Pak from Nintendo](https://static.wikitide.net/n64wiki/thumb/9/92/Bodge-Front.jpg/500px-Bodge-Front.jpg)](https://n64brew.dev/wiki/File:Bodge-Front.jpg "Front side of a Bodged Expansion Pak from Nintendo") Front side of a Bodged Expansion Pak from Nintendo * [![Back side of a Bodged Expansion Pak from Nintendo](https://static.wikitide.net/n64wiki/thumb/0/08/Bodge-Back.jpg/500px-Bodge-Back.jpg)](https://n64brew.dev/wiki/File:Bodge-Back.jpg "Back side of a Bodged Expansion Pak from Nintendo") Back side of a Bodged Expansion Pak from Nintendo ##### Version 3 - (Resistor added to PCB design) * [![Front side of the modified PCB design that includes the resistor](https://static.wikitide.net/n64wiki/thumb/1/14/Final-Front.jpg/500px-Final-Front.jpg)](https://n64brew.dev/wiki/File:Final-Front.jpg "Front side of the modified PCB design that includes the resistor") Front side of the modified PCB design that includes the resistor * [![Back side of the modified PCB design that includes the resistor](https://static.wikitide.net/n64wiki/thumb/0/04/Final-Back.jpg/500px-Final-Back.jpg)](https://n64brew.dev/wiki/File:Final-Back.jpg "Back side of the modified PCB design that includes the resistor") Back side of the modified PCB design that includes the resistor ### Open Source Recreation An open source, 1:1 recreation of the OEM Expansion Pak can be found [here](https://github.com/MasonStooksbury/OEM-N64-Expansion-Pak) complete with pictures and the full KiCad project. * [![Recreated OEM Expansion Pak](https://static.wikitide.net/n64wiki/thumb/1/1f/Recreated_OEM_Expansion_Pak.png/500px-Recreated_OEM_Expansion_Pak.png)](https://n64brew.dev/wiki/File:Recreated_OEM_Expansion_Pak.png "Recreated OEM Expansion Pak") Recreated OEM Expansion Pak * [![Electrical schematic for the OEM Expansion Pak](https://static.wikitide.net/n64wiki/thumb/1/1b/Schematic.png/439px-Schematic.png)](https://n64brew.dev/wiki/File:Schematic.png "Electrical schematic for the OEM Expansion Pak") Electrical schematic for the OEM Expansion Pak * [![Front side of the PCB in KiCad](https://static.wikitide.net/n64wiki/thumb/8/81/Front_side_of_the_schematic.png/500px-Front_side_of_the_schematic.png)](https://n64brew.dev/wiki/File:Front_side_of_the_schematic.png "Front side of the PCB in KiCad") Front side of the PCB in KiCad * [![Back side of the PCB in KiCad](https://static.wikitide.net/n64wiki/thumb/6/6b/Back_side_of_the_schematic.png/500px-Back_side_of_the_schematic.png)](https://n64brew.dev/wiki/File:Back_side_of_the_schematic.png "Back side of the PCB in KiCad") Back side of the PCB in KiCad 1. [↑](https://n64brew.dev/wiki/NUS-007#cite_ref-1) Picture provided by: [https://www.reddit.com/user/URA\_CJ/](https://www.reddit.com/user/URA_CJ/) 2. [↑](https://n64brew.dev/wiki/NUS-007#cite_ref-2) Picture provided by: [https://www.reddit.com/user/URA\_CJ/](https://www.reddit.com/user/URA_CJ/) Retrieved from "[https://n64brew.dev/wiki/Expansion\_Pak?oldid=5328](https://n64brew.dev/wiki/Expansion_Pak?oldid=5328) " [Categories](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") * [Paks](https://n64brew.dev/wiki/Category:Paks "Category:Paks") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Capture Cassette - N64brew Wiki Capture Cassette ================ From N64brew Wiki (Redirected from [NUS-028](https://n64brew.dev/wiki/NUS-028?redirect=no "NUS-028") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-028#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-028#searchInput) This cart was included with a disk game called Mario Artist: Talent Studio. It's a composite video capture device. The cart comes with a microphone, which plugs into the small port on the left of the cart in the above pictures. There's just one ASIC on the board that appears to handle everything. Trivia: this is one of the few N64 accessories that uses triwing screws. Retrieved from "[https://n64brew.dev/wiki/Capture\_Cassette?oldid=242](https://n64brew.dev/wiki/Capture_Cassette?oldid=242) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Modem - N64brew Wiki Modem ===== From N64brew Wiki (Redirected from [NUS-029](https://n64brew.dev/wiki/NUS-029?redirect=no "NUS-029") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-029#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-029#searchInput) The 64DD came with a modem cart. It fits into the cart slot, right where a game would go. There is a green light on the front to show when the modem is active. Inside the modem is an ASIC and a ROM. The ROM had to be dumped the "hard way", by removing the ROM chip from the board and connecting it to a reader. The ROM is 32mbits and has a base address of 0x18000000, with a mirror at 0x18400000. It begins with a standard 64-byte cart header, followed by three ELF files at 0x40, 0x10000, and 0x20000. Each ELF appears to implement a softmodem. It is not clear why there are three different versions. The Randnet Disk doesn't recognize the modem if its ROM is physically removed from the board. Both the Randnet Disk and the Communication Kit dial a built-in phone number that cannot be changed: 03-3568-5050. This number is no longer in service. Trivia: this is one of the few N64 accessories that uses triwing screws. Retrieved from "[https://n64brew.dev/wiki/Modem?oldid=241](https://n64brew.dev/wiki/Modem?oldid=241) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Jumper Pak - N64brew Wiki Jumper Pak ========== From N64brew Wiki (Redirected from [NUS-012](https://n64brew.dev/wiki/NUS-012?redirect=no "NUS-012") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-012#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-012#searchInput) The **Jumper Pak** is a filler that plugs into the console's memory expansion port. It serves no functional purpose other than to terminate the RAMBUS bus in the absence of the [Expansion Pak](https://n64brew.dev/wiki/Expansion_Pak "Expansion Pak") . If neither the Expansion Pak nor Jumper Pak are in the expansion port, the N64 will not boot. Retrieved from "[https://n64brew.dev/wiki/Jumper\_Pak?oldid=5142](https://n64brew.dev/wiki/Jumper_Pak?oldid=5142) " [Categories](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") * [Paks](https://n64brew.dev/wiki/Category:Paks "Category:Paks") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Jumper Pak - N64brew Wiki Jumper Pak ========== From N64brew Wiki (Redirected from [Jumper Pak Ejector](https://n64brew.dev/wiki/Jumper_Pak_Ejector?redirect=no "Jumper Pak Ejector") ) [Jump to navigation](https://n64brew.dev/wiki/Jumper_Pak_Ejector#mw-head) [Jump to search](https://n64brew.dev/wiki/Jumper_Pak_Ejector#searchInput) The **Jumper Pak** is a filler that plugs into the console's memory expansion port. It serves no functional purpose other than to terminate the RAMBUS bus in the absence of the [Expansion Pak](https://n64brew.dev/wiki/Expansion_Pak "Expansion Pak") . If neither the Expansion Pak nor Jumper Pak are in the expansion port, the N64 will not boot. Retrieved from "[https://n64brew.dev/wiki/Jumper\_Pak?oldid=5142](https://n64brew.dev/wiki/Jumper_Pak?oldid=5142) " [Categories](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") * [Paks](https://n64brew.dev/wiki/Category:Paks "Category:Paks") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Keyboard - N64brew Wiki Keyboard ======== From N64brew Wiki (Redirected from [RND-001](https://n64brew.dev/wiki/RND-001?redirect=no "RND-001") ) [Jump to navigation](https://n64brew.dev/wiki/RND-001#mw-head) [Jump to search](https://n64brew.dev/wiki/RND-001#searchInput) The **Nintendo 64 Keyboard** (Nintendo 64 キーボード) is an accessory released for the Nintendo 64DD in Japan. Nintendo released the accessory in conjunction with Randnet DD. Stickers came with the set that you could place over the buttons. With the keyboard, players could chat with people over the internet and send them digital mail. The accessory was never released outside of Japan. It cost ¥4,600. While the keyboard itself was black, the keys were white and dark blue. It seems to be used exclusively by one game, the Randnet Disk, which is a web browser and email client. Key Matrix Map -------------- 1. Converting Joybus to Compressed: #define JBSC2CSC(x, y) ((x - 2) << 4) + (y - 1) 2. Converting Compressed back to Joybus: #define CSC2JBSC(x) ((x >> 4) + 2), (x & 0x0F) + 1 | Key Function | Compressed Scan Code (1 byte) | Joybus Scan Code (2 byte) | | --- | --- | --- | | 0 | 0x45 | \[0x06, 0x06\] | | 1 | 0xA4 | \[0x0C, 0x05\] | | 2 | 0x34 | \[0x05, 0x05\] | | 3 | 0x44 | \[0x06, 0x05\] | | 4 | 0x54 | \[0x07, 0x05\] | | 5 | 0x64 | \[0x08, 0x05\] | | 6 | 0x74 | \[0x09, 0x05\] | | 7 | 0x75 | \[0x09, 0x06\] | | 8 | 0x65 | \[0x08, 0x06\] | | 9 | 0x55 | \[0x07, 0x06\] | | A | 0xB6 | \[0x0D, 0x07\] | | Alt\_L | 0xE7 | \[0x10, 0x08\] | | Asterisk | 0x42 | \[0x06, 0x03\] | | B | 0x57 | \[0x07, 0x08\] | | BackQuote | 0xA5 | \[0x0C, 0x06\] | | BackSlash | 0xE3 | \[0x10, 0x04\] | | BackSpace | 0xB5 | \[0x0D, 0x06\] | | Bar | 0xF4 | \[0x11, 0x05\] | | BracketLeft | 0xA3 | \[0x0C, 0x04\] | | BracketRight | 0x25 | \[0x04, 0x06\] | | C | 0x37 | \[0x05, 0x08\] | | Caps\_Lock | 0xD4 | \[0x0F, 0x05\] | | Control\_L | 0xF6 | \[0x11, 0x07\] | | D | 0x36 | \[0x05, 0x07\] | | Down | 0x14 | \[0x03, 0x05\] | | E | 0x40 | \[0x06, 0x01\] | | End | 0x05 | \[0x02, 0x06\] | | Escape | 0x87 | \[0x0A, 0x08\] | | F | 0x46 | \[0x06, 0x07\] | | F1 | 0x90 | \[0x0B, 0x01\] | | F10 | 0x83 | \[0x0A, 0x04\] | | F11 | 0x02 | \[0x02, 0x03\] | | F12 | 0x95 | \[0x0B, 0x06\] | | F2 | 0x80 | \[0x0A, 0x01\] | | F3 | 0x97 | \[0x0B, 0x08\] | | F4 | 0x86 | \[0x0A, 0x07\] | | F5 | 0x96 | \[0x0B, 0x07\] | | F6 | 0x81 | \[0x0A, 0x02\] | | F7 | 0x91 | \[0x0B, 0x02\] | | F8 | 0x82 | \[0x0A, 0x03\] | | F9 | 0x92 | \[0x0B, 0x03\] | | G | 0x56 | \[0x07, 0x07\] | | Greater | 0x61 | \[0x08, 0x02\] | | H | 0x66 | \[0x08, 0x07\] | | Henkan | 0xC1 | \[0x0E, 0x02\] | | Home | 0xEF | \[0x10, 0x10\] | | I | 0x63 | \[0x08, 0x04\] | | J | 0x76 | \[0x09, 0x07\] | | K | 0x72 | \[0x09, 0x03\] | | Kana\_Lock | 0xE5 | \[0x10, 0x06\] | | L | 0x62 | \[0x08, 0x03\] | | Left | 0x04 | \[0x02, 0x05\] | | Less | 0x71 | \[0x09, 0x02\] | | M | 0x77 | \[0x09, 0x08\] | | Menu | 0x94 | \[0x0B, 0x05\] | | Meta\_L | 0xD6 | \[0x0F, 0x07\] | | Minus | 0x35 | \[0x05, 0x06\] | | Muhenkan | 0xE1 | \[0x10, 0x02\] | | N | 0x67 | \[0x08, 0x08\] | | Next | 0x06 | \[0x02, 0x07\] | | Num\_Lock | 0x84 | \[0x0A, 0x05\] | | O | 0x53 | \[0x07, 0x04\] | | P | 0x43 | \[0x06, 0x04\] | | Plus | 0x52 | \[0x07, 0x03\] | | Prior | 0x07 | \[0x02, 0x08\] | | Q | 0xA0 | \[0x0C, 0x01\] | | QuestionMark | 0x51 | \[0x07, 0x02\] | | QuoteLeft | 0x33 | \[0x05, 0x04\] | | R | 0x50 | \[0x07, 0x01\] | | Return | 0xB3 | \[0x0D, 0x04\] | | Right | 0x24 | \[0x04, 0x05\] | | S | 0xA6 | \[0x0C, 0x07\] | | Shift\_L | 0xC0 | \[0x0E, 0x01\] | | Shift\_R | 0xC5 | \[0x0E, 0x06\] | | Space | 0x41 | \[0x06, 0x02\] | | T | 0x60 | \[0x08, 0x01\] | | Tab | 0xB0 | \[0x0D, 0x01\] | | U | 0x73 | \[0x09, 0x04\] | | Up | 0x03 | \[0x02, 0x04\] | | V | 0x47 | \[0x06, 0x08\] | | W | 0x30 | \[0x05, 0x01\] | | X | 0xA7 | \[0x0C, 0x08\] | | Y | 0x70 | \[0x09, 0x01\] | | Z | 0xB7 | \[0x0D, 0x08\] | | Zenkaku\_Hankaku | 0xB4 | \[0x0D, 0x05\] | Retrieved from "[https://n64brew.dev/wiki/Keyboard?oldid=5638](https://n64brew.dev/wiki/Keyboard?oldid=5638) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Rumble Pak - N64brew Wiki Rumble Pak ========== From N64brew Wiki (Redirected from [NUS-013](https://n64brew.dev/wiki/NUS-013?redirect=no "NUS-013") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-013#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-013#searchInput) The Rumble Pak (Japanese: 振動パック, Hepburn: Shindō Pakku) is a removable device from Nintendo which provides force feedback while playing video games. On the Nintendo 64, the Rumble Pak plugs into the bottom of the controller. Games that support the Rumble Pak cause it to vibrate in select situations, such as when firing a weapon or receiving damage, to immerse the player in the game. Versions of the Rumble Pak are available for the Nintendo 64, the Nintendo DS, and the Nintendo DS Lite. A select few Game Boy Color and Game Boy Advance games use a similar technology built into the game cartridge. Force feedback vibration has become a built-in standard feature in almost every home video game console controller since. Hardware -------- The OEM Rumble Pak contains a custom chip labelled VDEC-CNT, with this pinout: +CE -> |1 18| +3 /OE -> |2 17| ?? capacitor /WE -> |3 16| ?? n/c A14 -> |4 15| -> motor via BJT A15 -> |5 14| <> D7 D0 <> |6 13| <> D6 D1 <> |7 12| <> D5 D2 <> |8 11| <> D4 Gnd -- |9 10| <> D3 Software -------- When accessing the Rumble Pak via the [joybus](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") protocol, the accessory presents the following memory map: | | | | | | --- | --- | --- | --- |OEM (official) Rumble Pak memory map | Address | Direction | Description | Details | | 0x8000 - 0xBFFF | Read | Identification | 0x80 if unlocked, 0x00 otherwise | | Write | Unlock | 0x80 to unlock, all others lock | | 0xC000 - 0xFFFF | Read | Rumble status | odd numbers enabled, even numbers disabled | | Write | Rumble enable | Retrieved from "[https://n64brew.dev/wiki/Rumble\_Pak?oldid=5769](https://n64brew.dev/wiki/Rumble_Pak?oldid=5769) " [Categories](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") * [Paks](https://n64brew.dev/wiki/Category:Paks "Category:Paks") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # VRU - N64brew Wiki VRU === From N64brew Wiki (Redirected from [NUS-021](https://n64brew.dev/wiki/NUS-021?redirect=no "NUS-021") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-021#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-021#searchInput) Redirect to: * [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit") Retrieved from "[https://n64brew.dev/wiki/VRU?oldid=4352](https://n64brew.dev/wiki/VRU?oldid=4352) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Voice Recognition Unit - N64brew Wiki Voice Recognition Unit ====================== From N64brew Wiki (Redirected from [VRU](https://n64brew.dev/wiki/VRU?redirect=no "VRU") ) [Jump to navigation](https://n64brew.dev/wiki/VRU#mw-head) [Jump to search](https://n64brew.dev/wiki/VRU#searchInput) The **Voice Recoginition Unit** (VRU) is a microphone for the N64 developed by Ambrella. The VRU prototype was built from parts in Akihabara. The VRU is region locked, and known as the VRS (voice recognition system) in Japan. It was released December 12, 1998 in Japan and November 6, 2000 in North America. In North America, the only game supporting the VRU (and includes and requires it, in fact) is _Hey You, Pikachu!_ In Japan, the game _Densha de Go!_ (meaning "Go by train" in english) supports the VRU as well. The VRU consists of a microphone covered in a yellow foam ball with a 3.5mm audio jack. The microphone can be clipped onto the N64 controller and clamped around the Controller Pak port. Alternatively, the VRU can he hung from the neck. The VRU is calibrated for higher pitched voices. The VRU plugs into controller port #4 and the cable is 6 feet long. The VRU unit has 4 tripoint screws and contains the following chips (at this time, only speculation on the purpose of each chip is known): * **NEC d9930g (uPD9930 audio CODEC)**: Converts analog signals from the mic to digital samples that are forwarded to VRD-NUS, gain settings and some other registers are directly programmable via joybus command 13/0x0D * **VRD-NUS (VRS model) / EVR-NUS (VRU model)**: Probably a repackaged NEC uPD7701x 16-bit DSP, the pinout of the VRD-NUS matches up very well (explaining why there are so many unused pins) and its capabilities line up with expectation: it has an 8-bit bus connecting to VCI-NUS and a serial interface compatible with uPD9930. If so, there are instruction and data ROMs inside. uPD7701x has a JTAG interface but it isn't well documented so whether it can dump the ROM without decapsulation is unclear. * **VCI-NUS**: Controls joybus wire transactions and acts as the host controller for VRD-NUS and uPD9930. Retrieved from "[https://n64brew.dev/wiki/Voice\_Recognition\_Unit?oldid=5566](https://n64brew.dev/wiki/Voice_Recognition_Unit?oldid=5566) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # VRU - N64brew Wiki VRU === From N64brew Wiki (Redirected from [NUS-022](https://n64brew.dev/wiki/NUS-022?redirect=no "NUS-022") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-022#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-022#searchInput) Redirect to: * [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit") Retrieved from "[https://n64brew.dev/wiki/VRU?oldid=4352](https://n64brew.dev/wiki/VRU?oldid=4352) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # VRU - N64brew Wiki VRU === From N64brew Wiki (Redirected from [NUS-025](https://n64brew.dev/wiki/NUS-025?redirect=no "NUS-025") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-025#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-025#searchInput) Redirect to: * [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit") Retrieved from "[https://n64brew.dev/wiki/VRU?oldid=4352](https://n64brew.dev/wiki/VRU?oldid=4352) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Transfer Pak - N64brew Wiki Transfer Pak ============ From N64brew Wiki (Redirected from [NUS-019](https://n64brew.dev/wiki/NUS-019?redirect=no "NUS-019") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-019#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-019#searchInput) The Transfer Pak is an accessory that plugs into the controller and allows the Nintendo 64 to transfer data between its own games and Game Boy or Game Boy Color games. The Transfer Pak has a Game Boy Color slot and a part that fits onto the expansion port of the N64 controller. It was included with the game Pokémon Stadium, as the game's main feature is importing Pokémon teams from Game Boy games. In the Nintendo 64 Programming Manual, the Transfer Pak is referred to as the N64 Game Boy Pak. ### References * [https://ultra64.ca/files/documentation/online-manuals/man/pro-man/pro26/index26.7.html](https://ultra64.ca/files/documentation/online-manuals/man/pro-man/pro26/index26.7.html) Retrieved from "[https://n64brew.dev/wiki/Transfer\_Pak?oldid=4372](https://n64brew.dev/wiki/Transfer_Pak?oldid=4372) " [Categories](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") * [Paks](https://n64brew.dev/wiki/Category:Paks "Category:Paks") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Game Pak - N64brew Wiki Game Pak ======== From N64brew Wiki (Redirected from [NUS-006](https://n64brew.dev/wiki/NUS-006?redirect=no "NUS-006") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-006#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-006#searchInput) Nintendo 64 **Game Pak** (part number NUS-006) is the brand name of the consumer ROM cartridge product that stores game data for the Nintendo 64, released in 1996. As with Nintendo's previous consoles, the Game Pak's design tradeoffs were intended to achieve maximal system speed and minimal base console cost, with a lesser storage space and a higher unit cost per game. Integrating a CD-ROM drive, with its expensive and slow moving parts, would have drastically increased the console's base price and reduced its performance. See [ROM header](https://n64brew.dev/wiki/ROM_Header "ROM Header") for the standard header contents found in every Game Pak ROM. ### Connector Pinout | | | | | | | --- | --- | --- | --- | --- | | Name | Pin | | Pin | Name | | GND | 1 | | 26 | GND | | GND | 2 | | 27 | GND | | AD15 | 3 | | 28 | AD0 | | AD14 | 4 | | 29 | AD1 | | AD13 | 5 | | 30 | AD2 | | GND | 6 | | 31 | GND | | AD12 | 7 | | 32 | AD3 | | /WR | 8 | | 33 | ALE\_L | | 3.3V | 9 | | 34 | 3.3V | | /RD | 10 | | 35 | ALE\_H | | AD11 | 11 | | 36 | AD4 | | AD10 | 12 | | 37 | AD5 | | 12V | 13 | | 38 | 12V | | 12V | 14 | | 39 | 12V | | AD9 | 15 | | 40 | AD6 | | AD8 | 16 | | 41 | AD7 | | 3.3V | 17 | | 42 | 3.3V | | CIC\_15 | 18 | | 43 | CIC\_14 | | 1.95MHz\_CLK | 19 | | 44 | /INT1 | | /ColdReset | 20 | | 45 | /NMI | | EEPROM\_DAT | 21 | | 46 | VIDEO\_SYNC | | GND | 22 | | 47 | GND | | GND | 23 | | 48 | GND | | LAUDIO | 24 | | 49 | RAUDIO | | GND | 25 | | 50 | GND | ### Notes * Pins 14 & 39 on the cartridge connector are missing contacts, thus these pins only apply to the EXT connector on the bottom of the console. * Pin 18 is bi-directional data between the CIC and PIF. * Pin 19 is a ~1.95 MHz clock driven by the PIF, used for both the CIC's CPU clock (pin 11) and EEPROM communication. * Pin 21 is PIF Channel 5, via PIF pins 23 & 24. * Pin 43 is the clock associated with pin 18's data, and is always driven by the PIF. It only pulses when the PIF needs to send or receive data. Retrieved from "[https://n64brew.dev/wiki/Game\_Pak?oldid=5144](https://n64brew.dev/wiki/Game_Pak?oldid=5144) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Paks](https://n64brew.dev/wiki/Category:Paks "Category:Paks") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Cleaning Kit - N64brew Wiki Cleaning Kit ============ From N64brew Wiki (Redirected from [NUS-015](https://n64brew.dev/wiki/NUS-015?redirect=no "NUS-015") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-015#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-015#searchInput) Nintendo released a first party cleaning kit for the Nintendo 64. It contains everything required to clean the connectors of the control deck, controllers, Game Paks, Rumble Paks, and Controller Paks. Retrieved from "[https://n64brew.dev/wiki/Cleaning\_Kit?oldid=250](https://n64brew.dev/wiki/Cleaning_Kit?oldid=250) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Cleaning Kit - N64brew Wiki Cleaning Kit ============ From N64brew Wiki (Redirected from [NUS-016](https://n64brew.dev/wiki/NUS-016?redirect=no "NUS-016") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-016#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-016#searchInput) Nintendo released a first party cleaning kit for the Nintendo 64. It contains everything required to clean the connectors of the control deck, controllers, Game Paks, Rumble Paks, and Controller Paks. Retrieved from "[https://n64brew.dev/wiki/Cleaning\_Kit?oldid=250](https://n64brew.dev/wiki/Cleaning_Kit?oldid=250) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Cleaning Kit - N64brew Wiki Cleaning Kit ============ From N64brew Wiki (Redirected from [NUS-014](https://n64brew.dev/wiki/NUS-014?redirect=no "NUS-014") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-014#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-014#searchInput) Nintendo released a first party cleaning kit for the Nintendo 64. It contains everything required to clean the connectors of the control deck, controllers, Game Paks, Rumble Paks, and Controller Paks. Retrieved from "[https://n64brew.dev/wiki/Cleaning\_Kit?oldid=250](https://n64brew.dev/wiki/Cleaning_Kit?oldid=250) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # VRU - N64brew Wiki VRU === From N64brew Wiki (Redirected from [NUS-026](https://n64brew.dev/wiki/NUS-026?redirect=no "NUS-026") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-026#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-026#searchInput) Redirect to: * [Voice Recognition Unit](https://n64brew.dev/wiki/Voice_Recognition_Unit "Voice Recognition Unit") Retrieved from "[https://n64brew.dev/wiki/VRU?oldid=4352](https://n64brew.dev/wiki/VRU?oldid=4352) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Controller Pak - N64brew Wiki Controller Pak ============== From N64brew Wiki (Redirected from [NUS-004](https://n64brew.dev/wiki/NUS-004?redirect=no "NUS-004") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-004#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-004#searchInput) [![](https://upload.wikimedia.org/wikipedia/commons/thumb/9/9c/Nintendo-64-Controller-Pak.jpg/960px-Nintendo-64-Controller-Pak.jpg)](https://n64brew.dev/wiki/File:Nintendo-64-Controller-Pak.jpg) The Controller Pak is a memory card accessory, similar to memory cards used by the PlayStation consoles. Certain games allow saving of game files to the Controller Pak. Just like the [Rumble](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak") and [Transfer Paks](https://n64brew.dev/wiki/Transfer_Pak "Transfer Pak") , it plugs into the bottom of the Nintendo 64 [controller](https://n64brew.dev/wiki/Controller "Controller") . Some games are unable to save game files without the pak, however the games are still playable. The Controller Pak is also useful for carrying game saves between consoles and exchanging saves between multiple paks using a game's save manager. Original Controller Paks by Nintendo have a total of 32 KiB of memory available for saves, and use a custom (FAT-inspired) filesystem to allocate data from different games, implemented in software by the Nintendo SDK. Some third-party pak (such as by Datel) feature more memory (eg. 256 KiB or 512 KiB), via a bank switching system. This system was supported by the official Nintendo SDK, making such paks work transparently on all commercial games, even though Nintendo itself did not produce or sold any multi-bank controller pak. Contents -------- * [1 Hardware](https://n64brew.dev/wiki/NUS-004#Hardware) * [1.1 Components](https://n64brew.dev/wiki/NUS-004#Components) * [1.2 Operation](https://n64brew.dev/wiki/NUS-004#Operation) * [2 Software](https://n64brew.dev/wiki/NUS-004#Software) * [3 File System](https://n64brew.dev/wiki/NUS-004#File_System) Hardware -------- ### Components The primary component that makes up the Controller Pak is a 28pin, 32,768-byte [SRAM](https://en.wikipedia.org/wiki/Static_random-access_memory "wikipedia:Static random-access memory") chip, backed by a 3.3V battery located on the opposite side of the PCB. The battery supplies a low current to the SRAM when either not plugged into a controller or when the console is turned off, preventing the SRAM from losing data. While the controller's accessory port supports a 16 bit address, the Controller Pak leaves the 16th bit disconnected, as the SRAM only uses 15 bits for addressing. At least some, if not all, of the official Controller Paks used the [LH52256CVN](https://n64brew.dev/wiki/File:LH52256CVN.pdf "File:LH52256CVN.pdf") SRAM chip manufactured by Sharp. Alternative SRAM chips do exist. Any chip that follows the same pinout and runs on 3.3V should be compatible. One example is the AS6C62256-55PCN made by Alliance Memory. While not produced anymore, Toshiba's TC55257DFL-85V and Chiplus' CS18LV02563 are also compatible. ### Operation The pins of the SRAM chip are mapped 1 to 1 with the [accessory port's pinout](https://n64brew.dev/wiki/Controller#Accessory_Port "Controller") , except for pin A15 (the 16th address bit). On OEM paks (official ones produced by Nintendo), pin A15 is connected to pin CE2 of the SRAM chip (Chip Enable 2), causing the SRAM to go into an idle mode when asserted. On some third-party paks, instead, pin A15 is completely disconnected. [![](https://static.wikitide.net/n64wiki/thumb/2/28/20220327_205835.jpg/600px-20220327_205835.jpg)](https://n64brew.dev/wiki/File:20220327_205835.jpg) Example of a third-party (clone) controller pak where pin A15 (third from right) is disconnected The Chip Enable pin is inverted on the PCB using a transistor, meaning that the pin must be pulled HIGH to enable the chip. Write Enable and Output Enable should be pulled LOW to enable their respective function. Refer to the SRAM's datasheet for how to read/write data. [![](https://static.wikitide.net/n64wiki/6/6f/AccessoryPortPinout.png)](https://n64brew.dev/wiki/File:AccessoryPortPinout.png) Pinout of the accessory port connector with respect to the pak's orientation Software -------- When accessing the Controller Pak via the [joybus](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") protocol, the accessory presents the following memory map: | | | | --- | --- |OEM (official) Controller Pak memory map | Address | Description | | 0x0000 - 0x7FFF | SRAM: actual contents of the pak | | 0x8000 - 0xFFFF | Writes are ignored, reads return 0 | The Nintendo SDK (libultra) is designed to support a bank switching system, even though only third-party companies (like Datel) ever produced such paks. This is the memory map: | | | | --- | --- |Multi-bank (eg: Datel) Controller Pak memory map | Address | Description | | 0x0000 - 0x7FFF | Read/write current bank (32 KiB) | | 0x8000 - 0xFFFF | Bank-switching: the LSBs of the written word set the bank number.

If the bank number does not exist, the behavior is not fully investigated but it appears to just mirror. Eg: for a 4-bank cpak, writing 5 selects bank 1. Reads return 0. | There is no provision to read the number of available banks. Software is required to either ask that to the user, or perform a discovery process (eg: switching banks and checking contents until the first bank is seen again). Notice that this memory map is also compatible with the original one; in fact, ignoring the writes in range 0x8000-0xFFFF can be interpreted as "always switching to bank 0 which is the only available bank". Some clone Controller Paks instead are designed with A15 pin disconnected, exposing the following memory map: | | | | --- | --- |Third-party (clone) Controller Pak memory map | Address | Description | | 0x0000 - 0x7FFF | SRAM: actual contents of the pak | | 0x8000 - 0xFFFF | Mirror of the contents | Notice that this memory map is actually **not** compatible with the bank switching system. In fact, the Nintendo SDK will performa a bank switch by writing the bank number to 0x8000, but this will cause the bank number to be treated as data and written at 0x0000. Luckily, the first block (called "label area") is unused in the [standard filesystem](https://n64brew.dev/wiki/Controller_Pak/Filesystem "Controller Pak/Filesystem") which might explain why the designers of these clone PCBs never realized the mistake, File System ----------- Main article: [Controller Pak/Filesystem](https://n64brew.dev/wiki/Controller_Pak/Filesystem "Controller Pak/Filesystem") Save data management on the Controller Pak is facilitated through a simple proprietary filesystem. In all official and unofficial Controller Paks, there are 32,768 bytes of SRAM available, which is split into 256-byte sectors referred to as "pages". This allows for a maximum capacity of 128 pages, 5 of which are reserved for the filesystem data, and the remaining 123 for user software. Retrieved from "[https://n64brew.dev/wiki/Controller\_Pak?oldid=5635](https://n64brew.dev/wiki/Controller_Pak?oldid=5635) " [Categories](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") * [Paks](https://n64brew.dev/wiki/Category:Paks "Category:Paks") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Controller - N64brew Wiki Controller ========== From N64brew Wiki (Redirected from [NUS-005](https://n64brew.dev/wiki/NUS-005?redirect=no "NUS-005") ) [Jump to navigation](https://n64brew.dev/wiki/NUS-005#mw-head) [Jump to search](https://n64brew.dev/wiki/NUS-005#searchInput) [![](https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/N64-Controller-Gray.jpg/960px-N64-Controller-Gray.jpg)](https://n64brew.dev/wiki/File:N64-Controller-Gray.jpg) The N64 controller, with it's tri-wing design, featured 14 button inputs, and an optical encoder based analog stick. The controller connects to the console over three wires, power, data, and ground. On the bottom, it also had a 32 pin edge board connector for additional accessory Paks, such as the [Controller Pak](https://n64brew.dev/wiki/Controller_Pak "Controller Pak") and [Rumble Pak](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak") . Contents -------- * [1 Design](https://n64brew.dev/wiki/NUS-005#Design) * [2 Operation](https://n64brew.dev/wiki/NUS-005#Operation) * [2.1 Analog Stick](https://n64brew.dev/wiki/NUS-005#Analog_Stick) * [2.2 Accessory Port](https://n64brew.dev/wiki/NUS-005#Accessory_Port) * [2.2.1 Pak Detection](https://n64brew.dev/wiki/NUS-005#Pak_Detection) * [3 Homebrew Controllers](https://n64brew.dev/wiki/NUS-005#Homebrew_Controllers) Design ------ The controller's design was unique in that it had three handles, which allowed the player to hold the controller in multiple ways. However, typically the right hand would be on the right-most handle, and the left would alternate between the middle or left depending on whether the game utilized the D-pad or analog stick. The standard controller used rubber-like contacts underneath plastic button caps. When pressed, these contacts would close a circuit printed onto the PCB inside the controller; this is different from a clicky push button, but not uncommon for game controllers. Operation --------- The controller is managed by a single IC chip that works as an interface between the console and the other hardware components. The chip is proprietary and has a generic label "Nintendo NUS-CNT" plus a model number. There are three small PCBs connected via wires for the L, R, and Z buttons. Eleven pads on the main PCB are used for the other buttons. The analog stick is a self contained module that is connected via a 6-pin, JST-PH compatible connector. A 32-pin port connector is used to interface with different accessories. ### Analog Stick The analog stick uses a pair of optical encoding disks to determine its relative position. This works similar to linear encoders found in printers, except that instead of being a strip of tape/film, the N64 uses a rotating disk for each axis. Each disk has small holes along the circumference of the disk. These holes allow light to pass through to an optical sensor on the opposite side. When the stick is moved, there are two signals generated per axis, for a total of four, which are slightly offset from each other. One of the two signals is used as the interrupt signal (`XA` and `YA` for the x-axis and y-axis respectively), and the other (`XB` and `YB`) is compared with the state of the first in order to determine which direction (positive or negative) that analog stick is moving for that axis. If the stick is moving in the positive direction at a constant speed, the two signals will look similar to this: [![](https://static.wikitide.net/n64wiki/8/8a/Analogstick-xaxb-positive.svg)](https://n64brew.dev/wiki/File:Analogstick-xaxb-positive.svg) If the stick is moving in the negative direction, they will look similar to this: [![](https://static.wikitide.net/n64wiki/b/b8/Analogstick-xaxb-negative.svg)](https://n64brew.dev/wiki/File:Analogstick-xaxb-negative.svg) [![](https://static.wikitide.net/n64wiki/thumb/9/91/Analogstick-pinout-diagram.png/384px-Analogstick-pinout-diagram.png)](https://n64brew.dev/wiki/File:Analogstick-pinout-diagram.png) Analog stick connector pinout To determine the direction along a particular axis and how far the stick has moved, the NUS-CNT chip will listen for any changing edge of that axis' interrupt signal (e.g. `XA` for the x-axis). _A changing edge means any time the signal goes from LOW to HIGH, or HIGH to LOW._ When an edge change is detected, the state of `XA` is compared against `XB`. If `input`, then the stick has moved +1 units. If `input`, then the stick has moved -1 units. While the analog stick reports _relative_ movement whenever it moves, the N64 console requires the current position, not relative. In order to provide that, the NUS-CNT chip keeps track of these movements. The initial values for each axis is zero. _Although not tested/quantified, it may be possible to extrapolate the speed at which the user is moving the stick. The signals produced vary in length between each edge change. The time between edge changes may be able to be used to indicate speed. While speed isn't needed by the console, this could still offer some additional data in a custom controller or interface._ The analog stick has a 6-pin connector: `VCC` is 3.3V. `GND` is common ground. `XA` and `XB` are the signal pins for the x-axis. `YA` and `YB` are for the y-axis. ### Accessory Port [![](https://static.wikitide.net/n64wiki/6/6f/AccessoryPortPinout.png)](https://n64brew.dev/wiki/File:AccessoryPortPinout.png) Pinout of the accessory port connector with respect to the pak's orientation Pak devices could be plugged into this port to expand the console's capabilities or to enhance the player's experience. The console communicates with these devices via the [Joybus Protocol](https://n64brew.dev/wiki/Joybus_Protocol "Joybus Protocol") , however a microchip is used on the controller to interpret the Joybus commands and interface with the paks. #### Pak Detection If the `DETECT` pin is pulled HIGH, then a pak is inserted in the connector. Paks do this by directly connecting the pin to 3.3V. When the console sends the [0x00 Joybus](https://n64brew.dev/wiki/Joybus_Protocol#Additional_Command_Details "Joybus Protocol") command, the controller will return its hardware ID and then one additional byte that tells the console whether a pak is inserted (`0x01`) or not (`0x02`). If a game chooses to do so, it is possible to detect what kind of pak is inserted. _\*WIP Section\*_ Homebrew Controllers -------------------- While most of the official controller uses fairly common connectors and buttons, the port connector and the NUS-CNT chips are proprietary and hard to source. The vast majority of custom controllers use a salvaged NUS-CNT IC chip as the brain, to interface with the console. However, [Bigbass](https://n64brew.dev/wiki/User:Bigbass "User:Bigbass") has developed an open source alternative that utilizes a PIC microcontroller as the brain, and other modern components. More details are on the [project's Github page](https://github.com/bigbass1997/PIC-CNT64) . The [TAStm32](https://github.com/Ownasaurus/TAStm32) is another alternative designed as a replay device for Tool-Assisted Speedruns. The port connector uses an extremely uncommon pitch of 1.5mm, with a board thickness of 1.2mm. No sources are known at this time for purchasing these connectors. Retrieved from "[https://n64brew.dev/wiki/Controller?oldid=1485](https://n64brew.dev/wiki/Controller?oldid=1485) " [Category](https://n64brew.dev/wiki/Special:Categories "Special:Categories") : * [Accessories](https://n64brew.dev/wiki/Category:Accessories "Category:Accessories") Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") --- # Category:Paks - N64brew Wiki [Help](https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Categories) Category:Paks ============= From N64brew Wiki [Jump to navigation](https://n64brew.dev/wiki/Category:Paks#mw-head) [Jump to search](https://n64brew.dev/wiki/Category:Paks#searchInput) Accessories that end in "Pak" Pages in category "Paks" ------------------------ The following 6 pages are in this category, out of 6 total. ### C * [Controller Pak](https://n64brew.dev/wiki/Controller_Pak "Controller Pak") ### E * [Expansion Pak](https://n64brew.dev/wiki/Expansion_Pak "Expansion Pak") ### G * [Game Pak](https://n64brew.dev/wiki/Game_Pak "Game Pak") ### J * [Jumper Pak](https://n64brew.dev/wiki/Jumper_Pak "Jumper Pak") ### R * [Rumble Pak](https://n64brew.dev/wiki/Rumble_Pak "Rumble Pak") ### T * [Transfer Pak](https://n64brew.dev/wiki/Transfer_Pak "Transfer Pak") Retrieved from "[https://n64brew.dev/wiki/Category:Paks?oldid=487](https://n64brew.dev/wiki/Category:Paks?oldid=487) " Cookies help us deliver our services. By using our services, you agree to our use of cookies. [More information](https://meta.miraheze.org/wiki/Special:MyLanguage/Privacy_Policy#2._Cookies) OK Navigation menu --------------- ### Search [](https://n64brew.dev/wiki/Main_Page "Visit the main page") ---