Difference between revisions of "Endianness"
(found 11 examples of bitfield ordering or byte ordering) |
(Expanded the explanation and removed some examples that in my opinion don't clarify the issue.) |
||
Line 1: | Line 1: | ||
− | + | Endianness is referring to the order that bytes comprising a word are stored in memory. Little-endian means that less significant bytes are stored in a lower memory address. Big-endian means that more significant bytes are stored in a lower memory address. Some people call little-endian "reverse endian" or similar, as values appear to be "backwards" when viewed in a hexadecimal memory viewer. For example, the 16-bit value <code>$1234</code> would be stored as the bytes <code>34 12</code> in little-endian. | |
− | # Instructions with 16-bit operands, such as <code>ld de, d16</code> and <code>call a16</code>. This is visible in self-modifying code in SRAM, WRAM, or HRAM. | + | |
− | # <code>push</code> | + | Most parts of the Game Boy hardware are natively little-endian, as described below. However, when your code does not depend directly on the endianness of a certain hardware feature, you as the programmer are free to choose the endianness with which you store data. |
− | # <code>ld [a16], sp</code> ($08) instruction | + | |
+ | Consider the following example which stores and loads a 16-bit value as little-endian: | ||
+ | <pre> | ||
+ | SECTION "Code", ROMX | ||
+ | do_16_bit_operations: | ||
+ | ; Load a 16-bit value into hl. | ||
+ | ld hl, variable | ||
+ | ld a,[hl+] ; Load lower byte of 16-bit value from the lower address. | ||
+ | ld h,[hl] ; Load upper byte of 16-bit value from the higher address. | ||
+ | ld l,a | ||
+ | |||
+ | ; Do some other stuff. | ||
+ | ; ... | ||
+ | |||
+ | ; Save the value back to memory. | ||
+ | ld a,l | ||
+ | ld [variable],a ; Store lower byte of the 16-bit value to the lower adress. | ||
+ | ld a,h | ||
+ | ld [variable+1],a ; Store upper byte of the 16-bit value to the higher adress. | ||
+ | |||
+ | ret | ||
+ | |||
+ | SECTION "Variables", WRAM0 | ||
+ | variable: dw | ||
+ | </pre> | ||
+ | |||
+ | However, with only slight modifications, the code could be made to load the 16-bit value as big-endian with no change in performance. | ||
+ | <pre> | ||
+ | SECTION "Code", ROMX | ||
+ | do_16_bit_operations: | ||
+ | ; Load a 16-bit value into hl. | ||
+ | ld hl, variable | ||
+ | ld a,[hl+] ; Load upper byte of 16-bit value from the lower address. | ||
+ | ld l,[hl] ; Load lower byte of 16-bit value from the higher address. | ||
+ | ld h,a | ||
+ | |||
+ | ; Do some other stuff. | ||
+ | ; ... | ||
+ | |||
+ | ; Save the value back to memory. | ||
+ | ld a,h | ||
+ | ld [variable],a ; Store upper byte of the 16-bit value to the lower adress. | ||
+ | ld a,l | ||
+ | ld [variable+1],a ; Store lower byte of the 16-bit value to the higher adress. | ||
+ | |||
+ | ret | ||
+ | |||
+ | SECTION "Variables", WRAM0 | ||
+ | variable: dw | ||
+ | </pre> | ||
+ | Most code targeting the Game Boy would use the former format. However, for code that doesn't depend on hardware features like the ones listed below, the endianness can be considered merely a convention, and not an inherent property of the hardware. Thus, the programmer is free to choose either convention if it improves the function of the code, for example by introducing opportunities to optimize the order in which byte are read sequentially. | ||
+ | |||
+ | === Endianness of the SM83 CPU core === | ||
+ | |||
+ | The SM83 CPU core is little-endian. This is exposed in three places: | ||
+ | # Instructions with 16-bit operands, such as <code>ld de, d16</code> and <code>call a16</code>. This is visible in self-modifying code in SRAM, WRAM, or HRAM. For example, the instruction <code>call $1234</code> is stored in memory as <code>CD 34 12</code>. | ||
+ | # Instructions that interact with the stack, like <code>push/pop</code>, <code>call/ret</code> as well as interrupts. This is visible in code that "manually" interacts with data was previously <code>push</code>ed to the stack or will be <code>pop</code>ed in the future. For example, if <code>HL==$1234</code> and <code>SP==$DFF8</code> initially and you execute <code>push de</code>, then <code>34</code> is stored in the lower memory address (<code>SP-2=$DFF6</code>) and <code>12</code> is stored in the upper memory address (<code>SP-1=$DFF7</code>). | ||
+ | # The <code>ld [a16], sp</code> (<code>$08</code>) instruction, which stores the lower byte of <code>SP</code> at the address specified by <code>a16</code>, and the upper byte of <code>SP</code> at the address specified by <code>a16+1</code>. | ||
+ | |||
+ | === Endianness of the Game Boy peripheral units === | ||
The PPU has a mix of endiannesses. | The PPU has a mix of endiannesses. | ||
− | + | * Pattern data is little-endian between bytes. The byte storing bit 0 (the lower bitplane) of the color indices of the 8 pixels in each 8×1-pixel sliver is at a lower address than the byte storing bit 1 (the upper bitplane). | |
− | * Pattern data is little-endian between bytes. The byte storing bit 0 of the color indices of the 8 pixels in each 8×1-pixel sliver is at a lower address than the byte storing bit 1 | + | |
− | + | ||
* GBC color palette values written to BCPD and OCPD take bits 7-0 before bits 15-8, where green straddles bits 9-5. | * GBC color palette values written to BCPD and OCPD take bits 7-0 before bits 15-8, where green straddles bits 9-5. | ||
− | * Addresses in | + | * Addresses in the GBC specific VRAM DMA are big-endian, with the most significant byte written to the lower address. |
− | The APU is little-endian, with pulse and | + | The APU is little-endian, with pulse and wave period bits 7-0 in a lower address than bits 10-8. |
− | + | Additionally, the 16-bit global checksum in the ROM header at $014E-014F is big-endian. | |
− | + | ||
− | + | ||
− | + |
Latest revision as of 01:00, 28 February 2023
Endianness is referring to the order that bytes comprising a word are stored in memory. Little-endian means that less significant bytes are stored in a lower memory address. Big-endian means that more significant bytes are stored in a lower memory address. Some people call little-endian "reverse endian" or similar, as values appear to be "backwards" when viewed in a hexadecimal memory viewer. For example, the 16-bit value $1234
would be stored as the bytes 34 12
in little-endian.
Most parts of the Game Boy hardware are natively little-endian, as described below. However, when your code does not depend directly on the endianness of a certain hardware feature, you as the programmer are free to choose the endianness with which you store data.
Consider the following example which stores and loads a 16-bit value as little-endian:
SECTION "Code", ROMX do_16_bit_operations: ; Load a 16-bit value into hl. ld hl, variable ld a,[hl+] ; Load lower byte of 16-bit value from the lower address. ld h,[hl] ; Load upper byte of 16-bit value from the higher address. ld l,a ; Do some other stuff. ; ... ; Save the value back to memory. ld a,l ld [variable],a ; Store lower byte of the 16-bit value to the lower adress. ld a,h ld [variable+1],a ; Store upper byte of the 16-bit value to the higher adress. ret SECTION "Variables", WRAM0 variable: dw
However, with only slight modifications, the code could be made to load the 16-bit value as big-endian with no change in performance.
SECTION "Code", ROMX do_16_bit_operations: ; Load a 16-bit value into hl. ld hl, variable ld a,[hl+] ; Load upper byte of 16-bit value from the lower address. ld l,[hl] ; Load lower byte of 16-bit value from the higher address. ld h,a ; Do some other stuff. ; ... ; Save the value back to memory. ld a,h ld [variable],a ; Store upper byte of the 16-bit value to the lower adress. ld a,l ld [variable+1],a ; Store lower byte of the 16-bit value to the higher adress. ret SECTION "Variables", WRAM0 variable: dw
Most code targeting the Game Boy would use the former format. However, for code that doesn't depend on hardware features like the ones listed below, the endianness can be considered merely a convention, and not an inherent property of the hardware. Thus, the programmer is free to choose either convention if it improves the function of the code, for example by introducing opportunities to optimize the order in which byte are read sequentially.
Endianness of the SM83 CPU core
The SM83 CPU core is little-endian. This is exposed in three places:
- Instructions with 16-bit operands, such as
ld de, d16
andcall a16
. This is visible in self-modifying code in SRAM, WRAM, or HRAM. For example, the instructioncall $1234
is stored in memory asCD 34 12
. - Instructions that interact with the stack, like
push/pop
,call/ret
as well as interrupts. This is visible in code that "manually" interacts with data was previouslypush
ed to the stack or will bepop
ed in the future. For example, ifHL==$1234
andSP==$DFF8
initially and you executepush de
, then34
is stored in the lower memory address (SP-2=$DFF6
) and12
is stored in the upper memory address (SP-1=$DFF7
). - The
ld [a16], sp
($08
) instruction, which stores the lower byte ofSP
at the address specified bya16
, and the upper byte ofSP
at the address specified bya16+1
.
Endianness of the Game Boy peripheral units
The PPU has a mix of endiannesses.
- Pattern data is little-endian between bytes. The byte storing bit 0 (the lower bitplane) of the color indices of the 8 pixels in each 8×1-pixel sliver is at a lower address than the byte storing bit 1 (the upper bitplane).
- GBC color palette values written to BCPD and OCPD take bits 7-0 before bits 15-8, where green straddles bits 9-5.
- Addresses in the GBC specific VRAM DMA are big-endian, with the most significant byte written to the lower address.
The APU is little-endian, with pulse and wave period bits 7-0 in a lower address than bits 10-8.
Additionally, the 16-bit global checksum in the ROM header at $014E-014F is big-endian.