Address Maps: Difference between revisions

From MAMEDEV Wiki
(Obsolete, redirect to up-to-date doc.)
 
(20 intermediate revisions by 3 users not shown)
Line 1: Line 1:
Address maps define how the address space of a CPU is layed out. This article aims to explain how memory and address space is managed in MAME.
See [https://docs.mamedev.org/techspecs/memory.html]
 
This article is WIP.
 
== Address Spaces ==
 
Currently, MAME supports CPUs with up to three distinct address spaces:
 
# Program space ('''ADDRESS_SPACE_PROGRAM''') is by definition the address space where all code lives. On [http://en.wikipedia.org/wiki/Von_neumann_architecture Von Neumann architecture] CPUs, it is also where data is stored. Most CPUs are Von Neumann architecture systems, and thus comingle code and data in a single address space.
# Data space ('''ADDRESS_SPACE_DATA''') is a separate address space where data is stored for [http://en.wikipedia.org/wiki/Harvard_architecture Harvard architecture] CPUs. An example of a Harvard architecture CPU in MAME is the ADSP2100.
# I/O space ('''ADDRESS_SPACE_IO''') is a third address space for CPUs that have separate I/O operations. For example, the Intel x86 architecture has IN and OUT instructions which are effectively reads and writes to a separate address space. In MAME, these reads and writes are directed to the I/O space.
 
Note that a number of CPU architectures also have internal memory that is in a separate space or domain from the other three. Memory referenced in this way is expected to be maintained internally in the CPU core and is not exposed through the memory system in MAME.
 
== CPUs and Bus Width ==
 
Everyone's probably heard about the "8-bit" Z80 CPU or the "32-bit" 80386 CPU. But where does this notion of "8-bit" and "32-bit" come from? When referring to CPUs, there are three metrics worth considering, all of which might be used to describe a CPU, depending on which sounds better to the marketing department.
 
The first possible metric is the size of the internal arithmetic units in the CPU. For example, the Motorola 68000 CPU can do arithmetic operations on 32-bit numbers internally. Does this make it a 32-bit CPU? Depends on who you ask, though most people would probably say "no", because it is at odds with the other two metrics.
 
The second possible metric is the width of the address bus. When a CPU goes to fetch memory, it has to tell the outside world what address it wishes to access. To do this, it drives some of the pins on the chip to specify in binary the address it wants to access, and then signals a read or a write to actually cause the memory access to occur. The number of pins available on the chip for sending these addresses is referred to as the address bus width, and ultimately controls how much memory the CPU can access. For example, the original Intel 8086 has 20 address pins on it, and could access 2<sup>20</sup> bytes (or 1 MB) of memory; thus, we say it had a 20-bit address bus. When Intel created the 80286, it increased the address bus to 24-bit (16 MB), and then to 32-bit (4 GB) with the introduction of the 80386. Which is one reason why the 80386 is called a "32-bit" CPU.
 
The third possible metric is the width of the data bus. This describes how many bits of data the CPU can fetch at one time. Again, this is related to pins on the chip, so a CPU that has an 8-bit data bus can fetch 8 bits (or one byte) at a time, and has 8 pins on the CPU which either send or receive the data that goes out to memory. Almost all CPUs access memory either in 8-bit, 16-bit, 32-bit, or 64-bit chunks, though there are a few oddballs that don't follow these rules. For example, the original Motorola 68000 accessed memory in 16-bit chunks, meaning it had 16 pins on the CPU which sent/received data, and thus we say it had a 16-bit data bus width. When Motorola introduced the 68020, it doubled that data bus width to 32-bits, meaning that it could fetch twice the amount of data in a single memory access. This is why the 68020 is called a "32-bit" CPU.
 
So why do you need to know all of this for working with address maps? Well, the first metric is irrelevant because it doesn't apply to memory accesses, but the second two metrics describe how the CPUs deal with memory, and some of those details leak into the address maps.
 
MAME today supports any address bus width from 1-32 bits, and it supports data bus widths of 8, 16, 32, and 64-bit. For CPUs with oddball data bus widths, we generally round up to the next highest and clean up the details in the CPU core.
 
Also note that each address space can have different properties, even within the same CPU. For example, the Intel 80386 has a program address space with a 32-bit address bus and a 32-bit data bus, but it has an I/O address space with a 16-bit address bus and a 32-bit data bus.
 
== Address Map Structure ==
 
A typical address map looks like this (this example is taken from the [http://mamedev.org/source/src/mame/drivers/qix.c qix.c driver]):
 
static ADDRESS_MAP_START( main_map, ADDRESS_SPACE_PROGRAM, 8 )
    AM_RANGE(0x8000, 0x83ff) AM_RAM AM_SHARE(1)
    AM_RANGE(0x8400, 0x87ff) AM_RAM
    AM_RANGE(0x8800, 0x8bff) AM_READNOP  /* 6850 ACIA */
    AM_RANGE(0x8c00, 0x8c00) AM_MIRROR(0x3fe) AM_READWRITE(qix_video_firq_r, qix_video_firq_w)
    AM_RANGE(0x8c01, 0x8c01) AM_MIRROR(0x3fe) AM_READWRITE(qix_data_firq_ack_r, qix_data_firq_ack_w)
    AM_RANGE(0x9000, 0x93ff) AM_READWRITE(pia_3_r, pia_3_w)
    AM_RANGE(0x9400, 0x97ff) AM_READWRITE(pia_0_r, qix_pia_0_w)
    AM_RANGE(0x9800, 0x9bff) AM_READWRITE(pia_1_r, pia_1_w)
    AM_RANGE(0x9c00, 0x9fff) AM_READWRITE(pia_2_r, pia_2_w)
    AM_RANGE(0xa000, 0xffff) AM_ROM
ADDRESS_MAP_END
 
As you can see, it relies heavily on macros to do the heavy lifting. In the current implementation (as of March, 2008), the macros expand into a small "constructor" function. In the future, they may just boil down to a simple data-driven tokenization. Regardless, don't worry about the actual behavior of the macros, just what they mean.
 
Each address map starts with an ADDRESS_MAP_START declaration. This declaration takes 3 parameters. The first parameter ('''main_map''') is the name of the variable you are defining. Each memory map is associated with a variable name so that you can reference it in your machine configuration. The second parameter ('''ADDRESS_SPACE_PROGRAM''') simply specifies which address space the memory map is intended for. This helps MAME ensure that you don't mix memory maps inappropriately. The final parameter ('''8''') is the data bus width, which again is used as a cross-check against the CPU's defined data bus width for the address space you are working with.
 
Following the ADDRESS_MAP_START declaration is a list of address ranges. Each range starts with a begin/end address pair (note that these addresses are fully inclusive, so both the beginning and ending addresses are considered part of the range), followed by a series of macros that describe how to handle memory accesses within that range. The details of each macro will be described in detail below.
 
Finally, there is an ADDRESS_MAP_END macro which ties everything up.
 
A few general comments about the address map above:
 
First, note that this address map has everything listed in nice ascending order. This is not required, though it is usually recommended for readability.
 
Second, note that there are no overlapping ranges. This is also not a requirement. Entries in the address map are always processed in reverse order, starting from the bottom and working up to the top. So any overlapping ranges which appear earlier in the list will take precedence over ranges which appear later.
 
== Read/Write Handlers ==
 
Before diving into the details of the address map macros, let's talk about read/write handlers. The purpose of an address map is to describe what MAME should do when memory within a certain range is accessed. In its most simplistic sense, it specifies a set of functions which should be called in response to memory accesses. These are the read/write handlers.
 
A read handler is a function which accepts an address and perhaps a mask, and returns the value obtained by "reading" memory at that address. Here is a prototype for an 8-bit read handler:
 
UINT8 my_read_handler(offs_t offset);
 
Notice a couple of things about this definition. First, I specifically said an "8-bit" read handler, and you can see that the function returns a UINT8. This means that yes, there are 4 different handler function types, one each for 8, 16, 32, and 64-bit memory accesses. Regardless of the size of data returned, however, all the functions take an offset of type "offs_t", which today is 32 bits (though in the future we may expand it to 64). Also note that it is called an "offset", not an "address". This is because the memory system in MAME always subtracts the beginning address of a memory range from the raw address before passing it into the read/write handlers. This means that the '''offset''' parameter is always the offset relative to the starting address of the range.
 
Similarly, a write handler is a function which accepts an address, a value, and perhaps a mask, and "writes" memory at that address.
 
== Address Map Macros ==
 
Below is a comprehensive list of the supported macros and what they mean.
 
== Runtime Modifications ==
 
== Debugging Helpers ==

Latest revision as of 09:43, 10 December 2020

See [1]