Issue loading NESTEST ROM into memory

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

Post Reply
ToxicNugget
Posts: 7
Joined: Wed Mar 18, 2020 8:00 am

Issue loading NESTEST ROM into memory

Post by ToxicNugget » Fri Aug 21, 2020 7:46 am

Hi, I've been working on an NES emulator in VB.NET in hopes of learning more about hardware and I've had a lot of fun with it so far.
I thought I had implemented the CPU successfully from what I could tell as after loading a few 6502 programs directly they all operate
as expected.

However my next step was being able to load program ROM from a .NES file and execute it, but from what I can tell all the instructions are one address early from a certain point. When trying to load the PC from addresses 0xFFFC and 0xFFFD I ended up in a completely different memory location as the actual high byte gets loaded as the low byte and then the first instruction gets loaded as the high byte. I temporarily set my Reset function to load from 0xFFFB-0xFFFC instead and sure enough everything is one address behind in memory (my PC starts at 0xC004 but it holds the CLD instruction instead of SEI).

I made my emulator give a text log of all the memory addresses and their values so I could try and figure this out more easily, as expected the RAM started being written to from 0x8000 and it all looks correct from the beginning, so it seems at some point one of my instructions/addressing modes don't move forward as many bytes as they should. (Note: I have my BRK as immediate addressing instead of implied because apparently it technically is a 2 byte instruction but the operand is just ignored, I thought this would make it more realistic and help my logs whilst testing be more accurate.)

I've already spent a good two days trying to find the source to this issue but I've had no luck thus far.
I figured that people here who have a lot more experience will know what the memory should be looking like when loading a ROM like the NESTEST one, as I am quite stumped as of now and I can't figure out which instruction is messing me up, especially with no reference as to what the addresses should be looking like. I'll attach the log in this post if anyone would be kind enough to help me figure this one out. Thanks in advance!
log.txt
Output of whole addressable range and each addresses values.
(730.7 KiB) Downloaded 9 times

User avatar
Quietust
Posts: 1597
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Issue loading NESTEST ROM into memory

Post by Quietust » Fri Aug 21, 2020 8:14 am

It sounds like you've got a problem with your array indexes being zero-based versus one-based. It would help to see the code you're using to load ROM images.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

ToxicNugget
Posts: 7
Joined: Wed Mar 18, 2020 8:00 am

Re: Issue loading NESTEST ROM into memory

Post by ToxicNugget » Fri Aug 21, 2020 8:42 am

Okay so my when making a cartridge object I have this as my constructor, it loads the .NES file and separates the header, PRGROM and CHRROM:

Code: Select all

Public Sub New(fileName As String) 'reads our ROM file
        Dim header As New sHeader
        ReDim header.name(4) 'can't give explicit array bounds in structure so have to set them here
        ReDim header.unused(5)

        Dim buffer(Marshal.SizeOf(header)) As Byte 'make a buffer the same length in bytes as our header variable

        Using fs As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None)
            'Read file header
            fs.Read(buffer, 0, buffer.Length) 'the 0 represents the offset of index in our array, so in all cases pretty much this will be 0

            'Put buffer into header
            For i = 0 To 3 'name
                header.name(i) = Convert.ToChar(buffer(i))
            Next
            header.prg_rom_chunks = buffer(4)
            header.chr_rom_chunks = buffer(5)
            header.mapper1 = buffer(6)
            header.mapper2 = buffer(7)
            header.prg_ram_size = buffer(8)
            header.tv_system1 = buffer(9)
            header.tv_system2 = buffer(10)
            Dim j As Integer = 10
            For i = 0 To 4 'unused header bytes
                j += 1
                header.unused(i) = Convert.ToChar(buffer(j))
            Next

            'Skip past trainer data if there is any
            If header.mapper1 And &H4 Then 'if the third from lowest bit is set we have "trainer" data which is used for games that were modified to run on different hardware from the original cartridges like early RAM cartridges and emulators
                fs.Seek(512, SeekOrigin.Current) 'if there is trainer data we skip past it (it's 512 bytes long)
            End If

            'Determine mapper ID
            nMapperID = ((header.mapper2 >> 4) And &HF0) Or (header.mapper1 >> 4)

            'Find out which file format we are working with
            Dim nFileType As Byte = 1
            If nFileType = 0 Then
                'Will fill in later
            ElseIf nFileType = 1 Then
                nPRGBanks = header.prg_rom_chunks
                ReDim vPRGMemory(nPRGBanks * 16384) 'each program bank will be 16KB long
                fs.Read(vPRGMemory, 0, vPRGMemory.Length) 'read the data into our program memory

                nCHRBanks = header.chr_rom_chunks
                ReDim vCHRMemory(nCHRBanks * 8192) 'each character/pattern bank will be 8KB long
                fs.Read(vCHRMemory, 0, vCHRMemory.Length) 'read the data into our character memory
            ElseIf nFileType = 2 Then
                'Will fill in later
            End If

            'Load appopriate mapper
            Select Case nMapperID
                Case 0
                    mapper = New Mapper_000(nPRGBanks, nCHRBanks)
            End Select
        End Using
    End Sub
So after this my vPRGMemory and vCHRMemory arrays are populated. Upon trying to read from the bus, it points to this function in my cartridge class:

Code: Select all

Public Function cpuRead(addr As UInt16, ByRef data As Byte) As Boolean 'using ByRef otherwise the data being sent won't actually be modified, the return values are just booleans
        Dim mapped_addr As UInt32 = 0

        If mapper.cpuMapRead(addr, mapped_addr) Then 'returns true if the information needs to come from the cartridge, the mapper functions use ByRef meaning this actual variable will get modified accordingly
            data = vPRGMemory(mapped_addr) 'access the corresponding data directly from PRGROM
            Return True
        End If

        Return False
    End Function
As you can see I use another read function that is specific to the 000 mapper used in the NESTEST ROM:

Code: Select all

Public Overrides Function cpuMapRead(addr As UShort, ByRef mapped_addr As UInteger) As Boolean
        If addr >= &H8000 And addr <= &HFFFF Then 'this means that the address is within the cartridge area
            mapped_addr = addr And If(nPRGBanks > 1, &H7FFF, &H3FFF)

            Return True
        End If

        Return MyBase.cpuMapRead(addr, mapped_addr) 'this will return the base class's default 'false'
    End Function
So this is all the code that sets and retrieves the PRGROM when needed. Does this help? Should I change the indexes of my ROM arrays by +1?

User avatar
Quietust
Posts: 1597
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Issue loading NESTEST ROM into memory

Post by Quietust » Fri Aug 21, 2020 9:30 am

ToxicNugget wrote:
Fri Aug 21, 2020 8:42 am
Okay so my when making a cartridge object I have this as my constructor, it loads the .NES file and separates the header, PRGROM and CHRROM:

Code: Select all

Public Sub New(fileName As String) 'reads our ROM file
        Dim header As New sHeader
        ReDim header.name(4) 'can't give explicit array bounds in structure so have to set them here
        ReDim header.unused(5)

        Dim buffer(Marshal.SizeOf(header)) As Byte 'make a buffer the same length in bytes as our header variable

        Using fs As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None)
            'Read file header
            fs.Read(buffer, 0, buffer.Length) 'the 0 represents the offset of index in our array, so in all cases pretty much this will be 0
What exactly is the definition of "sHeader"? It sounds like it might actually be 17 bytes long, rather than the 16 you're expecting.
ToxicNugget wrote:
Fri Aug 21, 2020 8:42 am

Code: Select all

            nMapperID = ((header.mapper2 >> 4) And &HF0) Or (header.mapper1 >> 4)
Also, that line seems to be rather mixed up - it should be "(header.mapper2 And &HF0) Or (header.mapper1 >> 4)" (possibly with an extra "And &H0F" at the end, in case header.mapper1 is being treated as a signed 8-bit integer).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

ToxicNugget
Posts: 7
Joined: Wed Mar 18, 2020 8:00 am

Re: Issue loading NESTEST ROM into memory

Post by ToxicNugget » Fri Aug 21, 2020 10:01 am

Ah yes, so my sHeader structure is as follows:

Code: Select all

Private Structure sHeader
        Public name() As Char
        Public prg_rom_chunks As Byte
        Public chr_rom_chunks As Byte
        Public mapper1 As Byte
        Public mapper2 As Byte
        Public prg_ram_size As Byte
        Public tv_system1 As Byte
        Public tv_system2 As Byte
        Public unused() As Char 'unused bytes
    End Structure
When the name and unused arrays are redimmed in New() it becomes name(4) and unused (5). Should I be redimming header.name as header.name(3) instead since the name is 4 bytes? I also think I should minus one away from the Marshal.SizeOf(header) since it returns 16 which will make the buffer 17 bytes long in result.

As for the nMapperID line I don't know what I was thinking there but I corrected it now.

ToxicNugget
Posts: 7
Joined: Wed Mar 18, 2020 8:00 am

Re: Issue loading NESTEST ROM into memory

Post by ToxicNugget » Fri Aug 21, 2020 11:12 am

Update: I just tried fixing the cartridge class by resizing the buffer as I talked about above, it was one byte too long and after resizing it and putting the reset address back to its original position it works great. Thanks for your help!

Post Reply