gba/docs/print.html

476 lines
26 KiB
HTML
Raw Normal View History

2018-11-08 15:20:40 +11:00
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
2018-11-18 11:14:42 +11:00
<title>Rust GBA Guide</title>
2018-11-08 15:20:40 +11:00
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body class="light">
<!-- Provide site root to javascript -->
<script type="text/javascript">var path_to_root = "";</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = 'light'; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
2018-12-30 14:18:09 +11:00
<ol class="chapter"><li><a href="development-setup.html"><strong aria-hidden="true">1.</strong> Development Setup</a></li><li><a href="gba-asm.html"><strong aria-hidden="true">2.</strong> GBA Assembly</a></li></ol>
2018-11-08 15:20:40 +11:00
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light <span class="default">(default)</span></button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
2018-11-18 11:14:42 +11:00
<h1 class="menu-title">Rust GBA Guide</h1>
2018-11-08 15:20:40 +11:00
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
2018-12-30 14:18:09 +11:00
<a class="header" href="#development-setup" id="development-setup"><h1>Development Setup</h1></a>
2018-12-10 19:48:06 +11:00
<p>Before you can build a GBA game you'll have to follow some special steps to
setup the development environment.</p>
<p>Once again, extra special thanks to <strong>Ketsuban</strong>, who first dove into how to
make this all work with rust and then shared it with the world.</p>
<a class="header" href="#per-system-setup" id="per-system-setup"><h2>Per System Setup</h2></a>
<p>Obviously you need your computer to have a <a href="https://rustup.rs/">working rust
installation</a>. However, you'll also need to ensure that
you're using a nightly toolchain (we will need it for inline assembly, among
other potential useful features). You can run <code>rustup default nightly</code> to set
nightly as the system wide default toolchain, or you can use a <a href="https://github.com/rust-lang-nursery/rustup.rs#the-toolchain-file">toolchain
file</a> to use
nightly just on a specific project, but either way we'll be assuming the use of
nightly from now on. You'll also need the <code>rust-src</code> component so that
<code>cargo-xbuild</code> will be able to compile the core crate for us in a bit, so run
<code>rustup component add rust-src</code>.</p>
<p>Next, you need <a href="https://devkitpro.org/wiki/Getting_Started">devkitpro</a>. They've
got a graphical installer for Windows that runs nicely, and I guess <code>pacman</code>
support on Linux (I'm on Windows so I haven't tried the Linux install myself).
We'll be using a few of their general binutils for the <code>arm-none-eabi</code> target,
and we'll also be using some of their tools that are specific to GBA
development, so <em>even if</em> you already have the right binutils for whatever
reason, you'll still want devkitpro for the <code>gbafix</code> utility.</p>
<ul>
<li>On Windows you'll want something like <code>C:\devkitpro\devkitARM\bin</code> and
<code>C:\devkitpro\tools\bin</code> to be <a href="https://stackoverflow.com/q/44272416/455232">added to your
PATH</a>, depending on where you
installed it to and such.</li>
<li>On Linux you can use pacman to get it, and the default install puts the stuff
in <code>/opt/devkitpro/devkitARM/bin</code> and <code>/opt/devkitpro/tools/bin</code>. If you need
help you can look in our repository's
<a href="https://github.com/rust-console/gba/blob/master/.travis.yml">.travis.yml</a>
file to see exactly what our CI does.</li>
</ul>
<p>Finally, you'll need <code>cargo-xbuild</code>. Just run <code>cargo install cargo-xbuild</code> and
cargo will figure it all out for you.</p>
<a class="header" href="#per-project-setup" id="per-project-setup"><h2>Per Project Setup</h2></a>
<p>Once the system wide tools are ready, you'll need some particular files each
time you want to start a new project. You can find them in the root of the
<a href="https://github.com/rust-console/gba">rust-console/gba repo</a>.</p>
<ul>
<li><code>thumbv4-none-agb.json</code> describes the overall GBA to cargo-xbuild (and LLVM)
so it knows what to do. Technically the GBA is <code>thumbv4-none-eabi</code>, but we
change the <code>eabi</code> to <code>agb</code> so that we can distinguish it from other <code>eabi</code>
devices when using <code>cfg</code> flags.</li>
<li><code>crt0.s</code> describes some ASM startup stuff. If you have more ASM to place here
later on this is where you can put it. You also need to build it into a
<code>crt0.o</code> file before it can actually be used, but we'll cover that below.</li>
<li><code>linker.ld</code> tells the linker all the critical info about the layout
expectations that the GBA has about our program, and that it should also
include the <code>crt0.o</code> file with our compiled rust code.</li>
</ul>
<a class="header" href="#compiling" id="compiling"><h2>Compiling</h2></a>
<p>Once all the tools are in place, there's particular steps that you need to
compile the project. For these to work you'll need some source code to compile.
Unlike with other things, an empty main file and/or an empty lib file will cause
a total build failure, because we'll need a
<a href="https://rust-embedded.github.io/book/intro/no-std.html">no_std</a> build, and rust
defaults to builds that use the standard library. The next section has a minimal
example file you can use (along with explanation), but we'll describe the build
steps here.</p>
<ul>
<li>
<p><code>arm-none-eabi-as crt0.s -o target/crt0.o</code></p>
<ul>
<li>This builds your text format <code>crt0.s</code> file into object format <code>crt0.o</code>
that's placed in the <code>target/</code> directory. Note that if the <code>target/</code>
directory doesn't exist yet it will fail, so you have to make the directory
if it's not there. You don't need to rebuild <code>crt0.s</code> every single time,
only when it changes, but you might as well throw a line to do it every time
into your build script so that you never forget because it's a practically
instant operation anyway.</li>
</ul>
</li>
<li>
<p><code>cargo xbuild --target thumbv4-none-agb.json</code></p>
<ul>
<li>This builds your Rust source. It accepts <em>most of</em> the normal options, such
as <code>--release</code>, and options, such as <code>--bin foo</code> or <code>--examples</code>, that you'd
expect <code>cargo</code> to accept.</li>
<li>You <strong>can not</strong> build and run tests this way, because they require <code>std</code>,
which the GBA doesn't have. If you want you can still run some of your
project's tests with <code>cargo test --lib</code> or similar, but that builds for your
local machine, so anything specific to the GBA (such as reading and writing
registers) won't be testable that way. If you want to isolate and try out
some piece code running on the GBA you'll unfortunately have to make a demo
for it in your <code>examples/</code> directory and then run the demo in an emulator
and see if it does what you expect.</li>
<li>The file extension is important! It will work if you forget it, but <code>cargo xbuild</code> takes the inclusion of the extension as a flag to also compile
dependencies with the same sysroot, so you can include other crates in your
build. Well, crates that work in the GBA's limited environment, but you get
2018-12-10 19:48:06 +11:00
the idea.</li>
</ul>
</li>
</ul>
<p>At this point you have an ELF binary that some emulators can execute directly
(more on that later). However, if you want a &quot;real&quot; ROM that works in all
emulators and that you could transfer to a flash cart to play on real hardware
there's a little more to do.</p>
<ul>
<li>
<p><code>arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/MODE/BIN_NAME target/ROM_NAME.gba</code></p>
<ul>
<li>This will perform an <a href="https://linux.die.net/man/1/objcopy">objcopy</a> on our
program. Here I've named the program <code>arm-none-eabi-objcopy</code>, which is what
devkitpro calls their version of <code>objcopy</code> that's specific to the GBA in the
Windows install. If the program isn't found under that name, have a look in
your installation directory to see if it's under a slightly different name
or something.</li>
<li>As you can see from reading the man page, the <code>-O binary</code> option takes our
lovely ELF file with symbols and all that and strips it down to basically a
bare memory dump of the program.</li>
<li>The next argument is the input file. You might not be familiar with how
<code>cargo</code> arranges stuff in the <code>target/</code> directory, and between RLS and
<code>cargo doc</code> and stuff it gets kinda crowded, so it goes like this:
<ul>
<li>Since our program was built for a non-local target, first we've got a
directory named for that target, <code>thumbv4-none-agb/</code></li>
<li>Next, the &quot;MODE&quot; is either <code>debug/</code> or <code>release/</code>, depending on if we had
the <code>--release</code> flag included. You'll probably only be packing release
mode programs all the way into GBA roms, but it works with either mode.</li>
<li>Finally, the name of the program. If your program is something out of the
project's <code>src/bin/</code> then it'll be that file's name, or whatever name you
configured for the bin in the <code>Cargo.toml</code> file. If your program is
something out of the project's <code>examples/</code> directory there will be a
similar <code>examples/</code> sub-directory first, and then the example's name.</li>
</ul>
</li>
<li>The final argument is the output of the <code>objcopy</code>, which I suggest putting
at just the top level of the <code>target/</code> directory. Really it could go
anywhere, but if you're using git then it's likely that your <code>.gitignore</code>
file is already setup to exclude everything in <code>target/</code>, so this makes sure
that your intermediate game builds don't get checked into your git.</li>
</ul>
</li>
<li>
<p><code>gbafix target/ROM_NAME.gba</code></p>
<ul>
<li>The <code>gbafix</code> tool also comes from devkitpro. The GBA is very picky about a
ROM's format, and <code>gbafix</code> patches the ROM's header and such so that it'll
work right. Unlike <code>objcopy</code>, this tool is custom built for GBA development,
so it works just perfectly without any arguments beyond the file name. The
ROM is patched in place, so we don't even need to specify a new destination.</li>
</ul>
</li>
</ul>
<p>And you're <em>finally</em> done!</p>
<p>Of course, you probably want to make a script for all that, but it's up to you.
On our own project we have it mostly set up within a <code>Makefile.toml</code> which runs
using the <a href="https://github.com/sagiegurari/cargo-make">cargo-make</a> plugin.</p>
2018-12-30 14:18:09 +11:00
<a class="header" href="#checking-your-setup" id="checking-your-setup"><h2>Checking Your Setup</h2></a>
<p>As I said, you need some source code to compile just to check that your
compilation pipeline is working. Here's a sample file that just puts three dots
on the screen without depending on any crates or anything at all.</p>
2018-12-10 19:48:06 +11:00
<p><code>hello_magic.rs</code>:</p>
2018-12-30 14:18:09 +11:00
<pre><pre class="playpen"><code class="language-rust">#![no_std]
#![feature(start)]
2018-12-10 19:48:06 +11:00
#[panic_handler]
fn panic(_info: &amp;core::panic::PanicInfo) -&gt; ! {
loop {}
}
#[start]
fn main(_argc: isize, _argv: *const *const u8) -&gt; isize {
unsafe {
(0x400_0000 as *mut u16).write_volatile(0x0403);
(0x600_0000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
(0x600_0000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
(0x600_0000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
loop {}
}
}
</code></pre></pre>
<p>Throw that into your project skeleton, build the program, and give it a run. You
should see a red, green, and blue dot close-ish to the middle of the screen. If
you don't, something <em>already</em> went wrong. Double check things, phone a friend,
write your senators, try asking <code>Lokathor</code> or <code>Ketsuban</code> on the <a href="https://discordapp.com/invite/aVESxV8">Rust Community
Discord</a>, until you're eventually able to
get your three dots going.</p>
<p>Of course, I'm sure you want to know why those numbers are the numbers to use.
Well that's what the whole rest of the book is about!</p>
2018-12-30 14:18:09 +11:00
<a class="header" href="#gba-assembly" id="gba-assembly"><h1>GBA Assembly</h1></a>
<p>On the GBA sometimes you just end up using assembly. Not a whole lot, but
sometimes. Accordingly, you should know how assembly works on the GBA.</p>
2018-12-10 19:48:06 +11:00
<ul>
2018-12-30 14:18:09 +11:00
<li>
<p>The <a href="http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0210c/index.html">ARM Infocenter:
ARM7TDMI</a>
is the basic authority for reference information. The GBA has a CPU with the
<code>ARMv4</code> ISA, the <code>ARMv4T</code> variant, and specifically the <code>ARM7TDMI</code>
microarchitecture. Someone at ARM decided that having both <code>ARM#</code> and <code>ARMv#</code>
was a good way to <a href="https://en.wikichip.org/wiki/arm/versions">version things</a>,
even when the numbers don't match, and the rest of us have been sad ever
since. The link there will take you to the correct book within the big pile of
ARM books available within the ARM Infocenter. Note that there is also a <a href="http://infocenter.arm.com/help/topic/com.arm.doc.ddi0210c/DDI0210B.pdf">PDF
Version</a>
of the documentation available, if you'd like that.</p>
</li>
<li>
<p>The <a href="https://problemkaputt.de/gbatek.htm#armcpuoverview">GBATek: ARM CPU
Overview</a> also has quite a
bit of info. Most of it is somewhat a duplication of what you'd find in the
ARM Infocenter reference manual, but it's also somewhat specialized towards
the GBA's specifics. It's in the usual, uh, &quot;sparse&quot; style that GBATEK is
written in, so I wouldn't suggest that read it first.</p>
</li>
<li>
<p>The <a href="https://rust.godbolt.org/z/ndCnk3">Compiler Explorer</a> can be used to
quickly look at assembly output of your Rust code. That link there will load
up an essentially blank <code>no_std</code> file with <code>opt-level=3</code> set and targeting
<code>thumbv6m-none-eabi</code>. That's <em>not</em> the same as the GBA (it's two ISA revisions
later, ARMv6 instead of ARMv4), but it's the closest CPU target that ships
with rustc, so it's the closest you can get with the compiler explorer
website. If you're very dedicated I suppose you could setup a <a href="https://github.com/mattgodbolt/compiler-explorer#running-a-local-instance">local
instance</a>
of compiler explorer and then add the extra target definition and so on, but
that's <em>probably</em> overkill.</p>
</li>
2018-12-10 19:48:06 +11:00
</ul>
2018-12-30 14:18:09 +11:00
<a class="header" href="#arm-and-thumb" id="arm-and-thumb"><h2>ARM and THUMB</h2></a>
<p>The &quot;T&quot; part in <code>ARMv4T</code> and <code>ARM7TDMI</code> means &quot;Thumb&quot;. An ARM chip that supports
Thumb mode has two different instruction sets instead of just one. The chip can
run in ARM mode with 32-bit instructions, or it can run in THUMB mode with
16-bit instructions. Apparently these modes are sometimes called <code>a32</code> and <code>t32</code>
in a more modern context, but I will stick with ARM and THUMB because that's
what other GBA references use (particularly GBATEK), and it's probably best to
be more in agreement with them than with stuff for Raspberry Pi programming or
whatever other modern ARM thing.</p>
<p>On the GBA, the memory bus that physically transfers data from the game pak into
the device is a 16-bit memory bus. This means that if you need to transfer more
than 16 bits at a time you have to do more than one transfer. Since we'd like
our instructions to get to the CPU as fast as possible, we compile the majority
of our program with the THUMB instruction set. The ARM reference says that with
THUMB instructions on a 16-bit memory bus system you get about 160% performance
compared to using ARM instructions. That's absolutely something we want to take
advantage of. Also, your THUMB compiled code is about 65% of the same code
compiled with ARM. Since a game ROM can only be 32MB total, and we're trying to
fit in images and sound too, we want to get space savings where we can.</p>
<p>You may wonder, why is the THUMB code 65% as large if the instructions
themselves are 50% as large, and why have ARM mode at all if there's such a
benefit to be had with THUMB? Well, THUMB mode doesn't support as many different
instructions as ARM mode does. Some lines of source code that can compile to a
single ARM instruction might need to compile into more than one THUMB
instruction. THUMB still has most of the really good instructions available, so
it all averages out to about 65%.</p>
<p>That said, some parts of a GBA program <em>must</em> be written in ARM mode. Also, ARM
mode does allow that increased instruction flexibility. So we <em>need</em> to use ARM
some of the time, and we might just <em>want</em> to use ARM even when we don't need
to. It is possible to switch modes on the fly, there's extremely minimal
overhead, even less than doing some function calls. The only problem is the
16-bit memory bus of the game pak giving us a needless speed penalty with our
ARM code. The CPU <em>executes</em> the ARM instructions at full speed, but then it has
to wait while more instructions get sent in. What do we do? Well, code is
ultimately just a different kind of data. We can copy parts of our code off the
game pak ROM and place it into a part of the RAM that has a 32-bit memory bus.
Then the CPU can execute the code from there, going at full speed. Of course,
there's only a very small amount of RAM compared to the size of a game pak, so
we'll only do this with a few select functions. Exactly which functions will
probably depend on your game.</p>
<p>One problem with this process is that Rust doesn't currently offer a way to mark
individual functions for being ARM or THUMB. The whole program is compiled in a
single mode. That's not an automatic killer, since we can use the <code>asm!</code> macro
to write some inline assembly, then within our inline assembly we switch from
THUMB to ARM, do some ARM stuff, and switch back to THUMB mode before the inline
assembly is over. Rust is none the wiser to what happened. Yeah, it's clunky,
that's why <a href="https://github.com/rust-embedded/wg/issues/256#issuecomment-439677804">it's on the 2019
wishlist</a>
to fix it (then LLVM can manage it automatically for you).</p>
<p>The bigger problem is that when we do that all of our functions still start off
in THUMB mode, even if they temporarily use ARM mode. For the few bits of code
that must start <em>already in</em> ARM mode, we're stuck. Those parts have to be
written in external assembly files and then included with the linker. We were
already going to write some assembly, and we already use more than one file in
our project all the time, those parts aren't a big problem. The big problem is
that using custom linker scripts isn't transitive between crates.</p>
<p>What I mean is that once we have a file full of custom assembly that we're
linking in by hand, that's not &quot;part of&quot; the crate any more. At least not as
<code>cargo</code> see it. So we can't just upload it to <code>crates.io</code> and then depend on it
in other projects and have <code>cargo</code> download the right version and and include it
all automatically. We're back to fully manually copying files from the old
project into the new one, adding more lines to the linker script each time we
split up a new assembly file, all that stuff. Like the stone age. Sometimes ya
gotta suffer for your art.</p>
2018-11-08 15:20:40 +11:00
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
</nav>
</div>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
<script type="text/javascript">
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
</body>
</html>