Difference between revisions of "NEO mapper/Extension2"

From MSX Game Library

< NEO mapper

(→‎Detection)
Line 68: Line 68:
 
** otherwise: bank switching register access, as defined in the [[NEO mapper]] specification.
 
** otherwise: bank switching register access, as defined in the [[NEO mapper]] specification.
 
* '''Bank in any other mode''': the write is handled by the function, as defined in its description (written to RAM, ignored for read-only functions, etc.). No register access or function selection is decoded through this bank.
 
* '''Bank in any other mode''': the write is handled by the function, as defined in its description (written to RAM, ignored for read-only functions, etc.). No register access or function selection is decoded through this bank.
 +
 +
Notes:
 +
* A bank switching register can be written while its bank is not in NEO mapper mode. The register is updated but has no visible effect until the bank returns to function 0x00.
 +
* Selecting function 0x00 returns the bank to the ROM segment last selected for it.
  
 
==== Read access ====
 
==== Read access ====
  
 
Read accesses return the content provided by the function of the bank that contains the address. As in the base specification, reads in page 3 are undefined.
 
Read accesses return the content provided by the function of the bank that contains the address. As in the base specification, reads in page 3 are undefined.
−
 
−
==== Function state ====
 
−
 
−
* A bank switching register can be written while its bank is not in NEO mapper mode. The register is updated but has no visible effect until the bank returns to function 0x00.
 
−
* Selecting function 0x00 returns the bank to the ROM segment last selected for it.
 
  
 
==== Limitations ====
 
==== Limitations ====

Revision as of 00:09, 1 November 2026

⚠️ This page is still in the proposal stage and should not yet be considered an official specification.

Proposal for an extension to the NEO mapper format.

Specifications

Principles

The extension adds a set of functions that can be assigned to each bank of the NEO mapper. The function of a bank defines what this bank gives access to (ROM through the mapper, cartridge information, RAM, SRAM, sound chip, etc.).

  • At reset, every bank is assigned function 0x00 (NEO mapper): the base NEO mapper behavior is unchanged as long as no other function is selected.
  • Functions are selected by writing to the extension area of a bank that is currently in NEO mapper mode (see Write decoding).
  • The bank that receives the write (given by the address) and the bank that is configured (given by the Bank field) are independent: a bank can be configured from any other bank that is still in NEO mapper mode.
  • Functions 0x00 (NEO mapper) and 0x01 (Cartridge information) are mandatory. All others are optional; function 0x01 tells which ones are supported by the cartridge.
  • Function IDs are assigned by this specification only. Unassigned IDs are reserved and must not be used by implementations.

Function selection

Within a bank in NEO mapper mode, any write with address bits 11-13 set to 0 is a function selection. In that case, the bank switching registers are not modified.

ADDRESS
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Page 0 0 0 Bank 0 0 0 0 Function
DATA
7 6 5 4 3 2 1 0
Parameter
  • Page (0~3): ignored by the mapper, as for the bank switching registers.
  • Bank (0~7): destination bank to configure. Values above the last bank (5 for NEO-8, 2 for NEO-16) are ignored.
  • Function (0~15): ID of the function to assign to the destination bank.
  • Parameter: data byte passed to the function. Its meaning is defined by each function; functions without parameter expect 0.
  • Address bits 4~7 are reserved and must be 0. Writes with a non-zero value in these bits are ignored.
  • Selecting an unsupported function, or a bank that doesn't exist, is ignored: the destination bank keeps its current function.

The function selection address is therefore:

Address = Page × 4000h + Bank × 100h + Function

Function selection is done with an 8-bit write. Exceptions are described in the related function.

Write decoding

Each write to the cartridge is handled according to the function of the bank that contains the address (address >> 13 for NEO-8, address >> 14 for NEO-16):

  • Bank in NEO mapper mode (function 0x00):
    • address bits 11~13 = 0: function selection (see above);
    • otherwise: bank switching register access, as defined in the NEO mapper specification.
  • Bank in any other mode: the write is handled by the function, as defined in its description (written to RAM, ignored for read-only functions, etc.). No register access or function selection is decoded through this bank.

Notes:

  • A bank switching register can be written while its bank is not in NEO mapper mode. The register is updated but has no visible effect until the bank returns to function 0x00.
  • Selecting function 0x00 returns the bank to the ROM segment last selected for it.

Read access

Read accesses return the content provided by the function of the bank that contains the address. As in the base specification, reads in page 3 are undefined.

Limitations

Because register access and function selection are only decoded through banks in NEO mapper mode, a program must always keep at least one bank in this mode that contains the extension area:

  • NEO-16: any bank (0, 1 or 2). Each bank contains the extension area and a mirror of all bank switching registers.
  • NEO-8: an even-numbered bank (0, 2 or 4). Only even-numbered banks contain the extension area and the registers of banks 0 and 1. Odd-numbered banks (1, 3, 5) only contain the registers of banks 2 to 5.

Functions

0x00: NEO mapper (mandatory)

Activate access to the cartridge ROM mapper through the given bank (default mode)

Parameter:

7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0

0x01: Cartridge information (mandatory)

Activate read access to the cartridge information data through the given bank (256 bytes mirrored)

Parameter:

7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0

Information format:

Offset Size Description Content
7 6 5 4 3 2 1 0
0 3 Signature "NEO"
3 1 NEO-mapper version Major: 1 Minor: 3
4 1 Flag
  • S: Bank size (0=8KB, 1=16KB)
0 0 0 0 0 0 0 S
5 1 Reserved 0
6 2 Number of segments 4~16384
8 8 Reserved 0
16 1 Function 0 support (ROM) 1
17 1 Function 1 support (Info) 1
18 1 Function 2 support >0 if supported
19 1 Function 3 support >0 if supported
20 1 Function 4 support (RAM) >0 if supported
21 1 Function 5 support >0 if supported
22 1 Function 6 support (SRAM) >0 if supported
23 1 Function 7 support >0 if supported
24 1 Function 8 support (SCC) >0 if supported
25 1 Function 9 support (SP-OPL4) >0 if supported
26 1 Function 10 support >0 if supported
27 1 Function 11 support >0 if supported
28 1 Function 12 support >0 if supported
29 1 Function 13 support >0 if supported
30 1 Function 14 support >0 if supported
31 1 Function 15 support >0 if supported
32 224 Reserved 0

0x02: Reserved

Reserved for possible future use to control the NEO cartridge from within the program.

0x03: Reserved

0x04: RAM

Activate read/write access to RAM through the given bank

Parameter:

7 6 5 4 3 2 1 0
RAM segment

0x05: Reserved

Reserved for possible future use of a 16-bit mapper for RAM.

0x06: SRAM/FRAM

Activate read/write access to SRAM/FRAM through the given bank (8 KB mirrored on NEO-16)

Parameter:

7 6 5 4 3 2 1 0
0 0 0 0 Segment

0x07: Reserved

0x08: SCC/SCC+

Activate SCC to be accessed through memory area from 9800h to 9FFFh (4 KB mirrored) Any bank number is valid, but bank #4 is recommanded for NEO-8 and #2 for NEO-16 to use standard address for

7 6 5 4 3 2 1 0
C 0 0 0 0 0 0 0
  • C: 0=SCC, 1=SCC+

0x09: SP-OPL4

Work in progress...

0x0A: Reserved

0x0B: Reserved

0x0C: Reserved

0x0D: Reserved

0x0E: Reserved

0x0F: Reserved

Detection

A NEO mapper without the extension ignores function selection writes (address bits 11~13 = 0 is not a valid register address). A program can therefore detect the extension by selecting function 0x01 on a bank in NEO mapper mode and checking for the "NEO" signature at the beginning of this bank, then selecting function 0x00 to restore the ROM access.

All the information below is read from the cartridge information block, while function 0x01 is selected on the bank.

Version

The version of the NEO mapper and of its extension system is given by the byte at offset 3 of the information block (major version in bits 4~7, minor version in bits 0~3; e.g. 0x13 for version 1.3).

A program that requires a specific version compares this byte with the required one: the cartridge is compatible if its version is greater than or equal to the required version.

Example for a NEO-16 with program in page 1:

; Get the NEO mapper version
; Input:
; Output: A=Version
NEO_GetVersion:
  xor   a
  ld    (0x4201),a ; Select Information function in bank 2
  ld    a,(0x8003) ; Read version
  ld    hl,0x4200
  ld    (hl),0     ; Restore NEO mapper function in bank 2
  ret

Function support

The support of each function n (0~15) is given by the byte at offset 16+n of the information block:

  • 0: the function is not supported;
  • any other value: the function is supported. The meaning of the value (size, options, etc.) is defined in the description of each function.

Functions 0x00 and 0x01 are mandatory, so bytes 16 and 17 are always non-zero.

Example for a NEO-16 with program in page 1:

; Check if the given function is supported
; Input: A=Function ID
; Output: A=Support
NEO_GetFuncSupport:
  ld    hl,0x4201
  ld    (hl),0     ; Select Information function in bank 2
  add   a,16
  ld    b,0x80
  ld    c,a
  ld    a,(bc)     ; Read function support at 0x8010+Function
  ld    hl,0x4200
  ld    (hl),0     ; Restore NEO mapper function in bank 2
  ret

Appendix

Programmable processor and emulation

Here is a pseudo code in C to handle NEO mappers extension for programmable processor based cartridges or for emulators:

NEO-16 mapper:

#define BANK_NUM				3
#define BANK_SIZE				(16 * 1024)
#define ROM_SEG_NUM				1024
#define ROM_SEG_MASK			(ROM_SEG_NUM - 1)
#define ROM_SIZE				(ROM_SEG_NUM * BANK_SIZE)
#define FUNC_NUM				16

// Extended function prototypes
typedef void (*funcSelect_t)(uint8, uint8);
typedef void (*funcWrite_t)(uint16, uint8);
typedef uint8 (*funcRead_t)(uint16);

// Extended functions tables
funcSelect_t funcSelectTable[FUNC_NUM];
funcWrite_t funcWriteTable[BANK_NUM];
funcRead_t funcReadTable[BANK_NUM];

// Supported extended function initialization
void initMapper();
void initInfo();
void initRAM();
void initSRAM();

//=============================================================================
// NEO-16 Interface
//=============================================================================

// Handle device initialization
void NEO16_Init()
{
	// Mandatory functions
	initMapper();
	initInfo();
	// Optional functions
	initRAM();
	initSRAM();
}

// Handle write access
void NEO16_Write(uint16 address, uint8 data)
{
	uint8 bank = ((address >> 12) & 0x03) - 1; // Get the corresponding bank number
	if (bank < BANK_NUM)
	{
		funcWriteTable[bank](address, data);
	}
	else // Extended function
	{
		uint8 funcId = address & 0x0F; // Get function ID
		bank = (address >> 8) & 0x07; // Get destination bank
		if ((bank < BANK_NUM) && (funcSelectTable[funcId] != NULL)) // Is function supported?
			funcSelectTable[funcId](bank, data); // Initialize function specific code
	}
}

// Handle read access
uint8 NEO16_Read(uint16 address)
{
	uint8 bank = address >> 14; // Get the corresponding bank number
	if (bank < BANK_NUM)
		return funcReadTable[bank](address);

	return 0xFF; // skip
}

//=============================================================================
// Function 0x00 - ROM Mapper
//=============================================================================

// ROM data
extern const uint8* romData; 

// Bank switching register value
uint16 romSegment[BANK_NUM];

void writeMapper(uint16 address, uint8 data)
{
	if (address & 1) // Set bank register MSB
		romSegment[bank] = ((data & 0x0F) << 8) | (romSegment[bank] & 0x00FF);
	else // Set bank register LSB
		romSegment[bank] = (romSegment[bank] & 0xFF00) | (data);
}

uint8 readMapper(uint16 address)
{
	return romData[((romSegment[bank]) << 14) + (address & 0x3FFF)];
}

void selectMapper(uint8 bank, uint8 data)
{
	funcWriteTable[bank] = writeMapper;
	funcReadTable[bank] = readMapper;		
}

void initMapper()
{
	for (uint8 i = 0; i < BANK_NUM; i++)
	{
		romSegment[i] = 0x0000;
		selectMapper(i, 0);		
	}
	funcSelectTable[0] = selectMapper;
}
	
//=============================================================================
// Function 0x01 - Cartridge Information
//=============================================================================

struct info
{
	char	Sign[3];
	uint8	Version;
	uint8	Flag;
	uint8	_reserved1;
	uint16	SegmentNum;
	uint8	_reserved2[8];
	uint8	Functions[FUNC_NUM]; // Must be different from 0 for supported functions 
};

const struct info infoData = 
{
	{ 'N', 'E', 'O' },
	0x13, // Version 1.3
	0x01, // NEO-16
	0,
	ROM_SEG_NUM, // 1204 segments (16 MB)
	{ 0, 0, 0, 0, 0, 0, 0, 0 },
	{
		1, // Mapper
		1, // Info
		0,
		32,	// RAM
		32, // SRAM/FRAM
		0,
		0,
		0, // SCC
		0,
		0,
		0,
		0,		
		0,
		0,
		0,
		0,
	},
};

void writeInfo(uint16 address, uint8 data) {}

uint8 readInfo(uint16 address)
{
	uint8 index = address & 0xFF; // mirrored every 256 bytes
	if (index > sizeof(info))
		return 0;
	return ((const uint8*)&infoData)[index]; 
}

void selectInfo(uint8 bank, uint8 data)
{
	funcWriteTable[bank] = writeInfo;
	funcReadTable[bank] = readInfo;		
}

void initInfo()
{
	funcSelectTable[1] = selectInfo;
}

//=============================================================================
// Function 0x03 - RAM
//=============================================================================

#define RAM_SEG_NUM				32
#define RAM_SEG_MASK			(RAM_SEG_NUM - 1)

// RAM data
uint8 ramData[16 * 1024 * RAM_SEG_NUM]; // 32 * 16 KB = 512 KB

// Bank switching register value
uint8 ramSegment[BANK_NUM];

void writeRAM(uint16 address, uint8 data)
{
	ramData[((ramSegment[bank]) << 14) + (address & 0x3FFF)] = data;
}

uint8 readRAM(uint16 address)
{
	return ramData[((ramSegment[bank]) << 14) + (address & 0x3FFF)];
}

void selectRAM(uint8 bank, uint8 data)
{
	ramSegment[bank] = data & RAM_SEG_MASK;
	funcWriteTable[bank] = writeRAM;
	funcReadTable[bank] = readRAM;		
}

void initRAM()
{
	for (uint8 i = 0; i < BANK_NUM; i++)
		ramSegment = 0;
	funcSelectTable[3] = selectRAM;
}

//=============================================================================
// Function 0x04 - SRAM
//=============================================================================

#define SRAM_SEG_NUM			16
#define SRAM_SEG_MASK			(SRAM_SEG_NUM - 1)

// SRAM data
uint8 sramData[8 * 1024 * SRAM_SEG_NUM]; // 16 * 8 KB = 128 KB

// Bank switching register value
uint8 sramSegment[BANK_NUM];

void writeSRAM(uint16 address, uint8 data)
{
	sramData[((sramSegment[bank]) << 13) + (address & 0x1FFF)] = data;
}

uint8 readSRAM(uint16 address)
{
	return sramData[((sramSegment[bank]) << 13) + (address & 0x1FFF)];
}

void selectSRAM(uint8 bank, uint8 data)
{
	sramSegment[bank] = data & SRAM_SEG_MASK;
	funcWriteTable[bank] = writeSRAM;
	funcReadTable[bank] = readSRAM;		
}

void initSRAM()
{
	for (uint8 i = 0; i < BANK_NUM; i++)
		sramSegment = 0;
	funcSelectTable[4] = selectSRAM;
}