memory¶
This module exposes waitstate and DMA control registers, constants for relocating code/data, as well as access to ROM and SRAM as arrays.
Section Constants¶
These are to be used with the codegenDecl pragma to place your variables and procedures into certain regions of memory. They cannot be used for local variables, as those are always on the stack which is in IWRAM.
Example:
# Define an array which is statically allocated in EWRAM.
var collisionGrid {.codegenDecl: DataInEwram.}: array[2048, uint16]
- const DataInIwram = "__attribute__((section(\".iwram.data\"))) $# $#"¶
Put variable in IWRAM (default).
- const DataInEwram = "__attribute__((section(\".ewram.data\"))) $# $#"¶
Put variable in EWRAM
- const ArmCodeInIwram = "__attribute__((section(\".iwram.text\"), target(\"arm\"), long_call)) $# $#$#"¶
Put procedure in IWRAM.
- const ThumbCodeInEwram = "__attribute__((section(\".ewram.text\"), target(\"thumb\"), long_call)) $# $#$#"¶
Put procedure in EWRAM.
Tip
You may wish create shorthands for these in your project, like so:
{.pragma: ewdata, codegenDecl: DataInEwram.}
{.pragma: iwdata, codegenDecl: DataInIwram.}
{.pragma: ewcode, codegenDecl: ThumbCodeInEwram.}
{.pragma: iwcode, codegenDecl: ArmCodeInIwram.}
You could save this in a prelude.nim file along with commonly-used imports which could then be included at the start of each module in your game.
Memory-mapped arrays¶
- let romMem: array[0x1000000, uint16]¶
Access to ROM as an array of halfwords.
The max ROM size is
32MiB
.
- var sramMem: array[0x10000, uint8]¶
Access to SRAM as an array of bytes.
This has a maximum size of
64KiB
, though many carts don’t actually have that much available.
Waitstate Control¶
- var waitcnt: WaitCnt¶
Waitstate control register.
This controls the number of CPU cycles taken to access cart memory (ROM and SRAM).
The “standard” setting (used by most commercial games) is as follows:
waitcnt.init( sram = WsSram.N8_S8, # 8 cycles to access SRAM. rom0 = WsRom0.N3_S1, # 3 cycles to access ROM, or 1 cycle for sequential access. rom2 = WsRom2.N8_S8, # 8 cycles to access ROM (mirror #2) which may be used for flash storage. prefetch = true # prefetch buffer enabled. )
In this example,
waitcnt.rom0
determines the access time for the default view into ROM.Warning
The preferred access time of “3,1” works on every flashcart except for the SuperCard SD and its derivatives. If you want to support the SuperCard without compromising performance for all, use the
slowGamePak
proc.There are two additional ROM mirrors located at
0x0A000000
and0x0C000000
, which have access times determined bywaitcnt.rom1
andwaitcnt.rom2
.The mirrors may be useful for carts containing multiple ROM chips. For example on some carts the upper 128KiB of ROM is mapped to flash storage which would require different access timings.
- type WaitCnt = distinct uint16¶
Field
Type
Bits
Description
sram
0-1
SRAM access time.
rom0
2-4
ROM mirror 0 access time.
rom1
5-7
ROM mirror 1 access time.
rom2
8-10
ROM mirror 2 access time.
phi
11-12
Cart clock (don’t touch!)
prefetch
bool
14
Game Pak prefetch. If enabled, the GBA’s prefetch unit will fetch upcoming instructions from ROM, when ROM is not being accessed by the CPU, generally leading to a performance boost.
gb
bool
15
True if a Game Boy (Color) cartridge is currently inserted. (Read only!)
- type WsSram = enum¶
SRAM access timings
N4_S4
– SRAM access takes 4 cyclesN3_S3
– SRAM access takes 3 cyclesN2_S2
– SRAM access takes 2 cyclesN8_S8
– SRAM access takes 8 cycles
- type WsRom0 = enum¶
ROM access timings.
Initial access to ROM takes
N
cycles, sequential access takesS
cycles.For more information on
N
cycles andS
cycles, see the asm chapter of Tonc.N4_S2
N3_S2
N2_S2
N8_S2
N4_S1
N3_S1
N2_S1
N8_S1
Note
On Nim 1.6 and later, you can omit the enum prefixes as long as you enable overloadable enums.
In other words, you can write N3_S1
instead of WsRom0.N3_S1
.
- type WsRom1 = enum¶
Access timings for ROM mirror starting at
0x0A000000
.N4_S4
N3_S4
N2_S4
N8_S4
N4_S1
N3_S1
N2_S1
N8_S1
- type WsRom2 = enum¶
Access timings for ROM mirror starting at
0x0C000000
.N4_S8
N3_S8
N2_S8
N8_S8
N4_S1
N3_S1
N2_S1
N8_S1
- type WsPhi = enum¶
PHI Terminal Output. This allows the GBA to supply a clock signal to the cartridge hardware, but is not used in practise.
phiOff
– Disabled (recommended)phi4MHz
– 4.19MHzphi8MHz
– 8.38MHzphi17MHz
– 16.78MHz
Direct Memory Access¶
Direct Memory Access (DMA) is a hardware feature that allows the GBA to quickly copy data from one location to another at a given moment.
Channels 0 and 1 are required for audio (maxmod will take care of these), while 2 and 3 may be used for anything you like. Note that DMA comes with some caveats that make it dangerous to use (and it’s not much faster than an optimised copy routine like memcpy32
anyways), so I wouldn’t recommend it for general purposes.
DMA can be applied on HBlank (known as HDMA) for some neat scanline-based effects. The HBlank tnterrupt could also be used for such effects, but HDMA
Example:
# Create a table of horizontal scroll offsets:
var hofsTable: array[ScreenHeight, int16]
# Populate the table with a sine curve:
for i in 0 ..< ScreenHeight:
hofsTable[i] = (luSin(i * 100) shr 13).int16
proc onVBlank() =
# Cancel previous DMA.
dmach[3].stop()
# Setup new DMA: Each scanline, copy a subsequent value from the table
# into BG3's horizontal scroll register.
dmach[3].start(
dst = addr bgofs[3].x,
src = addr hofsTable[0],
count = 1,
dstMode = Fix,
srcMode = Inc,
repeat = true,
size = Halfwords,
time = AtHBlank,
)
Danger
If you cancel a DMA transfer within a few CPU cycles of it starting internally, the whole GBA will lock up and possibly trash all the memory (including save data!)
For this reason, you should make sure to stop DMA at a safe moment when no transfer is about to occur, for example HDMA could be cancelled at the start of your VBlank handler.
- var dmach: array[4, DmaChannel]¶
Direct memory access channels.
- type DmaChannel = object¶
A group of registers for a single DMA channel.
Note, all
DmaCnt
fields can be set directly via the channel too, e.g.dmach[3].
Field
Type
Description
src
pointer Source address.
dst
pointer Destination address.
count
uint16 Number of transfers.
cnt
DmaCnt DMA control register.
- type DmaCnt = distinct uint16¶
DMA control register. (Write only!)
Field
Type
Bits
Description
dstMode
DmaDstMode
5..6
Type of increment applied to destination address.
srcMode
DmaSrcMode
7..8
Type of increment applied to source address.
repeat
bool
9
Repeat the transfer for each occurrence specified by
time
.size
DmaSize
10
Whether to transfer 16 or 32 bits at a time.
time
DmaTime
12..13
Timing mode, determines when the transfer should occur.
irq
bool
14
If true, an interrupt will be raised when finished.
enable
bool
15
Enable DMA transfer for this channel. (Invoking start() will do this for you.)
- type DmaDstMode = enum¶
Inc
– Increment after each copy.Dec
– Decrement after each copy.Fix
– Remain unchanged.Reload
– LikeInc
but resets to its initial value after all transfers have been completed.
- type DmaSrcMode = enum¶
Inc
– Increment after each copy.Dec
– Decrement after each copy.Fix
– Remain unchanged.
- type DmaSize = enum¶
Halfwords
– Copy 16 bits.Words
– Copy 32 bits.
- type DmaTime = enum¶
AtNow
– Transfer immediately.AtVBlank
– Transfer on VBlank.AtHBlank
– Transfer on HBlank. Note: HBlank DMA does not occur during VBlank (unlike HBlank interrupts).AtSpecial
– Channels 0/1: start on FIFO empty. Channel 2: start on VCount = 2
- template start(d: DmaChannel, dst, src: pointer, count: uint16, args: varargs[untyped])¶
Activate DMA on the given channel.
- template stop(d: DmaChannel)¶
Cancels any transfer that may be happening on the given DMA channel.
Procedures¶
- proc slowGamePak(): bool¶
Check if the cartridge does not support fast ROM access, which might be the case if the game is running on the SuperCard MiniSD.
Example:
if slowGamePak(): waitcnt.init(rom0 = WsRom0.N4_S2) else: waitcnt.init(rom0 = WsRom0.N3_S1)