Skip to content

Commit

Permalink
add basic support for rgbds assembly
Browse files Browse the repository at this point in the history
This adds basic support for assembling and linking Game Boy and Game Boy
Color games with RGBDS's `rgbasm` and `rgblink`. This also adds
knowledge of the Sharp SM83 CPU family, the 8080/Z80-like CPU used on
the device's SoC.
  • Loading branch information
terinjokes committed Oct 28, 2024
1 parent b131b2d commit ff5dcc1
Show file tree
Hide file tree
Showing 17 changed files with 420 additions and 1 deletion.
1 change: 1 addition & 0 deletions ci/ciimage/arch/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pkgs=(
doxygen vulkan-validation-layers openssh mercurial gtk-sharp-2 qt5-tools
libwmf cmake netcdf-fortran openmpi nasm gnustep-base gettext
python-lxml hotdoc rust-bindgen qt6-base qt6-tools wayland wayland-protocols
rgbds
# cuda
)

Expand Down
3 changes: 3 additions & 0 deletions docs/markdown/Reference-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ These are return values of the `get_id` (Compiler family) and
| armasm | Microsoft Macro Assembler for ARM and AARCH64 (Since 0.64.0) | |
| mwasmarm | Metrowerks Assembler for Embedded ARM | |
| mwasmeppc | Metrowerks Assembler for Embedded PowerPC | |
| rgbds | Rednex GameBoy Development System | |

## Linker ids

Expand Down Expand Up @@ -80,6 +81,7 @@ These are return values of the `get_linker_id` method in a compiler object.
| ccomp | CompCert used as the linker driver |
| mwldarm | The Metrowerks Linker with the ARM interface, used with mwccarm only |
| mwldeppc | The Metrowerks Linker with the PowerPC interface, used with mwcceppc only |
| rgbds | Rednex GameBoy Development System |

For languages that don't have separate dynamic linkers such as C# and Java, the
`get_linker_id` will return the compiler name.
Expand Down Expand Up @@ -132,6 +134,7 @@ set in the cross file.
| s390 | IBM zSystem s390 |
| s390x | IBM zSystem s390x |
| sh4 | SuperH SH-4 |
| sm83 | Sharp SM83 (GB/GBC) |
| sparc | 32 bit SPARC |
| sparc64 | SPARC v9 processor |
| sw_64 | 64 bit sunway processor |
Expand Down
9 changes: 9 additions & 0 deletions docs/markdown/snippets/rgbds_language_support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Basic support for RGBDS assembly

Meson now provides basic support for the assembly
and linking of Game Boy and Game Boy Color games
with RGBDS.

Most projects will still need to call `rgbfix` as
a `custom_target` as this time, unless they have
correct header values in assembly source.
41 changes: 41 additions & 0 deletions mesonbuild/compilers/asm.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,44 @@ def get_instruction_set_args(self, instruction_set: str) -> T.Optional[T.List[st
def sanity_check(self, work_dir: str, environment: 'Environment') -> None:
if self.info.cpu_family not in {'ppc'}:
raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family')

class RgbdsCompiler(Compiler):
language = 'rgbds'

def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoice, info: MachineInfo,
linker: DynamicLinker, exe_wrapper: T.Optional['ExternalProgram'] = None,
is_cross: bool = True):
super().__init__([], exelist, version, for_machine, info, linker, is_cross=is_cross)
self.id = 'rgbds'
self.exe_wrapper = exe_wrapper

def needs_static_linker(self) -> bool:
return True

def get_optimization_args(self, optimization_level: str) -> T.List[str]:
return []

def get_output_args(self, outputname: str) -> T.List[str]:
return ['-o', outputname]

def get_depfile_suffix(self) -> str:
return 'd'

def get_include_args(self, path: str, is_system: bool) -> T.List[str]:
if not path:
path = '.'
return ['-I' + path]

def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]:
return ['-M', outfile, '-MQ', outtarget]

def sanity_check(self, work_dir: str, environment: 'Environment') -> None:
if self.info.cpu_family != 'sm83':
raise EnvironmentException(f'ASM compiler {self.id!r} does not support {self.info.cpu_family} CPU family')

def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str],
build_dir: str) -> T.List[str]:
for idx, i in enumerate(parameter_list):
if i[:2] == '-I':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
return parameter_list
1 change: 1 addition & 0 deletions mesonbuild/compilers/compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
'cython': ('pyx', ),
'nasm': ('asm', 'nasm',),
'masm': ('masm',),
'rgbds': ('asm',),
}
all_languages = lang_suffixes.keys()
c_cpp_suffixes = {'h'}
Expand Down
37 changes: 37 additions & 0 deletions mesonbuild/compilers/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
defaults['gcc_static_linker'] = ['gcc-ar']
defaults['clang_static_linker'] = ['llvm-ar']
defaults['nasm'] = ['nasm', 'yasm']
defaults['rgbasm'] = ['rgbasm']
defaults['rgblink'] = ['rgblink']


def compiler_from_language(env: 'Environment', lang: str, for_machine: MachineChoice) -> T.Optional[Compiler]:
Expand All @@ -98,6 +100,7 @@ def compiler_from_language(env: 'Environment', lang: str, for_machine: MachineCh
'cython': detect_cython_compiler,
'nasm': detect_nasm_compiler,
'masm': detect_masm_compiler,
'rgbds': detect_rgbds_compiler,
}
return lang_map[lang](env, for_machine) if lang in lang_map else None

Expand Down Expand Up @@ -1353,6 +1356,40 @@ def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp
_handle_exceptions(popen_exceptions, [comp])
raise EnvironmentException('Unreachable code (exception to make mypy happy)')

def detect_rgbds_compiler(env: Environment, for_machine: MachineChoice) -> Compiler:
from .asm import RgbdsCompiler
exelist = env.lookup_binary_entry(for_machine, 'rgbasm')
is_cross = env.is_cross_build(for_machine) # true, until Meson is ported to GB
info = env.machines[for_machine]
if exelist is None:
exelist = defaults['rgbasm']

try:
output = Popen_safe([exelist[0], '--version'])[1]
except OSError:
raise EnvironmentException('Could not execute RGBDS assembler "{}"'.format(''.join(exelist)))

version = search_version(output)
linker = _detect_rgblink(env, for_machine)
comp = RgbdsCompiler(exelist, version, for_machine, info, linker, env.exe_wrapper, is_cross)
env.coredata.add_lang_args(comp.language, RgbdsCompiler, for_machine, env)

return comp

def _detect_rgblink(env: Environment, for_machine: MachineChoice) -> DynamicLinker:
from ..linkers.linkers import RgbdsLinker
exelist = env.lookup_binary_entry(for_machine, 'rgblink')
if exelist is None:
exelist = defaults['rgblink']

try:
output = Popen_safe([exelist[0], '--version'])[1]
except OSError:
raise EnvironmentException('Could not execute RGBDS linker"{}"'.format(''.join(exelist)))

version = search_version(output)
return RgbdsLinker(exelist, for_machine, '', [], version=version)

# GNU/Clang defines and version
# =============================

Expand Down
1 change: 1 addition & 0 deletions mesonbuild/envconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
's390',
's390x',
'sh4',
'sm83',
'sparc',
'sparc64',
'sw_64',
Expand Down
13 changes: 13 additions & 0 deletions mesonbuild/linkers/linkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1660,3 +1660,16 @@ class MetrowerksLinkerARM(MetrowerksLinker):

class MetrowerksLinkerEmbeddedPowerPC(MetrowerksLinker):
id = 'mwldeppc'


class RgbdsLinker(DynamicLinker):
id = 'rgblink'

def get_allow_undefined_args(self) -> T.List[str]:
return []

def get_output_args(self, outputname: str) -> T.List[str]:
return ['-o', outputname]

def get_search_args(self, dirname: str) -> T.List[str]:
return []
3 changes: 2 additions & 1 deletion run_project_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class ArgumentType(CompilerArgumentType):
'keyval', 'platform-osx', 'platform-windows', 'platform-linux',
'java', 'C#', 'vala', 'cython', 'rust', 'd', 'objective c', 'objective c++',
'fortran', 'swift', 'cuda', 'python3', 'python', 'fpga', 'frameworks', 'nasm', 'wasm', 'wayland',
'format',
'format', 'rgbds'
]


Expand Down Expand Up @@ -1136,6 +1136,7 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat
TestCategory('wasm', 'wasm', shutil.which('emcc') is None or backend is not Backend.ninja),
TestCategory('wayland', 'wayland', should_skip_wayland()),
TestCategory('format', 'format'),
TestCategory('rgbds', 'rgbds', shutil.which('rgbasm') is None)
]

categories = [t.category for t in all_tests]
Expand Down
9 changes: 9 additions & 0 deletions test cases/rgbds/1 basic/crossfile.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[binaries]
c = 'gcc'
strip = 'false'

[host_machine]
system = 'dmg'
cpu_family = 'sm83'
cpu = 'sm8320'
endian = 'little'
106 changes: 106 additions & 0 deletions test cases/rgbds/1 basic/include/hardware.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
;*
;* Game Boy Hardware definitions
;* https://github.com/gbdev/hardware.inc
;*
;* Based on Jones' hardware.inc
;* And based on Carsten Sorensen's ideas.
;*
;* To the extent possible under law, the authors of this work have
;* waived all copyright and related or neighboring rights to the work.
;* See https://creativecommons.org/publicdomain/zero/1.0/ for details.
;*
;* SPDX-License-Identifier: CC0-1.0
;*
;* Trimmed version for unit tests

; --
; -- BGP ($FF47)
; -- BG Palette Data (W)
; --
; -- Bit 7-6 - Intensity for %11
; -- Bit 5-4 - Intensity for %10
; -- Bit 3-2 - Intensity for %01
; -- Bit 1-0 - Intensity for %00
; --
DEF rBGP EQU $FF47


;***************************************************************************
;*
;* Header
;*
;***************************************************************************

;*
;* Nintendo scrolling logo
;* (Code won't work on a real Game Boy)
;* (if next lines are altered.)
MACRO NINTENDO_LOGO
DB $CE,$ED,$66,$66,$CC,$0D,$00,$0B,$03,$73,$00,$83,$00,$0C,$00,$0D
DB $00,$08,$11,$1F,$88,$89,$00,$0E,$DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
DB $BB,$BB,$67,$63,$6E,$0E,$EC,$CC,$DD,$DC,$99,$9F,$BB,$B9,$33,$3E
ENDM

; $0143 Color Game Boy compatibility code
DEF CART_COMPATIBLE_DMG EQU $00
DEF CART_COMPATIBLE_DMG_GBC EQU $80
DEF CART_COMPATIBLE_GBC EQU $C0

; $0146 Game Boy/Super Game Boy indicator
DEF CART_INDICATOR_GB EQU $00
DEF CART_INDICATOR_SGB EQU $03

; $0147 Cartridge type
DEF CART_ROM EQU $00
DEF CART_ROM_MBC1 EQU $01
DEF CART_ROM_MBC1_RAM EQU $02
DEF CART_ROM_MBC1_RAM_BAT EQU $03
DEF CART_ROM_MBC2 EQU $05
DEF CART_ROM_MBC2_BAT EQU $06
DEF CART_ROM_RAM EQU $08
DEF CART_ROM_RAM_BAT EQU $09
DEF CART_ROM_MMM01 EQU $0B
DEF CART_ROM_MMM01_RAM EQU $0C
DEF CART_ROM_MMM01_RAM_BAT EQU $0D
DEF CART_ROM_MBC3_BAT_RTC EQU $0F
DEF CART_ROM_MBC3_RAM_BAT_RTC EQU $10
DEF CART_ROM_MBC3 EQU $11
DEF CART_ROM_MBC3_RAM EQU $12
DEF CART_ROM_MBC3_RAM_BAT EQU $13
DEF CART_ROM_MBC5 EQU $19
DEF CART_ROM_MBC5_RAM EQU $1A
DEF CART_ROM_MBC5_RAM_BAT EQU $1B
DEF CART_ROM_MBC5_RUMBLE EQU $1C
DEF CART_ROM_MBC5_RAM_RUMBLE EQU $1D
DEF CART_ROM_MBC5_RAM_BAT_RUMBLE EQU $1E
DEF CART_ROM_MBC7_RAM_BAT_GYRO EQU $22
DEF CART_ROM_POCKET_CAMERA EQU $FC
DEF CART_ROM_BANDAI_TAMA5 EQU $FD
DEF CART_ROM_HUDSON_HUC3 EQU $FE
DEF CART_ROM_HUDSON_HUC1 EQU $FF

; $0148 ROM size
; these are kilobytes
DEF CART_ROM_32KB EQU $00 ; 2 banks
DEF CART_ROM_64KB EQU $01 ; 4 banks
DEF CART_ROM_128KB EQU $02 ; 8 banks
DEF CART_ROM_256KB EQU $03 ; 16 banks
DEF CART_ROM_512KB EQU $04 ; 32 banks
DEF CART_ROM_1024KB EQU $05 ; 64 banks
DEF CART_ROM_2048KB EQU $06 ; 128 banks
DEF CART_ROM_4096KB EQU $07 ; 256 banks
DEF CART_ROM_8192KB EQU $08 ; 512 banks
DEF CART_ROM_1152KB EQU $52 ; 72 banks
DEF CART_ROM_1280KB EQU $53 ; 80 banks
DEF CART_ROM_1536KB EQU $54 ; 96 banks

; $0149 SRAM size
; these are kilobytes
DEF CART_SRAM_NONE EQU 0
DEF CART_SRAM_8KB EQU 2 ; 1 bank
DEF CART_SRAM_32KB EQU 3 ; 4 banks
DEF CART_SRAM_128KB EQU 4 ; 16 banks

; $014A Destination code
DEF CART_DEST_JAPANESE EQU $00
DEF CART_DEST_NON_JAPANESE EQU $01
5 changes: 5 additions & 0 deletions test cases/rgbds/1 basic/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
project('rgbds-test', 'rgbds')

executable('rgbdstest.gb', 'src/main.asm',
include_directories: ['include'],
link_language: 'rgbds')
33 changes: 33 additions & 0 deletions test cases/rgbds/1 basic/src/main.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
INCLUDE "hardware.inc"

SECTION "start",ROM0[$0100]
nop
jp begin

NINTENDO_LOGO

db "EXAMPLE",0,0,0,0,0,0,0,0 ; Cart Name
db CART_COMPATIBLE_DMG
db 0,0 ; Licensee code
db CART_INDICATOR_GB
db CART_ROM
db CART_ROM_32KB
db CART_SRAM_NONE
db CART_DEST_NON_JAPANESE
db $33 ; Old licensee code
db 0 ; Mask ROM version
db $a7 ; Header checksum
dw $5721 ; Global checksum

begin:
di
ld sp,$ffff

init:
ld a, %11111100
ld [rBGP], a ; clear the screen

wait:
halt
nop
jr wait
9 changes: 9 additions & 0 deletions test cases/rgbds/2 rgbfix/crossfile.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[binaries]
c = 'gcc'
strip = 'false'

[host_machine]
system = 'dmg'
cpu_family = 'sm83'
cpu = 'sm8320'
endian = 'little'
Loading

0 comments on commit ff5dcc1

Please sign in to comment.