Writing homebrew ROM hangs after 2 seconds on hardware
Users browsing this thread: 1 Guest(s)

Hello all,

For perverse reasons, I'm interested in squeezing small graphical effects into the boot code of an N64 rom. The code I have so far sets up the frame buffer and writes some garbage to it producing a pink screen. It works perfectly on Project64 2.2, but when I attempt to run it on real hardware, I see the pink screen for about 2 seconds and then it goes black. I have used extensively code from Peter Lemon's N64 github repo. I am assembling with bass. Here is my source code:

Code:

arch n64.cpu
endian msb
output "@party2016.n64", create
fill 1052672
include "N64.INC"
include "N64_GFX.INC"

origin  $00000000
base    $A4000000

// N64 Header (64 bytes)
db $80                      // Initial PI_BSB_DOM1_LAT_REG Value
db $37                      // Initial PI_BSB_DOM1_PGS_REG Value
db $12                      // Initial PI_BSB_DOM1_PWD_REG Value
db $40                      // Initial PI_BSB_DOM1_PGS_REG Value
dd $0000000F                // Initial clock rate
dd BootcodeStart            // Boot address (normally this is in RDRAM)
dd $00001444                // Release offset
db "CRC1"                   // CRC1
db "CRC2"                   // CRC2
dd $00000000                // Unknown
dd $00000000                // Unknown
db "@PARTY2016          "   // Program title (20 bytes)
dd $00000000                // Unknown
db $00                      // Unknown
db $00                      // Unknown
db $00                      // Unknown
db $00                      // Developer ID code "N" = Nintendo
dw $0000                    // Cartridge ID code
db $00                      // Country code
db $00                      // Unused

BootcodeStart:
// Write 8 to $BFC007FC. This is supposed to stop the N64 from freezing after a
// couple of seconds. Don't ask me why, I just read it on the internet.
   li      r1, 8
   lui     r2, $BFC0
   sw      r1, $07FC (r2)

// Initialize the screen buffer
   ScreenNTSC(320, 240, BPP32, $A0100000)
   
PaintScreen:
   lui     r1, $A010
   lui     r2, $0004
   ori     r2, r2, $B000

DrawLoop:
   sw      r1, $0000 (r1)
   bne     r1, r2, DrawLoop
   addiu   r1, r1, 4

   j       PaintScreen


ScreenNTSC just initializes the screen buffer. It is a macro that is defined in the following way:

Code:

macro ScreenNTSC(width,height, status, origin) {
 lui a0,VI_BASE // A0 = VI Base Register ($A4400000)
 li t0,{status}      // T0 = Status/Control
 sw t0,VI_STATUS(a0) // Store Status/Control To VI Status Register ($A4400000)
 la t0,{origin}      // T0 = Origin (Frame Buffer Origin In Bytes)
 sw t0,VI_ORIGIN(a0) // Store Origin To VI Origin Register ($A4400004)
 lli t0,{width}      // T0 = Width (Frame Buffer Line Width In Pixels)
 sw t0,VI_WIDTH(a0)  // Store Width To VI Width Register ($A4400008)
 lli t0,$200         // T0 = Vertical Interrupt (Interrupt When Current Half-Line $200)
 sw t0,VI_V_INTR(a0) // Store Vertical Interrupt To VI Interrupt Register ($A440000C)
 lli t0,0                    // T0 = Current Vertical Line (Current Half-Line, Sampled Once Per Line = 0)
 sw t0,VI_V_CURRENT_LINE(a0) // Store Current Vertical Line To VI Current Register ($A4400010)
 li t0,$3E52239      // T0 = Video Timing (Start Of Color Burst In Pixels from H-Sync = 3, Vertical Sync Width In Half Lines = 229, Color Burst Width In Pixels = 34, Horizontal Sync Width In Pixels = 57)
 sw t0,VI_TIMING(a0) // Store Video Timing To VI Burst Register ($A4400014)
 lli t0,$20D         // T0 = Vertical Sync (Number Of Half-Lines Per Field = 525)
 sw t0,VI_V_SYNC(a0) // Store Vertical Sync To VI V Sync Register ($A4400018)
 lli t0,$C15         // T0 = Horizontal Sync (5-bit Leap Pattern Used For PAL only = 0, Total Duration Of A Line In 1/4 Pixel = 3093)
 sw t0,VI_H_SYNC(a0) // Store Horizontal Sync To VI H Sync Register ($A440001C)
 li t0,$C150C15           // T0 = Horizontal Sync Leap (Identical To H Sync = 3093, Identical To H Sync = 3093)
 sw t0,VI_H_SYNC_LEAP(a0) // Store Horizontal Sync Leap To VI Leap Register ($A4400020)
 li t0,$6C02EC        // T0 = Horizontal Video (Start Of Active Video In Screen Pixels = 108, End Of Active Video In Screen Pixels = 748)
 sw t0,VI_H_VIDEO(a0) // Store Horizontal Video To VI H Start Register ($A4400024)
 li t0,$2501FF        // T0 = Vertical Video (Start Of Active Video In Screen Half-Lines = 37, End Of Active Video In Screen Half-Lines = 511)
 sw t0,VI_V_VIDEO(a0) // Store Vertical Video To VI V Start Register ($A4400028)
 li t0,$E0204         // T0 = Vertical Burst (Start Of Color Burst Enable In Half-Lines = 14, End Of Color Burst Enable In Half-Lines = 516)
 sw t0,VI_V_BURST(a0) // Store Vertical Burst To VI V Burst Register ($A440002C)
 lli t0,($100*({width}/160)) // T0 = X-Scale (Horizontal Subpixel Offset In 2.10 Format = 0, 1/Horizontal Scale Up Factor In 2.10 Format)
 sw t0,VI_X_SCALE(a0)        // Store X-Scale To VI X Scale Register ($A4400030)
 lli t0,($100*({height}/60)) // T0 = Y-Scale (Vertical Subpixel Offset In 2.10 Format = 0, 1/Vertical Scale Up Factor In 2.10 Format)
 sw t0,VI_Y_SCALE(a0)        // Store Y-Scale To VI Y Scale Register ($A4400034)
}


Are there any N64 hardware gurus who know why this might be freezing after a couple of seconds? I'm sure there is something that the bootcode does which prevents this from happening, but if anyone knows what it is before I spend the rest of the week picking apart the bootcode disassembly I have, it would be immensely helpful.
(This post was last modified: 16-01-2016, 02:09 AM by orbitaldecay.)

(16-01-2016, 02:06 AM)orbitaldecay Wrote:
Code:
​origin  $00000000
base    $A4000000


This base seems odd. Usually the base address is something in RDRAM (0x80000000-0x80400000). 0xA4000000 are where the PI/SI registers are located.

(16-01-2016, 02:06 AM)orbitaldecay Wrote:
Code:
​PaintScreen:
   lui     r1, $A010
   lui     r2, $0004
   ori     r2, r2, $B000
DrawLoop:
   sw      r1, $0000 (r1)
   bne     r1, r2, DrawLoop
   addiu   r1, r1, 4

   j       PaintScreen


Doublecheck this DrawLoop. It is almost equivalent to this in C (see comments):
Code:
​r1 = 0xA0100000;
r2 = 0x0004B000;
do {
  *r1 = r1; // do you really want to write the value of R1 to the address of R1?
  r1 += 4; // technically, this is evaluated after the conditional branch so you'll loop one more time than expected
} while (r1 != r2); // do you really want to loop from 0xA0100000 *UP* to 0x0004B004?

Last potential problem I can see: where is the boot code? Don't you need to include N64_BOOTCODE.bin? Or is that not really needed with your faked out boot code?

By the way, you can use the CEN64 emulator to test more accurately what the hardware will be doing, but it requires the boot code to be inserted for CIC detection.

(16-01-2016, 04:30 AM)queueRAM Wrote: This base seems odd. Usually the base address is something in RDRAM (0x80000000-0x80400000). 0xA4000000 are where the PI/SI registers are located.


Yes, the base is a little strange. What I've written here is boot code, and the boot code gets loaded to 0xA4000040. The ROM header uses 0x40 bytes, and the code starting at 0x40 is what gets loaded, so the base gets set to 0xA4000000.

(16-01-2016, 04:30 AM)queueRAM Wrote:
Doublecheck this DrawLoop. It is almost equivalent to this in C (see comments):


Code:
​r1 = 0xA0100000;
r2 = 0x0004B000;
do {
  *r1 = r1; // do you really want to write the value of R1 to the address of R1?


Yes, it makes a cute gradient effect.

(16-01-2016, 04:30 AM)queueRAM Wrote:
Code:

  r1 += 4; // technically, this is evaluated after the conditional branch so you'll loop one more time than expected


Thanks for pointing this out. Wasn't sure if it was evaluated before or after the branch.

(16-01-2016, 04:30 AM)queueRAM Wrote:
Code:

} while (r1 != r2); // do you really want to loop from 0xA0100000 *UP* to 0x0004B004?


No! You've found my mistake! I should have been setting r2 to 0xA014B000 *facepalm*. I'm surprised this worked in the emulator.

(16-01-2016, 04:30 AM)queueRAM Wrote:
Last potential problem I can see: where is the boot code? Don't you need to include N64_BOOTCODE.bin? Or is that not really needed with your faked out boot code?


Correct. I'm not including N64_BOOTCODE.bin because what I'm writing is a replacement for it.

(16-01-2016, 04:30 AM)queueRAM Wrote:
By the way, you can use the CEN64 emulator to test more accurately what the hardware will be doing, but it requires the boot code to be inserted for CIC detection.


Yeah, I was checking that out earlier but I can't seem to get it to run on my laptop. I've heard many good things though. Thanks so much for your help!

(16-01-2016, 07:25 AM)orbitaldecay Wrote:
(16-01-2016, 04:30 AM)queueRAM Wrote:
This base seems odd. Usually the base address is something in RDRAM (0x80000000-0x80400000). 0xA4000000 are where the PI/SI registers are located.


Yes, the base is a little strange. What I've written here is boot code, and the boot code gets loaded to 0xA4000040. The ROM header uses 0x40 bytes, and the code starting at 0x40 is what gets loaded, so the base gets set to 0xA4000000.


Ah, ok, that makes sense. I'm used to using the boot code from the licensed ROMs, so didn't actually know that the IPL code loaded the boot code to RSP DMEM at 0xa4000040.

(16-01-2016, 07:25 AM)orbitaldecay Wrote:
Yes, it makes a cute gradient effect.


Indeed it does
[Image: l9e8Qfn.png]


(16-01-2016, 07:25 AM)orbitaldecay Wrote:
No! You've found my mistake! I should have been setting r2 to 0xA014B000 *facepalm*. I'm surprised this worked in the emulator.


Glad that was it! Yeah, emulators don't really handle bad accesses well. CEN64 can crash. Project64 and Nemu64 occasionally produce error messages.

(16-01-2016, 07:25 AM)orbitaldecay Wrote:
(16-01-2016, 04:30 AM)queueRAM Wrote:
By the way, you can use the CEN64 emulator to test more accurately what the hardware will be doing, but it requires the boot code to be inserted for CIC detection.


Yeah, I was checking that out earlier but I can't seem to get it to run on my laptop. I've heard many good things though. Thanks so much for your help!


CEN64 is good for checking if and how it might run on the console. However, it is slow, not very configurable, and requires the PIF binary to run. You might be aware of this, but make sure you are getting the version of CEN64 that your processor supports. On Windows you can run CPU-Z to see what instructions are supported. On Linux you can 'cat /proc/cpuinfo' to see what flags are set. If you don't have a newer CPU, you might have to run SSE4 or SSE3 versions instead of AVX.

Hi orbitaldecay,

I am Peter Lemon, I got pointed to this forum thread by a friend.
Glad you are getting into N64 development, & are finding some of my stuff useful =D

orbitaldecay Wrote:
Are there any N64 hardware gurus who know why this might be freezing after a couple of seconds? I'm sure there is something that the bootcode does which prevents this from happening, but if anyone knows what it is before I spend the rest of the week picking apart the bootcode disassembly I have, it would be immensely helpful.

The reason it is crashing on real HW after a couple of seconds, is because there is a CIC protection which uses an IPL2 checksum test, if it fails the N64 will freeze.
The checksum is calculated from the whole of the bootcode, so when you change any code inside that, you need to make sure the checksum will still pass.

If you check my bootcode disassembly here: https://github.com/PeterLemon/N64/blob/master/BOOTCODE/BOOTCODE.asm#L1003
You will see I include a FOOTER.BIN file right at the end...
Nobody knows exactly what this data is or does, but my hypothesis is that maybe changing this data could be used to have the correct checksum required for the IPL2 checksum test to pass, for it to not crash on real HW when using a modified bootcode.

Hope this helps you out =D

Writing homebrew ROM hangs after 2 seconds on hardware
Users browsing this thread: 1 Guest(s)