NV1 VRAM structure and usage


NV1 cards can have 1MB, 2MB, or 4MB of memory. To learn the size of VRAM, read the PFB.VRAM_CONFIG register. The memory is handled by the PFB unit. It is used for several purposes. While the main function of VRAM is storage of pixel data to display, it is also used to contain several control structures for various units of the card.

VRAM can be accessed by the following:

  • the host, through FB map area
  • PFB/PDAC scanout hardware (at any address, as selected by nv1-pfb-start).
  • PGRAPH rendering, as managed by PFB
  • PRM, for:
    • the VGA memory area, covering addresses 0x00000:0x40000, which is in turn accessed by:
      • host access through PRMFB to the VGA memory area
      • reading VGA memory area for VGA rendering
    • writing VGA shadow scanout framebuffer for VGA rendering - starting at address 0x40000
    • writing ALOG entries - at addresses 0xe0000:0xf0000
    • VGA and DOS audio registers backing storage - at addresses 0xf0000:0xf0600
  • PAUDIO for sound data storage - at any address, as selected by the audio object descriptor.
  • PFIFO for password storage - at addresses 0x18000:0x18800 (???)
  • the RAMIN area [see below]


wtf is the password storage thing, and why is it located at an inconvenient and unmovable place?

Overall, VRAM addressing is very inflexible.

VRAM is mapped straight to MMIO area:

8-bit space nv1-fb [0x1000000]
nv1-mmio 0x1000000: FB

Address range mapped straight to VRAM. Can be accessed in arbitrarily-sized units.

The PGRAPH framebuffer

There are two major modes to choose from for PGRAPH:

  • Single buffer mode. All of VRAM is treated as one big framebuffer.
  • Double buffer mode. VRAM is split into two equal-sized halves, buffer 0 and buffer 1.

This choice also determines how RAMIN is addressed, and thus cannot be easily changed once it is in use.

The framebuffer also has configurable width (which can only be 576, 640, 800, 1024, 1152, 1280, 1600, or 1856 pixels) and pixel size (which can be 1, 2, or 4 bytes). The lines are tightly packed, and the whole thing always starts at address 0 of VRAM (or half of VRAM, for double buffer mode). Thus, address of a pixel can be calculated as follows:

def pixel_address(buf, x, y):
    width = [576, 640, 800, 1024, 1152, 1280, 1600, 1856][PFB.CONFIG.CANVAS_WIDTH]
    bytes = [1, 1, 2, 4][PFB.CONFIG.BPP]
    addr = (x & 0xfff) * bytes + (y & 0xfff) * width * bytes
    vram_size = [0x100000, 0x200000, 0x400000][PFB.VRAM_CONFIG.VRAM_SIZE]
        addr %= vram_size // 2
        addr += (vram_size // 2) * buf
        addr %= vram_size
    return addr


verify you cannot go between the two buffers by overflowing Y

All of that configuration is stored in nv1-pfb-config register.


No verification is done on X and Y coordinates received from PGRAPH - X coordinates larger than framebuffer width will silently overflow into the next line(s), Y coordinates too large to fill into the buffer will wrap to the beginning. To avoid that, as well as hitting other VRAM areas, PGRAPH canvas clipping registers should be set properly.


It’s impossible to have PGRAPH rendering and PFB/PDAC scanout use different bpp, since they share the bpp configuration register. Having PGRAPH and PFB/PDAC use different resolution is possible, but not particularly useful if the rendered data is supposed to ever be displayed.


RAMIN (aka instance memory) is a special area of VRAM, used to store various control structures. It uses different addressing than other parts of VRAM - it effectively grows from the end of VRAM, or from the end of both halves of VRAM in double buffer mode (in an interleaved fashion). There is no hardware register that stores the bounduary between “normal” VRAM and RAMIN - the areas as understood by the hardware actually overlap and it’s the driver’s responsibility to make sure the same chunk of VRAM isn’t used as both framebuffer and RAMIN. RAMIN addressing covers the last 1MB of VRAM (or last 0.5MB of each buffer in double buffer mode). RAMIN addresses correspond to VRAM addresses as follows:

def ramin_to_vram(addr):
    vram_size = [0x100000, 0x200000, 0x400000][PFB.VRAM_CONFIG.VRAM_SIZE]
    # In single buffer mode, just flip all bits of address, except the low 2
    # - this effectively means that RAMIN is split into 32-bit words, which
    # are stored starting at the end of VRAM, in reverse.
    addr ^= ~4
        # In double buffer mode, additionally switch between the two VRAM
        # halves every 0x100 bytes, starting from buffer 1.
        buf = (addr >> 8) & 1)
        addr = (addr & 0xff) | (addr >> 1 & ~0xff)
        addr %= vram_size // 2
        addr += (vram_size // 2) * buf
        addr %= vram_size
    return addr

RAMIN is split into several subareas:

  • RAMHT - PFIFO Hash Table, used by PFIFO to store PGRAPH objects and their handles [see RAMHT]
  • RAMRO - PFIFO RunOut area, used by PFIFO to send naughty FIFO accesses to [see RAMRO]
  • RAMFC - PFIFO Context, used by PFIFO to store context for currently inactive channels [see RAMFC]
  • RAMAU - unknown 0xc00-byte long area, used by PAUDIO.
  • UNK2 - unknown 0x400-byte long area.
  • RAMIN proper - PDMA and PAUDIO INstance memory, used to store DMA objects and audio objects.


figure out what RAMAU nad UNK2 are for

Of the above areas, the first 5 have fixed address and size, selected from 4 possible layout options by software. DMA objects, however, can be located anywhere in RAMIN - including space taken up by one of the other areas, but that’s not a particularly good idea. For the fixed areas, the layout is selected by PRAM.CONFIG register:

8-bit space nv1-pram [0x1000]
nv1-mmio 0x602000: PRAM
Address Name Description
0x200 CONFIG selects RAMIN fixed area layout and size
reg32 nv1-pram-config
nv1-pram 0x200: CONFIG

Selects RAMIN fixed areas layout, one of:

  • 0: 0x1000-byte RAMHT, 0x800-byte RAMRO and RAMFC
  • 1: 0x2000-byte RAMHT, 0x1000-byte RAMRO and RAMFC
  • 2: 0x4000-byte RAMHT, 0x2000-byte RAMRO and RAMFC, buggy
  • 3: 0x8000-byte RAMHT, 0x4000-byte RAMRO and RAMFC

The addresses of fixed RAMIN areas for various configurations are:

CONFIG 0 1 2 3
RAMHT 0x00000 0x00000 0x00000 0x00000
RAMRO 0x01000 0x02000 0x02000 0x08000
RAMFC 0x01800 0x03000 0x06000 0x0c000
RAMAU 0x02000 0x04000 0x08000 0x10000
UNK2 0x02c00 0x04c00 0x08c00 0x10c00
[end] 0x03000 0x05000 0x09000 0x11000

Due to a hardware bug, RAMFC location conflicts with RAMHT for CONFIG=2, effectively making it unusable.

RAMIN access areas

The MMIO ranges that are mapped to RAMIN areas are:

8-bit space nv1-pramht [0x8000]
nv1-mmio 0x640000: PRAMHT

Mapped to RAMHT area

8-bit space nv1-pramfc [0x4000]
nv1-mmio 0x648000: PRAMFC

Mapped to RAMFC area

8-bit space nv1-pramro [0x4000]
nv1-mmio 0x650000: PRAMRO

Mapped to RAMRO area

8-bit space nv1-pramau [0x1000]
nv1-mmio 0x604000: PRAMAU

Mapped to RAMAU area

8-bit space nv1-pramunk2 [0x1000]
nv1-mmio 0x606000: PRAMUNK2

Mapped to UNK2 area

8-bit space nv1-pramin [0x100000]
nv1-mmio 0x700000: PRAMIN

Mapped to RAMIN area

If any of the above MMIO areas happens to be larger than the underlying VRAM area it is mapped to, higher addresses will wrap over to the beginning of that area, except RAMAU, where higher addresses will go to UNK2.