mirror of
https://github.com/italicsjenga/gba.git
synced 2025-01-14 20:40:37 +11:00
Merge pull request #36 from rust-console/lokathor
Much improved Quirks chapter, some Concepts work
This commit is contained in:
commit
c666cc114d
43 changed files with 2906 additions and 1195 deletions
.travis.ymlCargo.tomlMakefile.toml
book
examples
src
todo_check.bat
13
.travis.yml
13
.travis.yml
|
@ -9,6 +9,7 @@ rust:
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- rustup component add rust-src
|
- rustup component add rust-src
|
||||||
|
- rustup component add clippy
|
||||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||||
- (test -x $HOME/.cargo/bin/cargo-xbuild || cargo install cargo-xbuild)
|
- (test -x $HOME/.cargo/bin/cargo-xbuild || cargo install cargo-xbuild)
|
||||||
- (test -x $HOME/.cargo/bin/cargo-make || cargo install cargo-make)
|
- (test -x $HOME/.cargo/bin/cargo-make || cargo install cargo-make)
|
||||||
|
@ -27,9 +28,15 @@ script:
|
||||||
- export PATH="$PATH:/opt/devkitpro/devkitARM/bin"
|
- export PATH="$PATH:/opt/devkitpro/devkitARM/bin"
|
||||||
- export PATH="$PATH:/opt/devkitpro/tools/bin"
|
- export PATH="$PATH:/opt/devkitpro/tools/bin"
|
||||||
- cd ..
|
- cd ..
|
||||||
# Test the lib and then compile all examples with `cargo make`
|
# Run all verificaions, both debug and release
|
||||||
- cargo test --lib && cargo test --lib --release
|
- cargo clippy
|
||||||
- cargo make
|
- cargo clippy --release
|
||||||
|
- cargo test --no-fail-fast --lib
|
||||||
|
- cargo test --no-fail-fast --lib --release
|
||||||
|
- cargo test --no-fail-fast --tests
|
||||||
|
- cargo test --no-fail-fast --tests --release
|
||||||
|
# Let cargo make take over the rest
|
||||||
|
- cargo make build-all
|
||||||
# Test build the book so that a failed book build kills this run
|
# Test build the book so that a failed book build kills this run
|
||||||
- cd book && mdbook build
|
- cd book && mdbook build
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,13 @@ license = "Apache-2.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
typenum = "1.10"
|
||||||
gba-proc-macro = "0.2.1"
|
gba-proc-macro = "0.2.1"
|
||||||
|
|
||||||
|
#[dev-dependencies]
|
||||||
|
#quickcheck="0.7"
|
||||||
|
# TODO: F
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
|
@ -55,12 +55,15 @@ fn main() -> std::io::Result<()> {
|
||||||
'''
|
'''
|
||||||
]
|
]
|
||||||
|
|
||||||
[tasks.build]
|
|
||||||
dependencies = ["build-examples-debug", "build-examples-release", "pack-roms"]
|
|
||||||
|
|
||||||
[tasks.test]
|
[tasks.test]
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
args = ["test", "--lib"]
|
args = ["test", "--lib"]
|
||||||
|
|
||||||
|
[tasks.justrelease]
|
||||||
|
dependencies = ["build-examples-release", "pack-roms"]
|
||||||
|
|
||||||
|
[tasks.build-all]
|
||||||
|
dependencies = ["build-examples-debug", "build-examples-release", "pack-roms"]
|
||||||
|
|
||||||
[tasks.default]
|
[tasks.default]
|
||||||
alias = "build"
|
alias = "build-all"
|
||||||
|
|
|
@ -1,115 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
## A basic hello1 explanation
|
|
||||||
|
|
||||||
So, what just happened? Even if you're used to Rust that might look pretty
|
|
||||||
strange. We'll go over most of the little parts right here, and then bigger
|
|
||||||
parts will get their own sections.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#![feature(start)]
|
|
||||||
```
|
|
||||||
|
|
||||||
This enables the [start
|
|
||||||
feature](https://doc.rust-lang.org/beta/unstable-book/language-features/start.html),
|
|
||||||
which you would normally be able to read about in the unstable book, except that
|
|
||||||
the book tells you nothing at all except to look at the [tracking
|
|
||||||
issue](https://github.com/rust-lang/rust/issues/29633).
|
|
||||||
|
|
||||||
Basically, a GBA game is even more low-level than the _normal_ amount of
|
|
||||||
low-level that you get from Rust, so we have to tell the compiler to account for
|
|
||||||
that by specifying a `#[start]`, and we need this feature on to do that.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#![no_std]
|
|
||||||
```
|
|
||||||
|
|
||||||
There's no standard library available on the GBA, so we'll have to live a core
|
|
||||||
only life.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[panic_handler]
|
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This sets our [panic
|
|
||||||
handler](https://doc.rust-lang.org/nightly/nomicon/panic-handler.html).
|
|
||||||
Basically, if we somehow trigger a panic, this is where the program goes.
|
|
||||||
However, right now we don't know how to get any sort of message out to the user
|
|
||||||
so... we do nothing at all. We _can't even return_ from here, so we just sit in
|
|
||||||
an infinite loop. The player will have to reset the universe from the outside.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[start]
|
|
||||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
|
||||||
```
|
|
||||||
|
|
||||||
This is our `#[start]`. We call it `main`, but it's not like a `main` that you'd
|
|
||||||
see in a Rust program. It's _more like_ the sort of `main` that you'd see in a C
|
|
||||||
program, but it's still **not** that either. If you compile a `#[start]` program
|
|
||||||
for a target with an OS such as `arm-none-eabi-nm` you can open up the debug
|
|
||||||
info and see that your result will have the symbol for the C `main` along side
|
|
||||||
the symbol for the start `main` that we write here. Our start `main` is just its
|
|
||||||
own unique thing, and the inputs and outputs have to be like that because that's
|
|
||||||
how `#[start]` is specified to work in Rust.
|
|
||||||
|
|
||||||
If you think about it for a moment you'll probably realize that, those inputs
|
|
||||||
and outputs are totally useless to us on a GBA. There's no OS on the GBA to call
|
|
||||||
our program, and there's no place for our program to "return to" when it's done.
|
|
||||||
|
|
||||||
Side note: if you want to learn more about stuff "before main gets called" you
|
|
||||||
can watch a great [CppCon talk](https://www.youtube.com/watch?v=dOfucXtyEsU) by
|
|
||||||
Matt Godbolt (yes, that Godbolt) where he delves into quite a bit of it. The
|
|
||||||
talk doesn't really apply to the GBA, but it's pretty good.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
unsafe {
|
|
||||||
```
|
|
||||||
|
|
||||||
I hope you're all set for some `unsafe`, because there's a lot of it to be had.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
(0x04000000 as *mut u16).write_volatile(0x0403);
|
|
||||||
```
|
|
||||||
|
|
||||||
Sure!
|
|
||||||
|
|
||||||
```rust
|
|
||||||
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
|
|
||||||
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
|
|
||||||
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
|
|
||||||
```
|
|
||||||
|
|
||||||
Ah, of course.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And, as mentioned above, there's no place for a GBA program to "return to", so
|
|
||||||
we can't ever let `main` try to return there. Instead, we go into an infinite
|
|
||||||
`loop` that does nothing. The fact that this doesn't ever return an `isize`
|
|
||||||
value doesn't seem to bother Rust, because I guess we're at least not returning
|
|
||||||
any other type of thing instead.
|
|
||||||
|
|
||||||
Fun fact: unlike in C++, an infinite loop with no side effects isn't Undefined
|
|
||||||
Behavior for us rustaceans... _semantically_. In truth LLVM has a [known
|
|
||||||
bug](https://github.com/rust-lang/rust/issues/28728) in this area, so we won't
|
|
||||||
actually be relying on empty loops in any future programs.
|
|
||||||
|
|
||||||
## All Those Magic Numbers
|
|
||||||
|
|
||||||
Alright, I cheated quite a bit in the middle there. The program works, but I
|
|
||||||
didn't really tell you why because I didn't really tell you what any of those
|
|
||||||
magic numbers mean or do.
|
|
||||||
|
|
||||||
* `0x04000000` is the address of an IO Register called the Display Control.
|
|
||||||
* `0x06000000` is the start of Video RAM.
|
|
||||||
|
|
||||||
So we write some magic to the display control register once, then we write some
|
|
||||||
other magic to three magic locations in the Video RAM. Somehow that shows three
|
|
||||||
dots. Gotta read on to find out why!
|
|
|
@ -1,132 +0,0 @@
|
||||||
# hello2
|
|
||||||
|
|
||||||
Okay so let's have a look again:
|
|
||||||
|
|
||||||
`hello1`
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#![feature(start)]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
#[panic_handler]
|
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[start]
|
|
||||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
|
||||||
unsafe {
|
|
||||||
(0x04000000 as *mut u16).write_volatile(0x0403);
|
|
||||||
(0x06000000 as *mut u16).offset(120 + 80 * 240).write_volatile(0x001F);
|
|
||||||
(0x06000000 as *mut u16).offset(136 + 80 * 240).write_volatile(0x03E0);
|
|
||||||
(0x06000000 as *mut u16).offset(120 + 96 * 240).write_volatile(0x7C00);
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Now let's clean this up so that it's clearer what's going on.
|
|
||||||
|
|
||||||
First we'll label that display control stuff, including using the `VolatilePtr`
|
|
||||||
type from the volatile explanation:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x04000000 as *mut u16);
|
|
||||||
pub const MODE3: u16 = 3;
|
|
||||||
pub const BG2: u16 = 0b100_0000_0000;
|
|
||||||
```
|
|
||||||
|
|
||||||
Next we make some const values for the actual pixel drawing
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub const VRAM: usize = 0x06000000;
|
|
||||||
pub const SCREEN_WIDTH: isize = 240;
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that VRAM has to be interpreted in different ways depending on mode, so we
|
|
||||||
just leave it as `usize` and we'll cast it into the right form closer to the
|
|
||||||
actual use.
|
|
||||||
|
|
||||||
Next we want a small helper function for putting together a color value.
|
|
||||||
Happily, this one can even be declared as a `const` function. At the time of
|
|
||||||
writing, we've got the "minimal const fn" support in nightly. It really is quite
|
|
||||||
limited, but I'm happy to let rustc and LLVM pre-compute as much as they can
|
|
||||||
when it comes to the GBA's tiny CPU.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
|
||||||
blue << 10 | green << 5 | red
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, we'll make a function for drawing a pixel in Mode 3. Even though it's
|
|
||||||
just a one-liner, having the "important parts" be labeled as function arguments
|
|
||||||
usually helps you think about it a lot better.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
|
|
||||||
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
So now we've got this:
|
|
||||||
|
|
||||||
`hello2`
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#![feature(start)]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
#[panic_handler]
|
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[start]
|
|
||||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
|
||||||
unsafe {
|
|
||||||
DISPCNT.write(MODE3 | BG2);
|
|
||||||
mode3_pixel(120, 80, rgb16(31, 0, 0));
|
|
||||||
mode3_pixel(136, 80, rgb16(0, 31, 0));
|
|
||||||
mode3_pixel(120, 96, rgb16(0, 0, 31));
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct VolatilePtr<T>(pub *mut T);
|
|
||||||
impl<T> VolatilePtr<T> {
|
|
||||||
pub unsafe fn read(&self) -> T {
|
|
||||||
core::ptr::read_volatile(self.0)
|
|
||||||
}
|
|
||||||
pub unsafe fn write(&self, data: T) {
|
|
||||||
core::ptr::write_volatile(self.0, data);
|
|
||||||
}
|
|
||||||
pub unsafe fn offset(self, count: isize) -> Self {
|
|
||||||
VolatilePtr(self.0.wrapping_offset(count))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x04000000 as *mut u16);
|
|
||||||
pub const MODE3: u16 = 3;
|
|
||||||
pub const BG2: u16 = 0b100_0000_0000;
|
|
||||||
|
|
||||||
pub const VRAM: usize = 0x06000000;
|
|
||||||
pub const SCREEN_WIDTH: isize = 240;
|
|
||||||
|
|
||||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
|
||||||
blue << 10 | green << 5 | red
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
|
|
||||||
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Exact same program that we started with, but much easier to read.
|
|
||||||
|
|
||||||
Of course, in the full `gba` crate that this book is a part of we have these and
|
|
||||||
other elements all labeled and sorted out for you (not identically, but
|
|
||||||
similarly). Still, for educational purposes it's often best to do it yourself at
|
|
||||||
least once.
|
|
|
@ -1,10 +0,0 @@
|
||||||
# Ch 1: Hello GBA
|
|
||||||
|
|
||||||
Traditionally a person writes a "hello, world" program so that they can test
|
|
||||||
that their development environment is setup properly and to just get a feel for
|
|
||||||
using the tools involved. To get an idea of what a small part of a source file
|
|
||||||
will look like. All that stuff.
|
|
||||||
|
|
||||||
Normally, you write a program that prints "hello, world" to the terminal. The
|
|
||||||
GBA has no terminal, but it does have a screen, so instead we're going to draw
|
|
||||||
three dots to the screen.
|
|
|
@ -1,70 +0,0 @@
|
||||||
# Volatile
|
|
||||||
|
|
||||||
Before we focus on what the numbers mean, first let's ask ourselves: Why are we
|
|
||||||
doing _volatile_ writes? You've probably never used that keywords before at all.
|
|
||||||
What _is_ volatile anyway?
|
|
||||||
|
|
||||||
Well, the optimizer is pretty aggressive, and so it'll skip reads and writes
|
|
||||||
when it thinks can. Like if you write to a pointer once, and then again a moment
|
|
||||||
later, and it didn't see any other reads in between, it'll think that it can
|
|
||||||
just skip doing that first write since it'll get overwritten anyway. Sometimes
|
|
||||||
that's correct, but sometimes it's not.
|
|
||||||
|
|
||||||
Marking a read or write as _volatile_ tells the compiler that it really must do
|
|
||||||
that action, and in the exact order that we wrote it out. It says that there
|
|
||||||
might even be special hardware side effects going on that the compiler isn't
|
|
||||||
aware of. In this case, the write to the display control register sets a video
|
|
||||||
mode, and the writes to the Video RAM set pixels that will show up on the
|
|
||||||
screen.
|
|
||||||
|
|
||||||
Similar to "atomic" operations you might have heard about, all volatile
|
|
||||||
operations are enforced to happen in the exact order that you specify them, but
|
|
||||||
only relative to other volatile operations. So something like
|
|
||||||
|
|
||||||
```rust
|
|
||||||
c.write_volatile(5);
|
|
||||||
a += b;
|
|
||||||
d.write_volatile(7);
|
|
||||||
```
|
|
||||||
|
|
||||||
might end up changing `a` either before or after the change to `c` (since the
|
|
||||||
value of `a` doesn't affect the write to `c`), but the write to `d` will
|
|
||||||
_always_ happen after the write to `c`, even though the compiler doesn't see any
|
|
||||||
direct data dependency there.
|
|
||||||
|
|
||||||
If you ever go on to use volatile stuff on other platforms it's important to
|
|
||||||
note that volatile doesn't make things thread-safe, you still need atomic for
|
|
||||||
that. However, the GBA doesn't have threads, so we don't have to worry about
|
|
||||||
those sorts of thread safety concerns (there's interrupts, but that's another
|
|
||||||
matter).
|
|
||||||
|
|
||||||
## Volatile by default
|
|
||||||
|
|
||||||
Of course, writing out `volatile_write` every time is more than we wanna do.
|
|
||||||
There's clarity and then there's excessive. This is a chance to write our first
|
|
||||||
[newtype](https://doc.rust-lang.org/1.0.0/style/features/types/newtype.html).
|
|
||||||
Basically a type that's got the exact same binary representation as some other
|
|
||||||
type, but new methods and trait implementations.
|
|
||||||
|
|
||||||
We want a `*mut T` that's volatile by default, and also when we offset it...
|
|
||||||
well the verdict is slightly unclear on how `offset` vs `wrapping_offset` work
|
|
||||||
when you're using pointers that you made up out of nowhere. I've asked the
|
|
||||||
experts and they genuinely weren't sure, so we'll make an `offset` method that
|
|
||||||
does a `wrapping_offset` just to be careful.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct VolatilePtr<T>(pub *mut T);
|
|
||||||
impl<T> VolatilePtr<T> {
|
|
||||||
pub unsafe fn read(&self) -> T {
|
|
||||||
core::ptr::read_volatile(self.0)
|
|
||||||
}
|
|
||||||
pub unsafe fn write(&self, data: T) {
|
|
||||||
core::ptr::write_volatile(self.0, data);
|
|
||||||
}
|
|
||||||
pub unsafe fn offset(self, count: isize) -> Self {
|
|
||||||
VolatilePtr(self.0.wrapping_offset(count))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,22 +0,0 @@
|
||||||
# Ch 2: User Input
|
|
||||||
|
|
||||||
It's all well and good to draw three pixels, but they don't do anything yet. We
|
|
||||||
want them to do something, and for that we need to get some input from the user.
|
|
||||||
|
|
||||||
The GBA, as I'm sure you know, has an arrow pad, A and B, L and R, Start and
|
|
||||||
Select. That's a little more than the NES/GB/CGB had, and a little less than the
|
|
||||||
SNES had. As you can guess, we get key state info from an IO register.
|
|
||||||
|
|
||||||
Also, we will need a way to keep the program from running "too fast". On a
|
|
||||||
modern computer or console you do this with vsync info from the GPU and Monitor,
|
|
||||||
and on the GBA we'll be using vsync info from an IO register that tracks what
|
|
||||||
the display hardware is doing.
|
|
||||||
|
|
||||||
As a way to apply our knowledge We'll make a simple "light cycle" game where
|
|
||||||
your dot leaves a trail behind them and you die if you go off the screen or if
|
|
||||||
you touch your own trail. We just make a copy of `hello2.rs` named
|
|
||||||
`light_cycle.rs` and then fill it in as we go through the chapter. Normally you
|
|
||||||
might not place the entire program into a single source file, particularly as it
|
|
||||||
grows over time, but since these are small examples it's much better to have
|
|
||||||
them be completely self contained than it is to have them be "properly
|
|
||||||
organized" for the long term.
|
|
|
@ -1,256 +0,0 @@
|
||||||
# GBA Memory Mapping
|
|
||||||
|
|
||||||
The [GBA Memory Map](http://problemkaputt.de/gbatek.htm#gbamemorymap) has
|
|
||||||
several memory portions to it, each with their own little differences. Most of
|
|
||||||
the memory has pre-determined use according to the hardware, but there is also
|
|
||||||
space for games to use as a scratch pad in whatever way the game sees fit.
|
|
||||||
|
|
||||||
The memory ranges listed here are _inclusive_, so they end with a lot of F's
|
|
||||||
and E's.
|
|
||||||
|
|
||||||
We've talked about volatile memory before, but just as a reminder I'll say that
|
|
||||||
all of the memory we'll talk about here should be accessed using volatile with
|
|
||||||
two exceptions:
|
|
||||||
|
|
||||||
1) Work RAM (both internal and external) can be used normally, and if the
|
|
||||||
compiler is able to totally elide some reads and writes that's okay.
|
|
||||||
2) However, if you set aside any space in Work RAM where an interrupt will
|
|
||||||
communicate with the main program then that specific location will have to
|
|
||||||
keep using volatile access, since the compiler never knows when an interrupt
|
|
||||||
will actually happen.
|
|
||||||
|
|
||||||
## BIOS / System ROM
|
|
||||||
|
|
||||||
* `0x0` to `0x3FFF` (16k)
|
|
||||||
|
|
||||||
This is special memory for the BIOS. It is "read-only", but even then it's only
|
|
||||||
accessible when the program counter is pointing into the BIOS region. At all
|
|
||||||
other times you get a [garbage
|
|
||||||
value](http://problemkaputt.de/gbatek.htm#gbaunpredictablethings) back when you
|
|
||||||
try to read out of the BIOS.
|
|
||||||
|
|
||||||
## External Work RAM / EWRAM
|
|
||||||
|
|
||||||
* `0x2000000` to `0x203FFFF` (256k)
|
|
||||||
|
|
||||||
This is a big pile of space, the use of which is up to each game. However, the
|
|
||||||
external work ram has only a 16-bit bus (if you read/write a 32-bit value it
|
|
||||||
silently breaks it up into two 16-bit operations) and also 2 wait cycles (extra
|
|
||||||
CPU cycles that you have to expend _per 16-bit bus use_).
|
|
||||||
|
|
||||||
It's most helpful to think of EWRAM as slower, distant memory, similar to the
|
|
||||||
"heap" in a normal application. You can take the time to go store something
|
|
||||||
within EWRAM, or to load it out of EWRAM, but if you've got several operations
|
|
||||||
to do in a row and you're worried about time you should pull that value into
|
|
||||||
local memory, work on your local copy, and then push it back out to EWRAM.
|
|
||||||
|
|
||||||
## Internal Work RAM / IWRAM
|
|
||||||
|
|
||||||
* `0x3000000` to `0x3007FFF` (32k)
|
|
||||||
|
|
||||||
This is a smaller pile of space, but it has a 32-bit bus and no wait.
|
|
||||||
|
|
||||||
By default, `0x3007F00` to `0x3007FFF` is reserved for interrupt and BIOS use.
|
|
||||||
The rest of it is totally up to you. The user's stack space starts at
|
|
||||||
`0x3007F00` and proceeds _down_ from there. For best results you should probably
|
|
||||||
start at `0x3000000` and then go upwards. Under normal use it's unlikely that
|
|
||||||
the two memory regions will crash into each other.
|
|
||||||
|
|
||||||
## IO Registers
|
|
||||||
|
|
||||||
* `0x4000000` to `0x40003FE`
|
|
||||||
|
|
||||||
We've touched upon a few of these so far, and we'll get to more later. At the
|
|
||||||
moment it is enough to say that, as you might have guessed, all of them live in
|
|
||||||
this region. Each individual register is a `u16` or `u32` and they control all
|
|
||||||
sorts of things. We'll actually be talking about some more of them in this very
|
|
||||||
chapter, because that's how we'll control some of the background and object
|
|
||||||
stuff.
|
|
||||||
|
|
||||||
## Palette RAM / PALRAM
|
|
||||||
|
|
||||||
* `0x5000000` to `0x50003FF` (1k)
|
|
||||||
|
|
||||||
Palette RAM has a 16-bit bus, which isn't really a problem because it
|
|
||||||
conceptually just holds `u16` values. There's no automatic wait state, but if
|
|
||||||
you try to access the same location that the display controller is accessing you
|
|
||||||
get bumped by 1 cycle. Since the display controller can use the palette ram any
|
|
||||||
number of times per scanline it's basically impossible to predict if you'll have
|
|
||||||
to do a wait or not during VDraw. During VBlank you won't have any wait of
|
|
||||||
course.
|
|
||||||
|
|
||||||
PALRAM is among the memory where there's weirdness if you try to write just one
|
|
||||||
byte: if you try to write just 1 byte, it writes that byte into _both_ parts of
|
|
||||||
the larger 16-bit location. This doesn't really affect us much with PALRAM,
|
|
||||||
because palette values are all supposed to be `u16` anyway.
|
|
||||||
|
|
||||||
The palette memory actually contains not one, but _two_ sets of palettes. First
|
|
||||||
there's 256 entries for the background palette data (starting at `0x5000000`),
|
|
||||||
and then there's 256 entries for object palette data (starting at `0x5000200`).
|
|
||||||
|
|
||||||
The GBA also has two modes for palette access: 8-bits-per-pixel (8bpp) and
|
|
||||||
4-bits-per-pixel (4bpp).
|
|
||||||
|
|
||||||
* In 8bpp mode an 8-bit palette index value within a background or sprite
|
|
||||||
simply indexes directly into the 256 slots for that type of thing.
|
|
||||||
* In 4bpp mode a 4-bit palette index value within a background or sprite
|
|
||||||
specifies an index within a particular "palbank" (16 palette entries each),
|
|
||||||
and then a _separate_ setting outside of the graphical data determines which
|
|
||||||
palbank is to be used for that background or object (the screen entry data for
|
|
||||||
backgrounds, and the object attributes for objects).
|
|
||||||
|
|
||||||
### Transparency
|
|
||||||
|
|
||||||
When a pixel within a background or object specifies index 0 as its palette
|
|
||||||
entry it is treated as a transparent pixel. This means that in 8bpp mode there's
|
|
||||||
only 255 actual color options (0 being transparent), and in 4bpp mode there's
|
|
||||||
only 15 actual color options available within each palbank (the 0th entry of
|
|
||||||
_each_ palbank is transparent).
|
|
||||||
|
|
||||||
Individual backgrounds, and individual objects, each determine if they're 4bpp
|
|
||||||
or 8bpp separately, so a given overall palette slot might map to a used color in
|
|
||||||
8bpp and an unused/transparent color in 4bpp. If you're a palette wizard.
|
|
||||||
|
|
||||||
Palette slot 0 of the overall background palette is used to determine the
|
|
||||||
"backdrop" color. That's the color you see if no background or object ends up
|
|
||||||
being rendered within a given pixel.
|
|
||||||
|
|
||||||
Since display mode 3 and display mode 5 don't use the palette, they cannot
|
|
||||||
benefit from transparency.
|
|
||||||
|
|
||||||
## Video RAM / VRAM
|
|
||||||
|
|
||||||
* `0x6000000` to `0x6017FFF` (96k)
|
|
||||||
|
|
||||||
We've used this before! VRAM has a 16-bit bus and no wait. However, the same as
|
|
||||||
with PALRAM, the "you might have to wait if the display controller is looking at
|
|
||||||
it" rule applies here.
|
|
||||||
|
|
||||||
Unfortunately there's not much more exact detail that can be given about VRAM.
|
|
||||||
The use of the memory depends on the video mode that you're using.
|
|
||||||
|
|
||||||
One general detail of note is that you can't write individual bytes to any part
|
|
||||||
of VRAM. Depending on mode and location, you'll either get your bytes doubled
|
|
||||||
into both the upper and lower parts of the 16-bit location targeted, or you
|
|
||||||
won't even affect the memory. This usually isn't a big deal, except in two
|
|
||||||
situations:
|
|
||||||
|
|
||||||
* In Mode 4, if you want to change just 1 pixel, you'll have to be very careful
|
|
||||||
to read the old `u16`, overwrite just the byte you wanted to change, and then
|
|
||||||
write that back.
|
|
||||||
* In any display mode, avoid using `memcopy` to place things into VRAM.
|
|
||||||
It's written to be byte oriented, and only does 32-bit transfers under select
|
|
||||||
conditions. The rest of the time it'll copy one byte at a time and you'll get
|
|
||||||
either garbage or nothing at all.
|
|
||||||
|
|
||||||
## Object Attribute Memory / OAM
|
|
||||||
|
|
||||||
* `0x7000000` to `0x70003FF` (1k)
|
|
||||||
|
|
||||||
The Object Attribute Memory has a 32-bit bus and no default wait, but suffers
|
|
||||||
from the "you might have to wait if the display controller is looking at it"
|
|
||||||
rule. You cannot write individual bytes to OAM at all, but that's not really a
|
|
||||||
problem because all the fields of the data types within OAM are either `i16` or
|
|
||||||
`u16` anyway.
|
|
||||||
|
|
||||||
Object attribute memory is the wildest yet: it conceptually contains two types
|
|
||||||
of things, but they're _interlaced_ with each other all the way through.
|
|
||||||
|
|
||||||
Now, [GBATEK](http://problemkaputt.de/gbatek.htm#lcdobjoamattributes) and
|
|
||||||
[CowByte](https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#OAM%20(sprites))
|
|
||||||
doesn't quite give names to the two data types here.
|
|
||||||
[TONC](https://www.coranac.com/tonc/text/regobj.htm#sec-oam) calls them
|
|
||||||
`OBJ_ATTR` and `OBJ_AFFINE`, but we'll be giving them names fitting with the
|
|
||||||
Rust naming convention. Just know that if you try to talk about it with others
|
|
||||||
they might not be using the same names. In Rust terms their layout would look
|
|
||||||
like this:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ObjectAttributes {
|
|
||||||
attr0: u16,
|
|
||||||
attr1: u16,
|
|
||||||
attr2: u16,
|
|
||||||
filler: i16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct AffineMatrix {
|
|
||||||
filler0: [u16; 3],
|
|
||||||
pa: i16,
|
|
||||||
filler1: [u16; 3],
|
|
||||||
pb: i16,
|
|
||||||
filler2: [u16; 3],
|
|
||||||
pc: i16,
|
|
||||||
filler3: [u16; 3],
|
|
||||||
pd: i16,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
(Note: the `#[repr(C)]` part just means that Rust must lay out the data exactly
|
|
||||||
in the order we specify, which otherwise it is not required to do).
|
|
||||||
|
|
||||||
So, we've got 1024 bytes in OAM and each `ObjectAttributes` value is 8 bytes, so
|
|
||||||
naturally we can support up to 128 objects.
|
|
||||||
|
|
||||||
_At the same time_, we've got 1024 bytes in OAM and each `AffineMatrix` is 32
|
|
||||||
bytes, so we can have 32 of them.
|
|
||||||
|
|
||||||
But, as I said, these things are all _interlaced_ with each other. See how
|
|
||||||
there's "filler" fields in each struct? If we imagine the OAM as being just an
|
|
||||||
array of one type or the other, indexes 0/1/2/3 of the `ObjectAttributes` array
|
|
||||||
would line up with index 0 of the `AffineMatrix` array. It's kinda weird, but
|
|
||||||
that's just how it works. When we setup functions to read and write these values
|
|
||||||
we'll have to be careful with how we do it. We probably _won't_ want to use
|
|
||||||
those representations above, at least not with the `AffineMatrix` type, because
|
|
||||||
they're quite wasteful if you want to store just object attributes or just
|
|
||||||
affine matrices.
|
|
||||||
|
|
||||||
## Game Pak ROM / Flash ROM
|
|
||||||
|
|
||||||
* `0x8000000` to `0x9FFFFFF` (wait 0)
|
|
||||||
* `0xA000000` to `0xBFFFFFF` (wait 1)
|
|
||||||
* `0xC000000` to `0xDFFFFFF` (wait 2)
|
|
||||||
* Max of 32Mb
|
|
||||||
|
|
||||||
These portions of the memory are less fixed, because they depend on the precise
|
|
||||||
details of the game pak you've inserted into the GBA. In general, they connect
|
|
||||||
to the game pak ROM and/or Flash memory, using a 16-bit bus. The ROM is
|
|
||||||
read-only, but the Flash memory (if any) allows writes.
|
|
||||||
|
|
||||||
The game pak ROM is listed as being in three sections, but it's actually the
|
|
||||||
same memory being effectively mirrored into three different locations. The
|
|
||||||
mirror that you choose to access the game pak through affects which wait state
|
|
||||||
setting it uses (configured via IO register of course). Unfortunately, the
|
|
||||||
details come down more to the game pak hardware that you load your game onto
|
|
||||||
than anything else, so there's not much I can say right here. We'll eventually
|
|
||||||
talk about it more later when I'm forced to do the boring thing and just cover
|
|
||||||
all the IO registers that aren't covered anywhere else.
|
|
||||||
|
|
||||||
One thing of note is the way that the 16-bit bus affects us: the instructions to
|
|
||||||
execute are coming through the same bus as the rest of the game data, so we want
|
|
||||||
them to be as compact as possible. The ARM chip in the GBA supports two
|
|
||||||
different instruction sets, "thumb" and "non-thumb". The thumb mode instructions
|
|
||||||
are 16-bit, so they can each be loaded one at a time, and the non-thumb
|
|
||||||
instructions are 32-bit, so we're at a penalty if we execute them directly out
|
|
||||||
of the game pak. However, some things will demand that we use non-thumb code, so
|
|
||||||
we'll have to deal with that eventually. It's possible to switch between modes,
|
|
||||||
but it's a pain to keep track of what mode you're in because there's not
|
|
||||||
currently support for it in Rust itself (perhaps some day). So we'll stick with
|
|
||||||
thumb code as much as we possibly can, that's why our target profile for our
|
|
||||||
builds starts with `thumbv4`.
|
|
||||||
|
|
||||||
## Game Pak SRAM
|
|
||||||
|
|
||||||
* `0xE000000` to `0xE00FFFF` (64k)
|
|
||||||
|
|
||||||
The game pak SRAM has an 8-bit bus. Why did Pokémon always take so long to save?
|
|
||||||
Saving the whole game one byte at a time is why. The SRAM also has some amount
|
|
||||||
of wait, but as with the ROM, the details depend on your game pak hardware (and
|
|
||||||
also as with ROM, you can adjust the settings with an IO register, should you
|
|
||||||
need to).
|
|
||||||
|
|
||||||
One thing to note about the SRAM is that the GBA has a Direct Memory Access
|
|
||||||
(DMA) feature that can be used for bulk memory movements in some cases, but the
|
|
||||||
DMA _cannot_ access the SRAM region. You really are stuck reading and writing
|
|
||||||
one byte at a time when you're using the SRAM.
|
|
|
@ -26,9 +26,19 @@ available while you're debugging problems.
|
||||||
|
|
||||||
## Information Resources
|
## Information Resources
|
||||||
|
|
||||||
Ketsuban and I didn't magically learn this all from nowhere, we read various
|
First, if I fail to describe something related to Rust, you can always try
|
||||||
technical manuals and guides ourselves and then distilled the knowledge (usually
|
checking in [The Rust
|
||||||
oriented towards C and C++) into this book for Rust.
|
Reference](https://doc.rust-lang.org/nightly/reference/introduction.html) to see
|
||||||
|
if they cover it. You can mostly ignore that big scary red banner at the top,
|
||||||
|
things are a lot better documented than they make it sound.
|
||||||
|
|
||||||
|
If you need help trying to fiddle your math down as hard as you can, there are
|
||||||
|
resources such as the [Bit Twiddling
|
||||||
|
Hacks](https://graphics.stanford.edu/~seander/bithacks.html) page.
|
||||||
|
|
||||||
|
As to GBA related lore, Ketsuban and I didn't magically learn this all from
|
||||||
|
nowhere, we read various technical manuals and guides ourselves and then
|
||||||
|
distilled those works oriented around C and C++ into a book for Rust.
|
||||||
|
|
||||||
We have personally used some or all of the following:
|
We have personally used some or all of the following:
|
||||||
|
|
||||||
|
|
|
@ -89,10 +89,6 @@ the standard library types to be used "for free" once it was set up, or just a
|
||||||
custom allocator that's GBA specific if Rust's global allocator style isn't a
|
custom allocator that's GBA specific if Rust's global allocator style isn't a
|
||||||
good fit for the GBA (I honestly haven't looked into it).
|
good fit for the GBA (I honestly haven't looked into it).
|
||||||
|
|
||||||
## LLVM Intrinsics
|
|
||||||
|
|
||||||
TODO: explain that we'll occasionally have to provide some intrinsics.
|
|
||||||
|
|
||||||
## Bare Metal Panic
|
## Bare Metal Panic
|
||||||
|
|
||||||
TODO: expand this
|
TODO: expand this
|
||||||
|
@ -114,3 +110,10 @@ TODO: expand this
|
||||||
* Sending the message also automatically zeroes the output buffer.
|
* Sending the message also automatically zeroes the output buffer.
|
||||||
* View the output within the "Tools" menu, "View Logs...". Note that the Fatal
|
* View the output within the "Tools" menu, "View Logs...". Note that the Fatal
|
||||||
message, if any doesn't get logged.
|
message, if any doesn't get logged.
|
||||||
|
|
||||||
|
TODO: this will probably fail without a `__clzsi2` implementation, which is a
|
||||||
|
good seg for the next section
|
||||||
|
|
||||||
|
## LLVM Intrinsics
|
||||||
|
|
||||||
|
TODO: explain that we'll occasionally have to provide some intrinsics.
|
||||||
|
|
|
@ -1,13 +1,548 @@
|
||||||
# Fixed Only
|
# Fixed Only
|
||||||
|
|
||||||
In addition to not having the standard library available, we don't even have a
|
In addition to not having much of the standard library available, we don't even
|
||||||
floating point unit available! We can't do floating point math in hardware! We
|
have a floating point unit available! We can't do floating point math in
|
||||||
could still do floating point math as software computations if we wanted, but
|
hardware! We _could_ still do floating point math as pure software computations
|
||||||
that's a slow, slow thing to do.
|
if we wanted, but that's a slow, slow thing to do.
|
||||||
|
|
||||||
Instead let's learn about another way to have fractional values called "Fixed
|
Are there faster ways? It's the same answer as always: "Yes, but not without a
|
||||||
Point"
|
tradeoff."
|
||||||
|
|
||||||
## Fixed Point
|
The faster way is to represent fractional values using a system called a [Fixed
|
||||||
|
Point Representation](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
|
||||||
|
What do we trade away? Numeric range.
|
||||||
|
|
||||||
TODO: describe fixed point, make some types, do the impls, all that.
|
* Floating point math stores bits for base value and for exponent all according
|
||||||
|
to a single [well defined](https://en.wikipedia.org/wiki/IEEE_754) standard
|
||||||
|
for how such a complicated thing works.
|
||||||
|
* Fixed point math takes a normal integer (either signed or unsigned) and then
|
||||||
|
just "mentally associates" it (so to speak) with a fractional value for its
|
||||||
|
"units". If you have 3 and it's in units of 1/2, then you have 3/2, or 1.5
|
||||||
|
using decimal notation. If your number is 256 and it's in units of 1/256th
|
||||||
|
then the value is 1.0 in decimal notation.
|
||||||
|
|
||||||
|
Floating point math requires dedicated hardware to perform quickly, but it can
|
||||||
|
"trade" precision when it needs to represent extremely large or small values.
|
||||||
|
|
||||||
|
Fixed point math is just integral math, which our GBA is reasonably good at, but
|
||||||
|
because your number is associated with a fixed fraction your results can get out
|
||||||
|
of range very easily.
|
||||||
|
|
||||||
|
## Representing A Fixed Point Value
|
||||||
|
|
||||||
|
So we want to associate our numbers with a mental note of what units they're in:
|
||||||
|
|
||||||
|
* [PhantomData](https://doc.rust-lang.org/core/marker/struct.PhantomData.html)
|
||||||
|
is a type that tells the compiler "please remember this extra type info" when
|
||||||
|
you add it as a field to a struct. It goes away at compile time, so it's
|
||||||
|
perfect for us to use as space for a note to ourselves without causing runtime
|
||||||
|
overhead.
|
||||||
|
* The [typenum](https://crates.io/crates/typenum) crate is the best way to
|
||||||
|
represent a number within a type in Rust. Since our values on the GBA are
|
||||||
|
always specified as a number of fractional bits to count the number as, we can
|
||||||
|
put `typenum` types such as `U8` or `U14` into our `PhantomData` to keep track
|
||||||
|
of what's going on.
|
||||||
|
|
||||||
|
Now, those of you who know me, or perhaps just know my reputation, will of
|
||||||
|
course _immediately_ question what happened to the real Lokathor. I do not care
|
||||||
|
for most crates, and I particularly don't care for using a crate in teaching
|
||||||
|
situations. However, `typenum` has a number of factors on its side that let me
|
||||||
|
suggest it in this situation:
|
||||||
|
|
||||||
|
* It's version 1.10 with a total of 21 versions and nearly 700k downloads, so we
|
||||||
|
can expect that the major troubles have been shaken out and that it will remain
|
||||||
|
fairly stable for quite some time to come.
|
||||||
|
* It has no further dependencies that it's going to drag into the compilation.
|
||||||
|
* It happens all at compile time, so it's not clogging up our actual game with
|
||||||
|
any nonsense.
|
||||||
|
* The (interesting) subject of "how do you do math inside Rust's trait system?" is
|
||||||
|
totally separate from the concern that we're trying to focus on here.
|
||||||
|
|
||||||
|
Therefore, we will consider it acceptable to use this crate.
|
||||||
|
|
||||||
|
Now the `typenum` crate defines a whole lot, but we'll focus down to just a
|
||||||
|
single type at the moment:
|
||||||
|
[UInt](https://docs.rs/typenum/1.10.0/typenum/uint/struct.UInt.html) is a
|
||||||
|
type-level unsigned value. It's like `u8` or `u16`, but while they're types that
|
||||||
|
then have values, each `UInt` construction statically equates to a specific
|
||||||
|
value. Like how the `()` type only has one value, which is also called `()`. In
|
||||||
|
this case, you wrap up `UInt` around smaller `UInt` values and a `B1` or `B0`
|
||||||
|
value to build up the binary number that you want at the type level.
|
||||||
|
|
||||||
|
In other words, instead of writing
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let six = 0b110;
|
||||||
|
```
|
||||||
|
|
||||||
|
We write
|
||||||
|
|
||||||
|
```rust
|
||||||
|
type U6 = UInt<UInt<UInt<UTerm, B1>, B1>, B0>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Wild, I know. If you look into the `typenum` crate you can do math and stuff
|
||||||
|
with these type level numbers, and we will a little bit below, but to start off
|
||||||
|
we _just_ need to store one in some `PhantomData`.
|
||||||
|
|
||||||
|
### A struct For Fixed Point
|
||||||
|
|
||||||
|
Our actual type for a fixed point value looks like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use typenum::marker_traits::Unsigned;
|
||||||
|
|
||||||
|
/// Fixed point `T` value with `F` fractional bits.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Fx<T, F: Unsigned> {
|
||||||
|
bits: T,
|
||||||
|
_phantom: PhantomData<F>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This says that `Fx<T,F>` is a generic type that holds some base number type `T`
|
||||||
|
and a `F` type that's marking off how many fractional bits we're using. We only
|
||||||
|
want people giving unsigned type-level values for the `PhantomData` type, so we
|
||||||
|
use the trait bound `F: Unsigned`.
|
||||||
|
|
||||||
|
We use
|
||||||
|
[repr(transparent)](https://github.com/rust-lang/rfcs/blob/master/text/1758-repr-transparent.md)
|
||||||
|
here to ensure that `Fx` will always be treated just like the base type in the
|
||||||
|
final program (in terms of bit pattern and ABI).
|
||||||
|
|
||||||
|
If you go and check, this is _basically_ how the existing general purpose crates
|
||||||
|
for fixed point math represent their numbers. They're a little fancier about it
|
||||||
|
because they have to cover every case, and we only have to cover our GBA case.
|
||||||
|
|
||||||
|
That's quite a bit to type though. We probably want to make a few type aliases
|
||||||
|
for things to be easier to look at. Unfortunately there's [no standard
|
||||||
|
notation](https://en.wikipedia.org/wiki/Fixed-point_arithmetic#Notation) for how
|
||||||
|
you write a fixed point type. We also have to limit ourselves to what's valid
|
||||||
|
for use in a Rust type too. I like the `fx` thing, so we'll use that for signed
|
||||||
|
and then `fxu` if we need an unsigned value.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Alias for an `i16` fixed point value with 8 fractional bits.
|
||||||
|
pub type fx8_8 = Fx<i16,U8>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Rust will complain about having `non_camel_case_types`, and you can shut that
|
||||||
|
warning up by putting an `#[allow(non_camel_case_types)]` attribute on the type
|
||||||
|
alias directly, or you can use `#![allow(non_camel_case_types)]` at the very top
|
||||||
|
of the module to shut up that warning for the whole module (which is what I
|
||||||
|
did).
|
||||||
|
|
||||||
|
## Constructing A Fixed Point Value
|
||||||
|
|
||||||
|
So how do we actually _make_ one of these values? Well, we can always just wrap or unwrap any value in our `Fx` type:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl<T, F: Unsigned> Fx<T, F> {
|
||||||
|
/// Uses the provided value directly.
|
||||||
|
pub fn from_raw(r: T) -> Self {
|
||||||
|
Fx {
|
||||||
|
num: r,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Unwraps the inner value.
|
||||||
|
pub fn into_raw(self) -> T {
|
||||||
|
self.num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
I'd like to use the `From` trait of course, but it was giving me some trouble, i
|
||||||
|
think because of the orphan rule. Oh well.
|
||||||
|
|
||||||
|
If we want to be particular to the fact that these are supposed to be
|
||||||
|
_numbers_... that gets tricky. Rust is actually quite bad at being generic about
|
||||||
|
number types. You can use the [num](https://crates.io/crates/num) crate, or you
|
||||||
|
can just use a macro and invoke it once per type. Guess what we're gonna do.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! fixed_point_methods {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Fx<$t, F> {
|
||||||
|
/// Gives the smallest positive non-zero value.
|
||||||
|
pub fn precision() -> Self {
|
||||||
|
Fx {
|
||||||
|
num: 1,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a value with the integer part shifted into place.
|
||||||
|
pub fn from_int_part(i: $t) -> Self {
|
||||||
|
Fx {
|
||||||
|
num: i << F::U8,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_methods! {u8}
|
||||||
|
fixed_point_methods! {i8}
|
||||||
|
fixed_point_methods! {i16}
|
||||||
|
fixed_point_methods! {u16}
|
||||||
|
fixed_point_methods! {i32}
|
||||||
|
fixed_point_methods! {u32}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now _you'd think_ that those can be `const`, but at the moment you can't have a
|
||||||
|
`const` function with a bound on any trait other than `Sized`, so they have to
|
||||||
|
be normal functions.
|
||||||
|
|
||||||
|
Also, we're doing something a little interesting there with `from_int_part`. We
|
||||||
|
can take our `F` type and get its constant value. There's other associated
|
||||||
|
constants if we want it in other types, and also non-const methods if you wanted
|
||||||
|
that for some reason (maybe passing it as a closure function? dunno).
|
||||||
|
|
||||||
|
## Casting Base Values
|
||||||
|
|
||||||
|
Next, once we have a value in one base type we will need to be able to move it
|
||||||
|
into another base type. Unfortunately this means we gotta use the `as` operator,
|
||||||
|
which requires a concrete source type and a concrete destination type. There's
|
||||||
|
no easy way for us to make it generic here.
|
||||||
|
|
||||||
|
We could let the user use `into_raw`, cast, and then do `from_raw`, but that's
|
||||||
|
error prone because they might change the fractional bit count accidentally.
|
||||||
|
This means that we have to write a function that does the casting while
|
||||||
|
perfectly preserving the fractional bit quantity. If we wrote one function for
|
||||||
|
each conversion it'd be like 30 different possible casts (6 base types that we
|
||||||
|
support, and then 5 possible target types). Instead, we'll write it just once in
|
||||||
|
a way that takes a closure, and let the user pass a closure that does the cast.
|
||||||
|
The compiler should merge it all together quite nicely for us once optimizations
|
||||||
|
kick in.
|
||||||
|
|
||||||
|
This code goes outside the macro. I want to avoid too much code in the macro if
|
||||||
|
we can, it's a little easier to cope with I think.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Casts the base type, keeping the fractional bit quantity the same.
|
||||||
|
pub fn cast_inner<Z, C: Fn(T) -> Z>(self, op: C) -> Fx<Z, F> {
|
||||||
|
Fx {
|
||||||
|
num: op(self.num),
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It's horrible and ugly, but Rust is just bad at numbers sometimes.
|
||||||
|
|
||||||
|
## Adjusting Fractional Part
|
||||||
|
|
||||||
|
In addition to the base value we might want to change our fractional bit
|
||||||
|
quantity. This is actually easier that it sounds, but it also requires us to be
|
||||||
|
tricky with the generics. We can actually use some typenum type level operators
|
||||||
|
here.
|
||||||
|
|
||||||
|
This code goes inside the macro: we need to be able to use the left shift and
|
||||||
|
right shift, which is easiest when we just use the macro's `$t` as our type. We
|
||||||
|
could alternately put a similar function outside the macro and be generic on `T`
|
||||||
|
having the left and right shift operators by using a `where` clause. As much as
|
||||||
|
I'd like to avoid too much code being generated by macro, I'd _even more_ like
|
||||||
|
to avoid generic code with huge and complicated trait bounds. It comes down to
|
||||||
|
style, and you gotta decide for yourself.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Changes the fractional bit quantity, keeping the base type the same.
|
||||||
|
pub fn adjust_fractional_bits<Y: Unsigned + IsEqual<F, Output = False>>(self) -> Fx<$t, Y> {
|
||||||
|
let leftward_movement: i32 = Y::to_i32() - F::to_i32();
|
||||||
|
Fx {
|
||||||
|
num: if leftward_movement > 0 {
|
||||||
|
self.num << leftward_movement
|
||||||
|
} else {
|
||||||
|
self.num >> (-leftward_movement)
|
||||||
|
},
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There's a few things at work. First, we introduce `Y` as the target number of
|
||||||
|
fractional bits, and we _also_ limit it that the target bits quantity can't be
|
||||||
|
the same as we already have using a type-level operator. If it's the same as we
|
||||||
|
started with, why are you doing the cast at all?
|
||||||
|
|
||||||
|
Now, once we're sure that the current bits and target bits aren't the same, we
|
||||||
|
compute `target - start`, and call this our "leftward movement". Example: if
|
||||||
|
we're targeting 8 bits and we're at 4 bits, we do 8-4 and get +4 as our leftward
|
||||||
|
movement. If the leftward_movement is positive we naturally shift our current
|
||||||
|
value to the left. If it's not positive then it _must_ be negative because we
|
||||||
|
eliminated 0 as a possibility using the type-level operator, so we shift to the
|
||||||
|
right by the negative value.
|
||||||
|
|
||||||
|
## Addition, Subtraction, Shifting, Negative, Comparisons
|
||||||
|
|
||||||
|
From here on we're getting help from [this blog
|
||||||
|
post](https://spin.atomicobject.com/2012/03/15/simple-fixed-point-math/) by [Job
|
||||||
|
Vranish](https://spin.atomicobject.com/author/vranish/), so thank them if you
|
||||||
|
learn something.
|
||||||
|
|
||||||
|
I might have given away the game a bit with those `derive` traits on our fixed
|
||||||
|
point type. For a fair number of operations you can use the normal form of the
|
||||||
|
op on the inner bits as long as the fractional parts have the same quantity.
|
||||||
|
This includes equality and ordering (which we derived) as well as addition,
|
||||||
|
subtraction, and bit shifting (which we need to do ourselves).
|
||||||
|
|
||||||
|
This code can go outside the macro, with sufficient trait bounds.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl<T: Add<Output = T>, F: Unsigned> Add for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Fx<T, F>) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: self.num + rhs.num,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The bound on `T` makes it so that `Fx<T, F>` can be added any time that `T` can
|
||||||
|
be added to its own type with itself as the output. We can use the exact same
|
||||||
|
pattern for `Sub`, `Shl`, `Shr`, and `Neg`. With enough trait bounds, we can do
|
||||||
|
anything!
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl<T: Sub<Output = T>, F: Unsigned> Sub for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn sub(self, rhs: Fx<T, F>) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: self.num - rhs.num,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Shl<u32, Output = T>, F: Unsigned> Shl<u32> for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn shl(self, rhs: u32) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: self.num << rhs,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Shr<u32, Output = T>, F: Unsigned> Shr<u32> for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn shr(self, rhs: u32) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: self.num >> rhs,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Neg<Output = T>, F: Unsigned> Neg for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: -self.num,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Unfortunately, for `Shl` and `Shr` to have as much coverage on our type as it
|
||||||
|
does on the base type (allowing just about any right hand side) we'd have to do
|
||||||
|
another macro, but I think just `u32` is fine. We can always add more later if
|
||||||
|
we need.
|
||||||
|
|
||||||
|
We could also implement `BitAnd`, `BitOr`, `BitXor`, and `Not`, but they don't
|
||||||
|
seem relevent to our fixed point math use, and this section is getting long
|
||||||
|
already. Just use the same general patterns if you want to add it in your own
|
||||||
|
programs. Shockingly, `Rem` also works directly if you want it, though I don't
|
||||||
|
forsee us needing floating point remainder. Also, the GBA can't do hardware
|
||||||
|
division or remainder, and we'll have to work around that below when we
|
||||||
|
implement `Div` (which maybe we don't need, but it's complex enough I should
|
||||||
|
show it instead of letting people guess).
|
||||||
|
|
||||||
|
**Note:** In addition to the various `Op` traits, there's also `OpAssign`
|
||||||
|
variants. Each `OpAssign` is the same as `Op`, but takes `&mut self` instead of
|
||||||
|
`self` and then modifies in place instead of producing a fresh value. In other
|
||||||
|
words, if you want both `+` and `+=` you'll need to do the `AddAssign` trait
|
||||||
|
too. It's not the worst thing to just write `a = a+b`, so I won't bother with
|
||||||
|
showing all that here. It's pretty easy to figure out for yourself if you want.
|
||||||
|
|
||||||
|
## Multiplication
|
||||||
|
|
||||||
|
This is where things get more interesting. When we have two numbers `A` and `B`
|
||||||
|
they really stand for `(a*f)` and `(b*f)`. If we write `A*B` then we're really
|
||||||
|
writing `(a*f)*(b*f)`, which can be rewritten as `(a*b)*2f`, and now it's
|
||||||
|
obvious that we have one more `f` than we wanted to have. We have to do the
|
||||||
|
multiply of the inner value and then divide out the `f`. We divide by `1 <<
|
||||||
|
bit_count`, so if we have 8 fractional bits we'll divide by 256.
|
||||||
|
|
||||||
|
The catch is that, when we do the multiply we're _extremely_ likely to overflow
|
||||||
|
our base type with that multiplication step. Then we do that divide, and now our
|
||||||
|
result is basically nonsense. We can avoid this to some extent by casting up to
|
||||||
|
a higher bit type, doing the multiplication and division at higher precision,
|
||||||
|
and then casting back down. We want as much precision as possible without being
|
||||||
|
too inefficient, so we'll always cast up to 32-bit (on a 64-bit machine you'd
|
||||||
|
cast up to 64-bit instead).
|
||||||
|
|
||||||
|
Naturally, any signed value has to be cast up to `i32` and any unsigned value
|
||||||
|
has to be cast up to `u32`, so we'll have to handle those separately.
|
||||||
|
|
||||||
|
Also, instead of doing an _actual_ divide we can right-shift by the correct
|
||||||
|
number of bits to achieve the same effect. _Except_ when we have a signed value
|
||||||
|
that's negative, because actual division truncates towards zero and
|
||||||
|
right-shifting truncates towards negative infinity. We can get around _this_ by
|
||||||
|
flipping the sign, doing the shift, and flipping the sign again (which sounds
|
||||||
|
silly but it's so much faster than doing an actual division).
|
||||||
|
|
||||||
|
Also, again signed values can be annoying, because if the value _just happens_
|
||||||
|
to be `i32::MIN` then when you negate it you'll have... _still_ a negative
|
||||||
|
value. I'm not 100% on this, but I think the correct thing to do at that point
|
||||||
|
is to give `$t::MIN` as out output num value.
|
||||||
|
|
||||||
|
Did you get all that? Good, because this is involves casting, we will need to
|
||||||
|
implement it three times, which calls for another macro.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! fixed_point_signed_multiply {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Mul for Fx<$t, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
|
let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32);
|
||||||
|
if pre_shift < 0 {
|
||||||
|
if pre_shift == core::i32::MIN {
|
||||||
|
Fx {
|
||||||
|
num: core::$t::MIN,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Fx {
|
||||||
|
num: (-((-pre_shift) >> F::U8)) as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Fx {
|
||||||
|
num: (pre_shift >> F::U8) as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_signed_multiply! {i8}
|
||||||
|
fixed_point_signed_multiply! {i16}
|
||||||
|
fixed_point_signed_multiply! {i32}
|
||||||
|
|
||||||
|
macro_rules! fixed_point_unsigned_multiply {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Mul for Fx<$t, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: ((self.num as u32).wrapping_mul(rhs.num as u32) >> F::U8) as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_unsigned_multiply! {u8}
|
||||||
|
fixed_point_unsigned_multiply! {u16}
|
||||||
|
fixed_point_unsigned_multiply! {u32}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Division
|
||||||
|
|
||||||
|
Division is similar to multiplication, but reversed. Which makes sense. This
|
||||||
|
time `A/B` gives `(a*f)/(b*f)` which is `a/b`, one _less_ `f` than we were
|
||||||
|
after.
|
||||||
|
|
||||||
|
As with the multiplication version of things, we have to up-cast our inner value
|
||||||
|
as much a we can before doing the math, to allow for the most precision
|
||||||
|
possible.
|
||||||
|
|
||||||
|
The snag here is that the GBA has no division or remainder. Instead, the GBA has
|
||||||
|
a BIOS function you can call to do `i32/i32` division.
|
||||||
|
|
||||||
|
This is a potential problem for us though. If we have some unsigned value, we
|
||||||
|
need it to fit within the positive space of an `i32` _after the multiply_ so
|
||||||
|
that we can cast it to `i32`, call the BIOS function that only works on `i32`
|
||||||
|
values, and cast it back to its actual type.
|
||||||
|
|
||||||
|
* If you have a u8 you're always okay, even with 8 floating bits.
|
||||||
|
* If you have a u16 you're okay even with a maximum value up to 15 floating
|
||||||
|
bits, but having a maximum value and 16 floating bits makes it break.
|
||||||
|
* If you have a u32 you're probably going to be in trouble all the time.
|
||||||
|
|
||||||
|
So... ugh, there's not much we can do about this. For now we'll just have to
|
||||||
|
suffer some.
|
||||||
|
|
||||||
|
// TODO: find a numerics book that tells us how to do `u32/u32` divisions.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! fixed_point_signed_division {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Div for Fx<$t, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
|
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
||||||
|
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
||||||
|
Fx {
|
||||||
|
num: divide_result as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_signed_division! {i8}
|
||||||
|
fixed_point_signed_division! {i16}
|
||||||
|
fixed_point_signed_division! {i32}
|
||||||
|
|
||||||
|
macro_rules! fixed_point_unsigned_division {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Div for Fx<$t, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
|
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
||||||
|
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
||||||
|
Fx {
|
||||||
|
num: divide_result as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_unsigned_division! {u8}
|
||||||
|
fixed_point_unsigned_division! {u16}
|
||||||
|
fixed_point_unsigned_division! {u32}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trigonometry
|
||||||
|
|
||||||
|
TODO: look up tables! arcbits!
|
||||||
|
|
||||||
|
## Just Using A Crate
|
||||||
|
|
||||||
|
If, after seeing all that, and seeing that I still didn't even cover every
|
||||||
|
possible trait impl that you might want for all the possible types... if after
|
||||||
|
all that you feel too intimidated, then I'll cave a bit on your behalf and
|
||||||
|
suggest to you that the [fixed](https://crates.io/crates/fixed) crate seems to
|
||||||
|
be the best crate available for fixed point math.
|
||||||
|
|
||||||
|
_I have not tested its use on the GBA myself_.
|
||||||
|
|
||||||
|
It's just my recommendation from looking at the docs of the various options
|
||||||
|
available, if you really wanted to just have a crate for it.
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
# Newtype
|
# Newtype
|
||||||
|
|
||||||
|
TODO: we've already used newtype twice by now (fixed point values and volatile
|
||||||
|
addresses), so we need to adjust how we start this section.
|
||||||
|
|
||||||
There's a great Zero Cost abstraction that we'll be using a lot that you might
|
There's a great Zero Cost abstraction that we'll be using a lot that you might
|
||||||
not already be familiar with: we're talking about the "Newtype Pattern"!
|
not already be familiar with: we're talking about the "Newtype Pattern"!
|
||||||
|
|
||||||
|
@ -27,32 +30,19 @@ cost at compile time.
|
||||||
pub struct PixelColor(u16);
|
pub struct PixelColor(u16);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
TODO: we've already talked about repr(transparent) by now
|
||||||
|
|
||||||
Ah, except that, as I'm sure you remember from [The
|
Ah, except that, as I'm sure you remember from [The
|
||||||
Rustonomicon](https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent)
|
Rustonomicon](https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent)
|
||||||
(and from [the
|
(and from the RFC too, of course), if we have a single field struct that's
|
||||||
RFC](https://github.com/rust-lang/rfcs/blob/master/text/1758-repr-transparent.md)
|
sometimes different from having just the bare value, so we should be using
|
||||||
too, of course), if we have a single field struct that's sometimes different
|
`#[repr(transparent)]` with our newtypes.
|
||||||
from having just the bare value, so we should be using `#[repr(transparent)]`
|
|
||||||
with our newtypes.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct PixelColor(u16);
|
pub struct PixelColor(u16);
|
||||||
```
|
```
|
||||||
|
|
||||||
Ah, and of course we'll need to make it so you can unwrap the value:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct PixelColor(u16);
|
|
||||||
|
|
||||||
impl From<PixelColor> for u16 {
|
|
||||||
fn from(color: PixelColor) -> u16 {
|
|
||||||
color.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And then we'll need to do that same thing for _every other newtype we want_.
|
And then we'll need to do that same thing for _every other newtype we want_.
|
||||||
|
|
||||||
Except there's only two tiny parts that actually differ between newtype
|
Except there's only two tiny parts that actually differ between newtype
|
||||||
|
@ -62,7 +52,12 @@ a job for a macro to me!
|
||||||
|
|
||||||
## Making It A Macro
|
## Making It A Macro
|
||||||
|
|
||||||
The most basic version of the macro we want goes like this:
|
If you're going to do much with macros you should definitely read through [The
|
||||||
|
Little Book of Rust
|
||||||
|
Macros](https://danielkeep.github.io/tlborm/book/index.html), but we won't be
|
||||||
|
doing too much so you can just follow along here a bit if you like.
|
||||||
|
|
||||||
|
The most basic version of a newtype macro starts like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -74,8 +69,39 @@ macro_rules! newtype {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Except we also want to be able to add attributes (which includes doc comments),
|
The `#[macro_export]` makes it exported by the current module (like `pub`
|
||||||
so we upgrade our macro a bit:
|
kinda), and then we have one expansion option that takes an identifier, a `,`,
|
||||||
|
and then a second identifier. The new name is the outer type we'll be using, and
|
||||||
|
the old name is the inner type that's being wrapped. You'd use our new macro
|
||||||
|
something like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
newtype! {PixelColorCurly, u16}
|
||||||
|
|
||||||
|
newtype!(PixelColorParens, u16);
|
||||||
|
|
||||||
|
newtype![PixelColorBrackets, u16];
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that you can invoke the macro with the outermost grouping as any of `()`,
|
||||||
|
`[]`, or `{}`. It makes no particular difference to the macro. Also, that space
|
||||||
|
in the first version is kinda to show off that you can put white space in
|
||||||
|
between the macro name and the grouping if you want. The difference is mostly
|
||||||
|
style, but there are some rules and considerations here:
|
||||||
|
|
||||||
|
* If you use curly braces then you _must not_ put a `;` after the invocation.
|
||||||
|
* If you use parentheses or brackets then you _must_ put the `;` at the end.
|
||||||
|
* Rustfmt cares which you use and formats accordingly:
|
||||||
|
* Curly brace macro use mostly gets treated like a code block.
|
||||||
|
* Parentheses macro use mostly gets treated like a function call.
|
||||||
|
* Bracket macro use mostly gets treated like an array declaration.
|
||||||
|
|
||||||
|
## Upgrade That Macro!
|
||||||
|
|
||||||
|
We also want to be able to add `derive` stuff and doc comments to our newtype.
|
||||||
|
Within the context of `macro_rules!` definitions these are called "meta". Since
|
||||||
|
we can have any number of them we wrap it all up in a "zero or more" matcher.
|
||||||
|
Then our macro looks like this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -88,52 +114,44 @@ macro_rules! newtype {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
And we want to automatically add the ability to turn the wrapper type back into
|
So now we can write
|
||||||
the wrapped type.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[macro_export]
|
newtype! {
|
||||||
macro_rules! newtype {
|
/// Color on the GBA gives 5 bits for each channel, the highest bit is ignored.
|
||||||
($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
|
#[derive(Debug, Clone, Copy)]
|
||||||
$(#[$attr])*
|
PixelColor, u16
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct $new_name($old_name);
|
|
||||||
|
|
||||||
impl From<$new_name> for $old_name {
|
|
||||||
fn from(x: $new_name) -> $old_name {
|
|
||||||
x.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
That seems like enough for all of our examples, so we'll stop there. We could
|
And that's about all we'll need for the examples.
|
||||||
add more things:
|
|
||||||
|
|
||||||
* Making the `From` impl being optional. We'd have to make the newtype
|
|
||||||
invocation be more complicated somehow, the user puts ", no-unwrap" after the
|
|
||||||
inner type declaration or something, or something like that.
|
|
||||||
* Allowing for more precise visibility controls on the wrapping type and on the
|
|
||||||
inner field. This would add a lot of line noise, so we'll just always have our
|
|
||||||
newtypes be `pub`.
|
|
||||||
* Allowing for generic newtypes, which might sound silly but that we'll actually
|
|
||||||
see an example of soon enough. To do this you might _think_ that we can change
|
|
||||||
the `:ident` declarations to `:ty`, but since we're declaring a fresh type not
|
|
||||||
using an existing type we have to accept it as an `:ident`. The way you get
|
|
||||||
around this is with a proc-macro, which is a lot more powerful but which also
|
|
||||||
requires that you write the proc-macro in an entirely other crate that gets
|
|
||||||
compiled first. We don't need that much power, so for our examples we'll go
|
|
||||||
with the macro_rules version and just do it by hand in the few cases where we
|
|
||||||
need a generic newtype.
|
|
||||||
* Allowing for `Deref` and `DerefMut`, which usually defeats the point of doing
|
|
||||||
the newtype, but maybe sometimes it's the right thing, so if you were going
|
|
||||||
for the full industrial strength version with a proc-macro and all you might
|
|
||||||
want to make that part of your optional add-ons as well the same way you might
|
|
||||||
want optional `From`. You'd probably want `From` to be "on by default" and
|
|
||||||
`Deref`/`DerefMut` to be "off by default", but whatever.
|
|
||||||
|
|
||||||
**As a reminder:** remember that `macro_rules` macros have to appear _before_
|
**As a reminder:** remember that `macro_rules` macros have to appear _before_
|
||||||
they're invoked in your source, so the `newtype` macro will always have to be at
|
they're invoked in your source, so the `newtype` macro will always have to be at
|
||||||
the very top of your file, or if you put it in a module within your project
|
the very top of your file, or if you put it in a module within your project
|
||||||
you'll need to declare the module before anything that uses it.
|
you'll need to declare the module before anything that uses it.
|
||||||
|
|
||||||
|
## Potential Homework
|
||||||
|
|
||||||
|
If you wanted to keep going and get really fancy with it, you could potentially
|
||||||
|
add a lot more:
|
||||||
|
|
||||||
|
* Make a `pub const fn new() -> Self` method that outputs the base value in a
|
||||||
|
const way. Combine this with builder style "setter" methods that are also
|
||||||
|
const and you can get the compiler to do quite a bit of the value building
|
||||||
|
work at compile time.
|
||||||
|
* Making the macro optionally emit a `From` impl to unwrap it back into the base
|
||||||
|
type.
|
||||||
|
* Allow for visibility modifiers to be applied to the inner field and the newly
|
||||||
|
generated type.
|
||||||
|
* Allowing for generic newtypes. You already saw the need for this once in the
|
||||||
|
volatile section. Unfortunately, this particular part gets really tricky if
|
||||||
|
you're using `macro_rules!`, so you might need to move up to a full
|
||||||
|
`proc_macro`. Having a `proc_macro` isn't bad except that they have to be
|
||||||
|
defined in a crate of their own and they're compiled before use. You can't
|
||||||
|
ever use them in the crate that defines them, so we won't be using them in any
|
||||||
|
of our single file examples.
|
||||||
|
* Allowing for optional `Deref` and `DerefMut` of the inner value. This takes
|
||||||
|
away most all the safety aspect of doing the newtype, but there may be times
|
||||||
|
for it. As an example, you could make a newtype with a different form of
|
||||||
|
Display impl that you want to otherwise treat as the base type in all places.
|
||||||
|
|
130
book/src/01-quirks/05-const_asserts.md
Normal file
130
book/src/01-quirks/05-const_asserts.md
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
# Constant Assertions
|
||||||
|
|
||||||
|
Have you ever wanted to assert things _even before runtime_? We all have, of
|
||||||
|
course. Particularly when the runtime machine is a poor little GBA, we'd like to
|
||||||
|
have the machine doing the compile handle as much checking as possible.
|
||||||
|
|
||||||
|
Enter the [static assertions](https://docs.rs/static_assertions/) crate, which
|
||||||
|
provides a way to let you assert on a `const` expression.
|
||||||
|
|
||||||
|
This is an amazing crate that you should definitely use when you can.
|
||||||
|
|
||||||
|
It's written by [Nikolai Vazquez](https://github.com/nvzqz), and they kindly
|
||||||
|
wrote up a [blog
|
||||||
|
post](https://nikolaivazquez.com/posts/programming/rust-static-assertions/) that
|
||||||
|
explains the thinking behind it.
|
||||||
|
|
||||||
|
However, I promised that each example would be single file, and I also promised
|
||||||
|
to explain what's going on as we go, so we'll briefly touch upon giving an
|
||||||
|
explanation here.
|
||||||
|
|
||||||
|
## How We Const Assert
|
||||||
|
|
||||||
|
Alright, as it stands (2018-12-15), we can't use `if` in a `const` context.
|
||||||
|
|
||||||
|
Since we can't use `if`, we can't use a normal `assert!`. Some day it will be
|
||||||
|
possible, and a failed assert at compile time will be a compile error and a
|
||||||
|
failed assert at run time will be a panic and we'll have a nice unified
|
||||||
|
programming experience. We can add runtime-only assertions by being a little
|
||||||
|
tricky with the compiler.
|
||||||
|
|
||||||
|
If we write
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const ASSERT: usize = 0 - 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
that gives a warning, since the math would underflow. We can upgrade that
|
||||||
|
warning to a hard error:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[deny(const_err)]
|
||||||
|
const ASSERT: usize = 0 - 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
And to make our construction reusable we can enable the
|
||||||
|
[underscore_const_names](https://github.com/rust-lang/rust/issues/54912) feature
|
||||||
|
in our program (or library) and then give each such const an underscore for a
|
||||||
|
name.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#![feature(underscore_const_names)]
|
||||||
|
|
||||||
|
#[deny(const_err)]
|
||||||
|
const _: usize = 0 - 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we wrap this in a macro where we give a `bool` expression as input. We
|
||||||
|
negate the bool then cast it to a `usize`, meaning that `true` negates into
|
||||||
|
`false`, which becomes `0usize`, and then there's no underflow error. Or if the
|
||||||
|
input was `false`, it negates into `true`, then becomes `1usize`, and then the
|
||||||
|
underflow error fires.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! const_assert {
|
||||||
|
($condition:expr) => {
|
||||||
|
#[deny(const_err)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const ASSERT: usize = 0 - !$condition as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Technically, written like this, the expression can be anything with a
|
||||||
|
`core::ops::Not` implementation that can also be `as` cast into `usize`. That's
|
||||||
|
`bool`, but also basically all the other number types. Since we want to ensure
|
||||||
|
that we get proper looking type errors when things go wrong, we can use
|
||||||
|
`($condition && true)` to enforce that we get a `bool` (thanks to `Talchas` for
|
||||||
|
that particular suggestion).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! const_assert {
|
||||||
|
($condition:expr) => {
|
||||||
|
#[deny(const_err)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
const _: usize = 0 - !($condition && true) as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Asserting Something
|
||||||
|
|
||||||
|
As an example of how we might use a `const_assert`, we'll do a demo with colors.
|
||||||
|
There's a red, blue, and green channel. We store colors in a `u16` with 5 bits
|
||||||
|
for each channel.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
newtype! {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
Color, u16
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And when we're building a color, we're passing in `u16` values, but they could
|
||||||
|
be using more than just 5 bits of space. We want to make sure that each channel
|
||||||
|
is 31 or less, so we can make a color builder that does a `const_assert!` on the
|
||||||
|
value of each channel.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
macro_rules! rgb {
|
||||||
|
($r:expr, $g:expr, $b:expr) => {
|
||||||
|
{
|
||||||
|
const_assert!($r <= 31);
|
||||||
|
const_assert!($g <= 31);
|
||||||
|
const_assert!($b <= 31);
|
||||||
|
Color($b << 10 | $g << 5 | $r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then we can declare some colors
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const RED: Color = rgb!(31, 0, 0);
|
||||||
|
|
||||||
|
const BLUE: Color = rgb!(31, 500, 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
The second one is clearly out of bounds and it fires an error just like we
|
||||||
|
wanted.
|
|
@ -1 +1,38 @@
|
||||||
# Broad Concepts
|
# Broad Concepts
|
||||||
|
|
||||||
|
The GameBoy Advance sits in a middle place between the chthonic game consoles of
|
||||||
|
the ancient past and the "small PC in a funny case" consoles of the modern age.
|
||||||
|
|
||||||
|
On the one hand, yeah, you're gonna find a few strange conventions as you learn
|
||||||
|
all the ropes.
|
||||||
|
|
||||||
|
On the other, at least we're writing in Rust at all, and not having to do all
|
||||||
|
the assembly by hand.
|
||||||
|
|
||||||
|
This chapter for "concepts" has a section for each part of the GBA's hardware
|
||||||
|
memory map, going by increasing order of base address value. The sections try to
|
||||||
|
explain as much as possible while sticking to just the concerns you might have
|
||||||
|
regarding that part of the memory map.
|
||||||
|
|
||||||
|
For an assessment of how to wrangle all three parts of the video system (PALRAM,
|
||||||
|
VRAM, and OAM), along with the correct IO registers, into something that shows a
|
||||||
|
picture, you'll want the Video chapter.
|
||||||
|
|
||||||
|
Similarly, the "IO Registers" part of the GBA actually controls how you interact
|
||||||
|
with every single bit of hardware connected to the GBA. A full description of
|
||||||
|
everything is obviously too much for just one section of the book. Instead you
|
||||||
|
get an overview of general IO register rules and advice. Each particular
|
||||||
|
register is described in the appropriate sections of either the Video or
|
||||||
|
Non-Video chapters.
|
||||||
|
|
||||||
|
## Bus Size
|
||||||
|
|
||||||
|
TODO: describe this
|
||||||
|
|
||||||
|
## Minimum Write Size
|
||||||
|
|
||||||
|
TODO: talk about parts where you can't write one byte at a time
|
||||||
|
|
||||||
|
## Volatile or Not?
|
||||||
|
|
||||||
|
TODO: discuss what memory should be used volatile style and what can be used normal style.
|
|
@ -1 +1,241 @@
|
||||||
# BIOS
|
# BIOS
|
||||||
|
|
||||||
|
* **Address Span:** `0x0` to `0x3FFF` (16k)
|
||||||
|
|
||||||
|
The [BIOS](https://en.wikipedia.org/wiki/BIOS) of the GBA is a small read-only
|
||||||
|
portion of memory at the very base of the address space. However, it is also
|
||||||
|
hardware protected against reading, so if you try to read from BIOS memory when
|
||||||
|
the program counter isn't pointed into the BIOS (eg: any time code _you_ write
|
||||||
|
is executing) then you get [basically garbage
|
||||||
|
data](https://problemkaputt.de/gbatek.htm#gbaunpredictablethings) back.
|
||||||
|
|
||||||
|
So we're not going to spend time here talking about what bits to read or write
|
||||||
|
within BIOS memory like we do with the other sections. Instead we're going to
|
||||||
|
spend time talking about [inline
|
||||||
|
assembly](https://doc.rust-lang.org/unstable-book/language-features/asm.html)
|
||||||
|
([tracking issue](https://github.com/rust-lang/rust/issues/29722)) and then use
|
||||||
|
it to call the [GBA BIOS
|
||||||
|
Functions](https://problemkaputt.de/gbatek.htm#biosfunctions).
|
||||||
|
|
||||||
|
Note that BIOS calls have _more overhead than normal function calls_, so don't
|
||||||
|
go using them all over the place if you don't have to. They're also usually
|
||||||
|
written more to be compact in terms of code than for raw speed, so you actually
|
||||||
|
can out speed them in some cases. Between the increased overhead and not being
|
||||||
|
as speed optimized, you can sometimes do a faster job without calling the BIOS
|
||||||
|
at all. (TODO: investigate more about what parts of the BIOS we could
|
||||||
|
potentially offer faster alternatives for.)
|
||||||
|
|
||||||
|
I'd like to take a moment to thank [Marc Brinkmann](https://github.com/mbr)
|
||||||
|
(with contributions from [Oliver Schneider](https://github.com/oli-obk) and
|
||||||
|
[Philipp Oppermann](https://github.com/phil-opp)) for writing [this blog
|
||||||
|
post](http://embed.rs/articles/2016/arm-inline-assembly-rust/). It's at least
|
||||||
|
ten times the tutorial quality as the `asm` entry in the Unstable Book has. In
|
||||||
|
fairness to the Unstable Book, the actual spec of how inline ASM works in rust
|
||||||
|
is "basically what clang does", and that's specified as "basically what GCC
|
||||||
|
does", and that's basically/shockingly not specified much at all despite GCC
|
||||||
|
being like 30 years old.
|
||||||
|
|
||||||
|
So let's be slow and pedantic about this process.
|
||||||
|
|
||||||
|
## Inline ASM
|
||||||
|
|
||||||
|
**Fair Warning:** Inline asm is one of the least stable parts of Rust overall,
|
||||||
|
and if you write bad things you can trigger internal compiler errors and panics
|
||||||
|
and crashes and make LLVM choke and die without explanation. If you write some
|
||||||
|
inline asm and then suddenly your program suddenly stops compiling without
|
||||||
|
explanation, try commenting out that whole inline asm use and see if it's
|
||||||
|
causing the problem. Double check that you've written every single part of the
|
||||||
|
asm call absolutely correctly, etc, etc.
|
||||||
|
|
||||||
|
**Bonus Warning:** The general information that follows regarding the asm macro
|
||||||
|
is consistent from system to system, but specific information about register
|
||||||
|
names, register quantities, asm instruction argument ordering, and so on is
|
||||||
|
specific to ARM on the GBA. If you're programming for any other device you'll
|
||||||
|
need to carefully investigate that before you begin.
|
||||||
|
|
||||||
|
Now then, with those out of the way, the inline asm docs describe an asm call as
|
||||||
|
looking like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
asm!(assembly template
|
||||||
|
: output operands
|
||||||
|
: input operands
|
||||||
|
: clobbers
|
||||||
|
: options
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
And once you stick a lot of stuff in there it can _absolutely_ be hard to
|
||||||
|
remember the ordering of the elements. So we'll start with a code block that
|
||||||
|
has some comments thrown in on each line:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
asm!(/* ASM */ TODO
|
||||||
|
:/* OUT */ TODO
|
||||||
|
:/* INP */ TODO
|
||||||
|
:/* CLO */ TODO
|
||||||
|
:/* OPT */
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we have to decide what we're gonna write. Obviously we're going to do some
|
||||||
|
instructions, but those instructions use registers, and how are we gonna talk
|
||||||
|
about them? We've got two choices.
|
||||||
|
|
||||||
|
1) We can pick each and every register used by specifying exact register names.
|
||||||
|
In THUMB mode we have 8 registers available, named `r0` through `r7`. If you
|
||||||
|
switch into 32-bit mode there's additional registers that are also available.
|
||||||
|
|
||||||
|
2) We can specify slots for registers we need and let LLVM decide. In this style
|
||||||
|
you name your slots `$0`, `$1` and so on. Slot numbers are assigned first to
|
||||||
|
all specified outputs, then to all specified inputs, in the order that you
|
||||||
|
list them.
|
||||||
|
|
||||||
|
In the case of the GBA BIOS, each BIOS function has pre-designated input and
|
||||||
|
output registers, so we will use the first style. If you use inline ASM in other
|
||||||
|
parts of your code you're free to use the second style.
|
||||||
|
|
||||||
|
### Assembly
|
||||||
|
|
||||||
|
This is just one big string literal. You write out one instruction per line, and
|
||||||
|
excess whitespace is ignored. You can also do comments within your assembly
|
||||||
|
using `;` to start a comment that goes until the end of the line.
|
||||||
|
|
||||||
|
Assembly convention doesn't consider it unreasonable to comment potentially as
|
||||||
|
much as _every single line_ of asm that you write when you're getting used to
|
||||||
|
things. Or even if you are used to things. This is cryptic stuff, there's a
|
||||||
|
reason we avoid writing in it as much as possible.
|
||||||
|
|
||||||
|
Remember that our Rust code is in 16-bit mode. You _can_ switch to 32-bit mode
|
||||||
|
within your asm as long as you switch back by the time the block ends. Otherwise
|
||||||
|
you'll have a bad time.
|
||||||
|
|
||||||
|
### Outputs
|
||||||
|
|
||||||
|
A comma separated list. Each entry looks like
|
||||||
|
|
||||||
|
* `"constraint" (binding)`
|
||||||
|
|
||||||
|
An output constraint starts with a symbol:
|
||||||
|
|
||||||
|
* `=` for write only
|
||||||
|
* `+` for reads and writes
|
||||||
|
* `&` for for "early clobber", meaning that you'll write to this at some point
|
||||||
|
before all input values have been read. It prevents this register from being
|
||||||
|
assigned to an input register.
|
||||||
|
|
||||||
|
Followed by _either_ the letter `r` (if you want LLVM to pick the register to
|
||||||
|
use) or curly braces around a specific register (if you want to pick).
|
||||||
|
|
||||||
|
* The binding can be any single 32-bit or smaller value.
|
||||||
|
* If your binding has bit pattern requirements ("must be non-zero", etc) you are
|
||||||
|
responsible for upholding that.
|
||||||
|
* If your binding type will try to `Drop` later then you are responsible for it
|
||||||
|
being in a fit state to do that.
|
||||||
|
* The binding must be either a mutable binding or a binding that was
|
||||||
|
pre-declared but not yet assigned.
|
||||||
|
|
||||||
|
Anything else is UB.
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
This is a similar comma separated list.
|
||||||
|
|
||||||
|
* `"constraint" (binding)`
|
||||||
|
|
||||||
|
An input constraint doesn't have the symbol prefix, you just pick either `r` or
|
||||||
|
a named register with curly braces around it.
|
||||||
|
|
||||||
|
* An input binding must be a single 32-bit or smaller value.
|
||||||
|
* An input binding _should_ be a type that is `Copy` but this is not an absolute
|
||||||
|
requirement. Having the input be read is semantically similar to using
|
||||||
|
`core::ptr::read(&binding)` and forgetting the value when you're done.
|
||||||
|
|
||||||
|
### Clobbers
|
||||||
|
|
||||||
|
Sometimes your asm will touch registers other than the ones declared for input
|
||||||
|
and output.
|
||||||
|
|
||||||
|
Clobbers are declared as a comma separated list of string literals naming
|
||||||
|
specific registers. You don't use curly braces with clobbers.
|
||||||
|
|
||||||
|
LLVM _needs_ to know this information. It can move things around to keep your
|
||||||
|
data safe, but only if you tell it what's about to happen.
|
||||||
|
|
||||||
|
Failure to define all of your clobbers can cause UB.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
There's only one option we'd care to specify. That option is "volatile".
|
||||||
|
|
||||||
|
Just like with a function call, LLVM will skip a block of asm if it doesn't see
|
||||||
|
that any outputs from the asm were used later on. Nearly every single BIOS call
|
||||||
|
(other than the math operations) will need to be marked as "volatile".
|
||||||
|
|
||||||
|
### BIOS ASM
|
||||||
|
|
||||||
|
* Inputs are always `r0`, `r1`, `r2`, and/or `r3`, depending on function.
|
||||||
|
* Outputs are always zero or more of `r0`, `r1`, and `r3`.
|
||||||
|
* Any of the output registers that aren't actually used should be marked as
|
||||||
|
clobbered.
|
||||||
|
* All other registers are unaffected.
|
||||||
|
|
||||||
|
All of the GBA BIOS calls are performed using the
|
||||||
|
[swi](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0068b/BABFCEEG.html)
|
||||||
|
instruction, combined with a value depending on what BIOS function you're trying
|
||||||
|
to invoke. If you're in 16-bit code you use the value directly, and if you're in
|
||||||
|
32-bit mode you shift the value up by 16 bits first.
|
||||||
|
|
||||||
|
### Example BIOS Function: Division
|
||||||
|
|
||||||
|
For our example we'll use the division function, because GBATEK gives very clear
|
||||||
|
instructions on how each register is used with that one:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Signed Division, r0/r1.
|
||||||
|
r0 signed 32bit Number
|
||||||
|
r1 signed 32bit Denom
|
||||||
|
Return:
|
||||||
|
r0 Number DIV Denom ;signed
|
||||||
|
r1 Number MOD Denom ;signed
|
||||||
|
r3 ABS (Number DIV Denom) ;unsigned
|
||||||
|
For example, incoming -1234, 10 should return -123, -4, +123.
|
||||||
|
The function usually gets caught in an endless loop upon division by zero.
|
||||||
|
```
|
||||||
|
|
||||||
|
The math folks tell me that the `r1` value should be properly called the
|
||||||
|
"remainder" not the "modulus". We'll go with that for our function, doesn't hurt
|
||||||
|
to use the correct names. Our Rust function has an assert against dividing by
|
||||||
|
`0`, then we name some bindings _without_ giving them a value, we make the asm
|
||||||
|
call, and then return what we got.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) {
|
||||||
|
assert!(denominator != 0);
|
||||||
|
let div_out: i32;
|
||||||
|
let rem_out: i32;
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x06"
|
||||||
|
:/* OUT */ "={r0}"(div_out), "={r1}"(rem_out)
|
||||||
|
:/* INP */ "{r0}"(numerator), "{r1}"(denominator)
|
||||||
|
:/* CLO */ "r3"
|
||||||
|
:/* OPT */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(div_out, rem_out)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
I _hope_ this all makes sense by now.
|
||||||
|
|
||||||
|
## Specific BIOS Functions
|
||||||
|
|
||||||
|
For a full list of all the specific BIOS functions and their use you should
|
||||||
|
check the `gba::bios` module within the `gba` crate. There's just so many of
|
||||||
|
them that enumerating them all here wouldn't serve much purpose.
|
||||||
|
|
||||||
|
Which is not to say that we'll never cover any BIOS functions in this book!
|
||||||
|
Instead, we'll simply mention them when whenever they're relevent to the task at
|
||||||
|
hand (such as controlling sound or waiting for vblank).
|
||||||
|
|
||||||
|
//TODO: list/name all BIOS functions as well as what they relate to elsewhere.
|
||||||
|
|
|
@ -1 +1,28 @@
|
||||||
# Work RAM
|
# Work RAM
|
||||||
|
|
||||||
|
## External Work RAM (EWRAM)
|
||||||
|
|
||||||
|
* **Address Span:** `0x2000000` to `0x203FFFF` (256k)
|
||||||
|
|
||||||
|
This is a big pile of space, the use of which is up to each game. However, the
|
||||||
|
external work ram has only a 16-bit bus (if you read/write a 32-bit value it
|
||||||
|
silently breaks it up into two 16-bit operations) and also 2 wait cycles (extra
|
||||||
|
CPU cycles that you have to expend _per 16-bit bus use_).
|
||||||
|
|
||||||
|
It's most helpful to think of EWRAM as slower, distant memory, similar to the
|
||||||
|
"heap" in a normal application. You can take the time to go store something
|
||||||
|
within EWRAM, or to load it out of EWRAM, but if you've got several operations
|
||||||
|
to do in a row and you're worried about time you should pull that value into
|
||||||
|
local memory, work on your local copy, and then push it back out to EWRAM.
|
||||||
|
|
||||||
|
## Internal Work RAM (IWRAM)
|
||||||
|
|
||||||
|
* **Address Span:** `0x3000000` to `0x3007FFF` (32k)
|
||||||
|
|
||||||
|
This is a smaller pile of space, but it has a 32-bit bus and no wait.
|
||||||
|
|
||||||
|
By default, `0x3007F00` to `0x3007FFF` is reserved for interrupt and BIOS use.
|
||||||
|
The rest of it is mostly up to you. The user's stack space starts at `0x3007F00`
|
||||||
|
and proceeds _down_ from there. For best results you should probably start at
|
||||||
|
`0x3000000` and then go upwards. Under normal use it's unlikely that the two
|
||||||
|
memory regions will crash into each other.
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
# IO Registers
|
# IO Registers
|
||||||
|
|
||||||
|
* **Address Span:** `0x400_0000` to `0x400_03FE`
|
||||||
|
|
|
@ -1 +1,50 @@
|
||||||
# Palette RAM
|
# Palette RAM (PALRAM)
|
||||||
|
|
||||||
|
* **Address Span:** `0x500_0000` to `0x500_03FF` (1k)
|
||||||
|
|
||||||
|
Palette RAM has a 16-bit bus, which isn't really a problem because it
|
||||||
|
conceptually just holds `u16` values. There's no automatic wait state, but if
|
||||||
|
you try to access the same location that the display controller is accessing you
|
||||||
|
get bumped by 1 cycle. Since the display controller can use the palette ram any
|
||||||
|
number of times per scanline it's basically impossible to predict if you'll have
|
||||||
|
to do a wait or not during VDraw. During VBlank you won't have any wait of
|
||||||
|
course.
|
||||||
|
|
||||||
|
PALRAM is among the memory where there's weirdness if you try to write just one
|
||||||
|
byte: if you try to write just 1 byte, it writes that byte into _both_ parts of
|
||||||
|
the larger 16-bit location. This doesn't really affect us much with PALRAM,
|
||||||
|
because palette values are all supposed to be `u16` anyway.
|
||||||
|
|
||||||
|
The palette memory actually contains not one, but _two_ sets of palettes. First
|
||||||
|
there's 256 entries for the background palette data (starting at `0x5000000`),
|
||||||
|
and then there's 256 entries for object palette data (starting at `0x5000200`).
|
||||||
|
|
||||||
|
The GBA also has two modes for palette access: 8-bits-per-pixel (8bpp) and
|
||||||
|
4-bits-per-pixel (4bpp).
|
||||||
|
|
||||||
|
* In 8bpp mode an 8-bit palette index value within a background or sprite
|
||||||
|
simply indexes directly into the 256 slots for that type of thing.
|
||||||
|
* In 4bpp mode a 4-bit palette index value within a background or sprite
|
||||||
|
specifies an index within a particular "palbank" (16 palette entries each),
|
||||||
|
and then a _separate_ setting outside of the graphical data determines which
|
||||||
|
palbank is to be used for that background or object (the screen entry data for
|
||||||
|
backgrounds, and the object attributes for objects).
|
||||||
|
|
||||||
|
### Transparency
|
||||||
|
|
||||||
|
When a pixel within a background or object specifies index 0 as its palette
|
||||||
|
entry it is treated as a transparent pixel. This means that in 8bpp mode there's
|
||||||
|
only 255 actual color options (0 being transparent), and in 4bpp mode there's
|
||||||
|
only 15 actual color options available within each palbank (the 0th entry of
|
||||||
|
_each_ palbank is transparent).
|
||||||
|
|
||||||
|
Individual backgrounds, and individual objects, each determine if they're 4bpp
|
||||||
|
or 8bpp separately, so a given overall palette slot might map to a used color in
|
||||||
|
8bpp and an unused/transparent color in 4bpp. If you're a palette wizard.
|
||||||
|
|
||||||
|
Palette slot 0 of the overall background palette is used to determine the
|
||||||
|
"backdrop" color. That's the color you see if no background or object ends up
|
||||||
|
being rendered within a given pixel.
|
||||||
|
|
||||||
|
Since display mode 3 and display mode 5 don't use the palette, they cannot
|
||||||
|
benefit from transparency.
|
||||||
|
|
|
@ -1 +1,24 @@
|
||||||
# Video RAM
|
# Video RAM (VRAM)
|
||||||
|
|
||||||
|
* **Address Span:** `0x600_0000` to `0x601_7FFF` (96k)
|
||||||
|
|
||||||
|
We've used this before! VRAM has a 16-bit bus and no wait. However, the same as
|
||||||
|
with PALRAM, the "you might have to wait if the display controller is looking at
|
||||||
|
it" rule applies here.
|
||||||
|
|
||||||
|
Unfortunately there's not much more exact detail that can be given about VRAM.
|
||||||
|
The use of the memory depends on the video mode that you're using.
|
||||||
|
|
||||||
|
One general detail of note is that you can't write individual bytes to any part
|
||||||
|
of VRAM. Depending on mode and location, you'll either get your bytes doubled
|
||||||
|
into both the upper and lower parts of the 16-bit location targeted, or you
|
||||||
|
won't even affect the memory. This usually isn't a big deal, except in two
|
||||||
|
situations:
|
||||||
|
|
||||||
|
* In Mode 4, if you want to change just 1 pixel, you'll have to be very careful
|
||||||
|
to read the old `u16`, overwrite just the byte you wanted to change, and then
|
||||||
|
write that back.
|
||||||
|
* In any display mode, avoid using `memcopy` to place things into VRAM.
|
||||||
|
It's written to be byte oriented, and only does 32-bit transfers under select
|
||||||
|
conditions. The rest of the time it'll copy one byte at a time and you'll get
|
||||||
|
either garbage or nothing at all.
|
||||||
|
|
|
@ -1 +1,62 @@
|
||||||
# Object Attribute Memory
|
# Object Attribute Memory (OAM)
|
||||||
|
|
||||||
|
* **Address Span:** `0x700_0000` to `0x700_03FF` (1k)
|
||||||
|
|
||||||
|
The Object Attribute Memory has a 32-bit bus and no default wait, but suffers
|
||||||
|
from the "you might have to wait if the display controller is looking at it"
|
||||||
|
rule. You cannot write individual bytes to OAM at all, but that's not really a
|
||||||
|
problem because all the fields of the data types within OAM are either `i16` or
|
||||||
|
`u16` anyway.
|
||||||
|
|
||||||
|
Object attribute memory is the wildest yet: it conceptually contains two types
|
||||||
|
of things, but they're _interlaced_ with each other all the way through.
|
||||||
|
|
||||||
|
Now, [GBATEK](http://problemkaputt.de/gbatek.htm#lcdobjoamattributes) and
|
||||||
|
[CowByte](https://www.cs.rit.edu/~tjh8300/CowBite/CowBiteSpec.htm#OAM%20(sprites))
|
||||||
|
doesn't quite give names to the two data types here.
|
||||||
|
[TONC](https://www.coranac.com/tonc/text/regobj.htm#sec-oam) calls them
|
||||||
|
`OBJ_ATTR` and `OBJ_AFFINE`, but we'll be giving them names fitting with the
|
||||||
|
Rust naming convention. Just know that if you try to talk about it with others
|
||||||
|
they might not be using the same names. In Rust terms their layout would look
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ObjectAttributes {
|
||||||
|
attr0: u16,
|
||||||
|
attr1: u16,
|
||||||
|
attr2: u16,
|
||||||
|
filler: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AffineMatrix {
|
||||||
|
filler0: [u16; 3],
|
||||||
|
pa: i16,
|
||||||
|
filler1: [u16; 3],
|
||||||
|
pb: i16,
|
||||||
|
filler2: [u16; 3],
|
||||||
|
pc: i16,
|
||||||
|
filler3: [u16; 3],
|
||||||
|
pd: i16,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
(Note: the `#[repr(C)]` part just means that Rust must lay out the data exactly
|
||||||
|
in the order we specify, which otherwise it is not required to do).
|
||||||
|
|
||||||
|
So, we've got 1024 bytes in OAM and each `ObjectAttributes` value is 8 bytes, so
|
||||||
|
naturally we can support up to 128 objects.
|
||||||
|
|
||||||
|
_At the same time_, we've got 1024 bytes in OAM and each `AffineMatrix` is 32
|
||||||
|
bytes, so we can have 32 of them.
|
||||||
|
|
||||||
|
But, as I said, these things are all _interlaced_ with each other. See how
|
||||||
|
there's "filler" fields in each struct? If we imagine the OAM as being just an
|
||||||
|
array of one type or the other, indexes 0/1/2/3 of the `ObjectAttributes` array
|
||||||
|
would line up with index 0 of the `AffineMatrix` array. It's kinda weird, but
|
||||||
|
that's just how it works. When we setup functions to read and write these values
|
||||||
|
we'll have to be careful with how we do it. We probably _won't_ want to use
|
||||||
|
those representations above, at least not with the `AffineMatrix` type, because
|
||||||
|
they're quite wasteful if you want to store just object attributes or just
|
||||||
|
affine matrices.
|
||||||
|
|
|
@ -1 +1,14 @@
|
||||||
# Game Pak ROM / Flash ROM
|
# Game Pak ROM / Flash ROM (ROM)
|
||||||
|
|
||||||
|
* **Address Span (Wait State 0):** `0x800_0000` to `0x9FF_FFFF`
|
||||||
|
* **Address Span (Wait State 1):** `0xA00_0000` to `0xBFF_FFFF`
|
||||||
|
* **Address Span (Wait State 2):** `0xC00_0000` to `0xDFF_FFFF`
|
||||||
|
|
||||||
|
The game's ROM data is a single set of data that's up to 32 megabytes in size.
|
||||||
|
However, that data is mirrored to three different locations in the address
|
||||||
|
space. Depending on which part of the address space you use, it can affect the
|
||||||
|
memory timings involved.
|
||||||
|
|
||||||
|
TODO: describe `WAITCNT` here, we won't get a better chance at it.
|
||||||
|
|
||||||
|
TODO: discuss THUMB vs ARM code and why THUMB is so much faster (because ROM is a 16-bit bus)
|
||||||
|
|
|
@ -1 +1,16 @@
|
||||||
# Save RAM
|
# Save RAM (SRAM)
|
||||||
|
|
||||||
|
* **Address Span:** `0xE00_0000` to `0xE00FFFF` (64k)
|
||||||
|
|
||||||
|
The actual amount of SRAM available depends on your game pak, and the 64k figure
|
||||||
|
is simply the maximum possible. A particular game pak might have less, and an
|
||||||
|
emulator will likely let you have all 64k if you want.
|
||||||
|
|
||||||
|
As with other portions of the address space, SRAM has some number of wait cycles
|
||||||
|
per use. As with ROM, you can change the wait cycle settings via the `WAITCNT`
|
||||||
|
register if the defaults don't work well for your game pak. See the ROM section
|
||||||
|
for full details of how the `WAITCNT` register works.
|
||||||
|
|
||||||
|
The game pak SRAM also has only an 8-bit bus, so have fun with that.
|
||||||
|
|
||||||
|
The GBA Direct Memory Access (DMA) unit cannot access SRAM.
|
||||||
|
|
|
@ -1 +1,9 @@
|
||||||
# Video
|
# Video
|
||||||
|
|
||||||
|
GBA Video starts with an IO register called the "Display Control Register", and
|
||||||
|
then spirals out from there. You generally have to use Palette RAM (PALRAM),
|
||||||
|
Video RAM (VRAM), Object Attribute Memory (OAM), as well as any number of other
|
||||||
|
IO registers.
|
||||||
|
|
||||||
|
They all have to work together just right, and there's a lot going on when you
|
||||||
|
first try doing it, so try to take it very slowly as you're learning each step.
|
||||||
|
|
|
@ -1 +1,21 @@
|
||||||
# Non-Video
|
# Non-Video
|
||||||
|
|
||||||
|
Besides video effects the GBA still has an okay amount of stuff going on.
|
||||||
|
|
||||||
|
Obviously you'll want to know how to read the user's button inputs. That can
|
||||||
|
almost go without saying, except that I said it.
|
||||||
|
|
||||||
|
Each other part can be handled in about any order you like.
|
||||||
|
|
||||||
|
Using interrupts is perhaps one of the hardest things for us as Rust programmers
|
||||||
|
due to quirks in our compilation process. Our code all gets compiled to 16-bit
|
||||||
|
THUMB instructions, and we don't have a way to mark a function to be compiled
|
||||||
|
using 32-bit ASM instructions instead. However, an interrupt handler _must_ be
|
||||||
|
written in 32-bit ASM instructions for it to work. That means that we have to
|
||||||
|
write our interrupt handler in 32-bit ASM by hand. We'll do it, but I don't
|
||||||
|
think we'll be too happy about it.
|
||||||
|
|
||||||
|
The Link Cable related stuff is also probably a little harder to test than
|
||||||
|
anything else. Just because link cable emulation isn't always the best, and or
|
||||||
|
you need two GBAs with two flash carts and the cable for hardware testing.
|
||||||
|
Still, we'll try to go over it eventually.
|
||||||
|
|
|
@ -1 +1,5 @@
|
||||||
# Buttons
|
# Buttons
|
||||||
|
|
||||||
|
It's all well and good to just show a picture, even to show an animation, but if
|
||||||
|
we want a game we have to let the user interact with something.
|
||||||
|
|
||||||
|
|
1
book/src/04-non-video/06-link_cable.md
Normal file
1
book/src/04-non-video/06-link_cable.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Link Cable
|
|
@ -1 +0,0 @@
|
||||||
# Network
|
|
|
@ -12,6 +12,7 @@
|
||||||
* [Fixed Only](01-quirks/02-fixed_only.md)
|
* [Fixed Only](01-quirks/02-fixed_only.md)
|
||||||
* [Volatile Destination](01-quirks/03-volatile_destination.md)
|
* [Volatile Destination](01-quirks/03-volatile_destination.md)
|
||||||
* [Newtype](01-quirks/04-newtype.md)
|
* [Newtype](01-quirks/04-newtype.md)
|
||||||
|
* [Const Asserts](01-quirks/05-const_asserts.md)
|
||||||
* [Concepts](02-concepts/00-index.md)
|
* [Concepts](02-concepts/00-index.md)
|
||||||
* [CPU](02-concepts/01-cpu.md)
|
* [CPU](02-concepts/01-cpu.md)
|
||||||
* [BIOS](02-concepts/02-bios.md)
|
* [BIOS](02-concepts/02-bios.md)
|
||||||
|
@ -31,7 +32,7 @@
|
||||||
* [Direct Memory Access](04-non-video/03-dma.md)
|
* [Direct Memory Access](04-non-video/03-dma.md)
|
||||||
* [Sound](04-non-video/04-sound.md)
|
* [Sound](04-non-video/04-sound.md)
|
||||||
* [Interrupts](04-non-video/05-interrupts.md)
|
* [Interrupts](04-non-video/05-interrupts.md)
|
||||||
* [Network](04-non-video/06-network.md)
|
* [Link Cable](04-non-video/06-link_cable.md)
|
||||||
* [Game Pak](04-non-video/07-game_pak.md)
|
* [Game Pak](04-non-video/07-game_pak.md)
|
||||||
* [Examples](05-examples/00-index.md)
|
* [Examples](05-examples/00-index.md)
|
||||||
* [hello_magic](05-examples/01-hello_magic.md)
|
* [hello_magic](05-examples/01-hello_magic.md)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![feature(start)]
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
|
|
@ -1,22 +1,37 @@
|
||||||
#![feature(start)]
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
#![feature(underscore_const_names)]
|
||||||
|
|
||||||
#[panic_handler]
|
#[macro_export]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
macro_rules! newtype {
|
||||||
loop {}
|
($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
|
||||||
|
$(#[$attr])*
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $new_name($old_name);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[start]
|
#[macro_export]
|
||||||
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
macro_rules! const_assert {
|
||||||
unsafe {
|
($condition:expr) => {
|
||||||
DISPCNT.write(MODE3 | BG2);
|
#[deny(const_err)]
|
||||||
mode3_pixel(120, 80, rgb16(31, 0, 0));
|
#[allow(dead_code)]
|
||||||
mode3_pixel(136, 80, rgb16(0, 31, 0));
|
const _: usize = 0 - !$condition as usize;
|
||||||
mode3_pixel(120, 96, rgb16(0, 0, 31));
|
};
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs an RGB value with a `const_assert!` that the input is in range.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! const_rgb {
|
||||||
|
($r:expr, $g:expr, $b:expr) => {{
|
||||||
|
const_assert!($r <= 31);
|
||||||
|
const_assert!($g <= 31);
|
||||||
|
const_assert!($b <= 31);
|
||||||
|
Color::new($r, $g, $b)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: kill this
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct VolatilePtr<T>(pub *mut T);
|
pub struct VolatilePtr<T>(pub *mut T);
|
||||||
|
@ -32,17 +47,50 @@ impl<T> VolatilePtr<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x04000000 as *mut u16);
|
newtype! {
|
||||||
pub const MODE3: u16 = 3;
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub const BG2: u16 = 0b100_0000_0000;
|
Color, u16
|
||||||
|
|
||||||
pub const VRAM: usize = 0x06000000;
|
|
||||||
pub const SCREEN_WIDTH: isize = 240;
|
|
||||||
|
|
||||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
|
||||||
blue << 10 | green << 5 | red
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn mode3_pixel(col: isize, row: isize, color: u16) {
|
impl Color {
|
||||||
VolatilePtr(VRAM as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
/// Combines the Red, Blue, and Green provided into a single color value.
|
||||||
|
pub const fn new(red: u16, green: u16, blue: u16) -> Color {
|
||||||
|
Color(blue << 10 | green << 5 | red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype! {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
DisplayControlSetting, u16
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DISPLAY_CONTROL: VolatilePtr<DisplayControlSetting> = VolatilePtr(0x0400_0000 as *mut DisplayControlSetting);
|
||||||
|
pub const JUST_MODE3: DisplayControlSetting = DisplayControlSetting(3);
|
||||||
|
pub const JUST_BG2: DisplayControlSetting = DisplayControlSetting(0b100_0000_0000);
|
||||||
|
pub const JUST_MODE3_AND_BG2: DisplayControlSetting = DisplayControlSetting(JUST_MODE3.0 | JUST_BG2.0);
|
||||||
|
|
||||||
|
pub struct Mode3;
|
||||||
|
impl Mode3 {
|
||||||
|
const SCREEN_WIDTH: isize = 240;
|
||||||
|
const PIXELS: VolatilePtr<Color> = VolatilePtr(0x600_0000 as *mut Color);
|
||||||
|
|
||||||
|
pub unsafe fn draw_pixel_unchecked(col: isize, row: isize, color: Color) {
|
||||||
|
Self::PIXELS.offset(col + row * Self::SCREEN_WIDTH).write(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[start]
|
||||||
|
fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
|
unsafe {
|
||||||
|
DISPLAY_CONTROL.write(JUST_MODE3_AND_BG2);
|
||||||
|
Mode3::draw_pixel_unchecked(120, 80, const_rgb!(31, 0, 0));
|
||||||
|
Mode3::draw_pixel_unchecked(136, 80, const_rgb!(0, 31, 0));
|
||||||
|
Mode3::draw_pixel_unchecked(120, 96, const_rgb!(0, 0, 31));
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![feature(start)]
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
#![feature(start)]
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||||
|
|
517
src/bios.rs
Normal file
517
src/bios.rs
Normal file
|
@ -0,0 +1,517 @@
|
||||||
|
//! This module contains wrappers for all GBA BIOS function calls.
|
||||||
|
//!
|
||||||
|
//! A GBA BIOS call has significantly more overhead than a normal function call,
|
||||||
|
//! so think carefully before using them too much.
|
||||||
|
//!
|
||||||
|
//! The actual content of each function here is generally a single inline asm
|
||||||
|
//! instruction to invoke the correct BIOS function (`swi x`, with `x` being
|
||||||
|
//! whatever value is necessary for that function). Some functions also perform
|
||||||
|
//! necessary checks to save you from yourself, such as not dividing by zero.
|
||||||
|
|
||||||
|
//TODO: ALL functions in this module should have `if cfg!(test)` blocks. The
|
||||||
|
//functions that never return must panic, the functions that return nothing
|
||||||
|
//should just do so, and the math functions should just return the correct math
|
||||||
|
//I guess.
|
||||||
|
|
||||||
|
/// (`swi 0x00`) SoftReset the device.
|
||||||
|
///
|
||||||
|
/// This function does not ever return.
|
||||||
|
///
|
||||||
|
/// Instead, it clears the top `0x200` bytes of IWRAM (containing stacks, and
|
||||||
|
/// BIOS IRQ vector/flags), re-initializes the system, supervisor, and irq stack
|
||||||
|
/// pointers (new values listed below), sets `r0` through `r12`, `LR_svc`,
|
||||||
|
/// `SPSR_svc`, `LR_irq`, and `SPSR_irq` to zero, and enters system mode. The
|
||||||
|
/// return address is loaded into `r14` and then the function jumps there with
|
||||||
|
/// `bx r14`.
|
||||||
|
///
|
||||||
|
/// * sp_svc: `0x300_7FE0`
|
||||||
|
/// * sp_irq: `0x300_7FA0`
|
||||||
|
/// * sp_sys: `0x300_7F00`
|
||||||
|
/// * Zero-filled Area: `0x300_7E00` to `0x300_7FFF`
|
||||||
|
/// * Return Address: Depends on the 8-bit flag value at `0x300_7FFA`. In either
|
||||||
|
/// case execution proceeds in ARM mode.
|
||||||
|
/// * zero flag: `0x800_0000` (ROM), which for our builds means that the
|
||||||
|
/// `crt0` program to execute (just like with a fresh boot), and then
|
||||||
|
/// control passes into `main` and so on.
|
||||||
|
/// * non-zero flag: `0x200_0000` (RAM), This is where a multiboot image would
|
||||||
|
/// go if you were doing a multiboot thing. However, this project doesn't
|
||||||
|
/// support multiboot at the moment. You'd need an entirely different build
|
||||||
|
/// pipeline because there's differences in header format and things like
|
||||||
|
/// that. Perhaps someday, but probably not even then. Submit the PR for it
|
||||||
|
/// if you like!
|
||||||
|
///
|
||||||
|
/// ## Safety
|
||||||
|
///
|
||||||
|
/// This functions isn't ever unsafe to the current iteration of the program.
|
||||||
|
/// However, because not all memory is fully cleared you theoretically could
|
||||||
|
/// threaten the _next_ iteration of the program that runs. I'm _fairly_
|
||||||
|
/// convinced that you can't actually use this to force purely safe code to
|
||||||
|
/// perform UB, but such a scenario might exist.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn soft_reset() -> ! {
|
||||||
|
asm!(/* ASM */ "swi 0x00"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
core::hint::unreachable_unchecked()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x01`) RegisterRamReset.
|
||||||
|
///
|
||||||
|
/// Clears the portions of memory given by the `flags` value, sets the Display
|
||||||
|
/// Control Register to `0x80` (forced blank and nothing else), then returns.
|
||||||
|
///
|
||||||
|
/// * Flag bits:
|
||||||
|
/// 0) Clears the 256k of EWRAM (don't use if this is where your function call
|
||||||
|
/// will return to!)
|
||||||
|
/// 1) Clears the 32k of IWRAM _excluding_ the last `0x200` bytes (see also:
|
||||||
|
/// the `soft_reset` function)
|
||||||
|
/// 2) Clears all Palette data
|
||||||
|
/// 3) Clears all VRAM
|
||||||
|
/// 4) Clears all OAM (reminder: a zeroed object isn't disabled!)
|
||||||
|
/// 5) Reset SIO registers (resets them to general purpose mode)
|
||||||
|
/// 6) Reset Sound registers
|
||||||
|
/// 7) Reset all IO registers _other than_ SIO and Sound
|
||||||
|
///
|
||||||
|
/// **Bug:** The LSB of `SIODATA32` is always zeroed, even if bit 5 was not
|
||||||
|
/// enabled. This is sadly a bug in the design of the GBA itself.
|
||||||
|
///
|
||||||
|
/// ## Safety
|
||||||
|
///
|
||||||
|
/// It is generally a safe operation to suddenly clear any part of the GBA's
|
||||||
|
/// memory, except in the case that you were executing out of EWRAM and clear
|
||||||
|
/// that. If you do then you return to nothing and have a bad time.
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn register_ram_reset(flags: u8) {
|
||||||
|
asm!(/* ASM */ "swi 0x01"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ "{r0}"(flags)
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
//TODO(lokathor): newtype this flag business.
|
||||||
|
|
||||||
|
/// (`swi 0x02`) Halts the CPU until an interrupt occurs.
|
||||||
|
///
|
||||||
|
/// Components _other than_ the CPU continue to function. Halt mode ends when
|
||||||
|
/// any enabled interrupt triggers.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn halt() {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x02"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x03`) Stops the CPU as well as most other components.
|
||||||
|
///
|
||||||
|
/// Stop mode must be stopped by an interrupt, but can _only_ be stopped by a
|
||||||
|
/// Keypad, Game Pak, or General-Purpose-SIO interrupt.
|
||||||
|
///
|
||||||
|
/// Before going into stop mode you should manually disable video and sound (or
|
||||||
|
/// they will continue to consume power), and you should also disable any other
|
||||||
|
/// optional externals such as rumble and infra-red.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn stop() {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x03"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x04`) "IntrWait", similar to halt but with more options.
|
||||||
|
///
|
||||||
|
/// * The first argument controls if you want to ignore all current flags and
|
||||||
|
/// wait until a new flag is set.
|
||||||
|
/// * The second argument is what flags you're waiting on (same format as the
|
||||||
|
/// IE/IF registers).
|
||||||
|
///
|
||||||
|
/// If you're trying to handle more than one interrupt at once this has less
|
||||||
|
/// overhead than calling `halt` over and over.
|
||||||
|
///
|
||||||
|
/// When using this routing your interrupt handler MUST update the BIOS
|
||||||
|
/// Interrupt Flags `0x300_7FF8` in addition to the usual interrupt
|
||||||
|
/// acknowledgement.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: u16) {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x04"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ "{r0}"(ignore_current_flags), "{r1}"(target_flags)
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO(lokathor): newtype this flag business.
|
||||||
|
|
||||||
|
/// (`swi 0x05`) "VBlankIntrWait", VBlank Interrupt Wait.
|
||||||
|
///
|
||||||
|
/// This is as per `interrupt_wait(true, 1)` (aka "wait for a new vblank"). You
|
||||||
|
/// must follow the same guidelines that `interrupt_wait` outlines.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn vblank_interrupt_wait() {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x04"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ "r0", "r1" // both set to 1 by the routine
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x06`) Software Division and Remainder.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// If the denominator is 0.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) {
|
||||||
|
assert!(denominator != 0);
|
||||||
|
if cfg!(test) {
|
||||||
|
(numerator / denominator, numerator % denominator)
|
||||||
|
} else {
|
||||||
|
let div_out: i32;
|
||||||
|
let rem_out: i32;
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x06"
|
||||||
|
:/* OUT */ "={r0}"(div_out), "={r1}"(rem_out)
|
||||||
|
:/* INP */ "{r0}"(numerator), "{r1}"(denominator)
|
||||||
|
:/* CLO */ "r3"
|
||||||
|
:/* OPT */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(div_out, rem_out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// As `div_rem`, keeping only the `div` output.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn div(numerator: i32, denominator: i32) -> i32 {
|
||||||
|
div_rem(numerator, denominator).0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// As `div_rem`, keeping only the `rem` output.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn rem(numerator: i32, denominator: i32) -> i32 {
|
||||||
|
div_rem(numerator, denominator).1
|
||||||
|
}
|
||||||
|
|
||||||
|
// (`swi 0x07`): We deliberately don't implement this one. It's the same as DIV
|
||||||
|
// but with reversed arguments, so it just runs 3 cycles slower as it does the
|
||||||
|
// swap.
|
||||||
|
|
||||||
|
/// (`swi 0x08`) Integer square root.
|
||||||
|
///
|
||||||
|
/// If you want more fractional precision, you can shift your input to the left
|
||||||
|
/// by `2n` bits to get `n` more bits of fractional precision in your output.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn sqrt(val: u32) -> u16 {
|
||||||
|
let out: u16;
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x08"
|
||||||
|
:/* OUT */ "={r0}"(out)
|
||||||
|
:/* INP */ "{r0}"(val)
|
||||||
|
:/* CLO */ "r1", "r3"
|
||||||
|
:/* OPT */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x09`) Gives the arctangent of `theta`.
|
||||||
|
///
|
||||||
|
/// The input format is 1 bit for sign, 1 bit for integral part, 14 bits for
|
||||||
|
/// fractional part.
|
||||||
|
///
|
||||||
|
/// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn atan(theta: i16) -> i16 {
|
||||||
|
let out: i16;
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x09"
|
||||||
|
:/* OUT */ "={r0}"(out)
|
||||||
|
:/* INP */ "{r0}"(theta)
|
||||||
|
:/* CLO */ "r1", "r3"
|
||||||
|
:/* OPT */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x0A`) Gives the atan2 of `y` over `x`.
|
||||||
|
///
|
||||||
|
/// The output `theta` value maps into the range `[0, 2pi)`, or `0 .. 2pi` if
|
||||||
|
/// you prefer Rust's range notation.
|
||||||
|
///
|
||||||
|
/// `y` and `x` use the same format as with `atan`: 1 bit for sign, 1 bit for
|
||||||
|
/// integral, 14 bits for fractional.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn atan2(y: i16, x: i16) -> u16 {
|
||||||
|
let out: u16;
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x0A"
|
||||||
|
:/* OUT */ "={r0}"(out)
|
||||||
|
:/* INP */ "{r0}"(x), "{r1}"(y)
|
||||||
|
:/* CLO */ "r3"
|
||||||
|
:/* OPT */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x0B`) "CpuSet", `u16` memory copy.
|
||||||
|
///
|
||||||
|
/// * `count` is the number of `u16` values to copy (20 bits or less)
|
||||||
|
/// * `fixed_source` argument, if true, turns this copying routine into a
|
||||||
|
/// filling routine.
|
||||||
|
///
|
||||||
|
/// ## Safety
|
||||||
|
///
|
||||||
|
/// * Both pointers must be aligned
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_source: bool) {
|
||||||
|
let control = count + ((fixed_source as u32) << 24);
|
||||||
|
asm!(/* ASM */ "swi 0x0B"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x0B`) "CpuSet", `u32` memory copy/fill.
|
||||||
|
///
|
||||||
|
/// * `count` is the number of `u32` values to copy (20 bits or less)
|
||||||
|
/// * `fixed_source` argument, if true, turns this copying routine into a
|
||||||
|
/// filling routine.
|
||||||
|
///
|
||||||
|
/// ## Safety
|
||||||
|
///
|
||||||
|
/// * Both pointers must be aligned
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
|
||||||
|
let control = count + ((fixed_source as u32) << 24) + (1 << 26);
|
||||||
|
asm!(/* ASM */ "swi 0x0B"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x0C`) "CpuFastSet", copies memory in 32 byte chunks.
|
||||||
|
///
|
||||||
|
/// * The `count` value is the number of `u32` values to transfer (20 bits or
|
||||||
|
/// less), and it's rounded up to the nearest multiple of 8 words.
|
||||||
|
/// * The `fixed_source` argument, if true, turns this copying routine into a
|
||||||
|
/// filling routine.
|
||||||
|
///
|
||||||
|
/// ## Safety
|
||||||
|
///
|
||||||
|
/// * Both pointers must be aligned
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
|
||||||
|
let control = count + ((fixed_source as u32) << 24);
|
||||||
|
asm!(/* ASM */ "swi 0x0C"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ "{r0}"(src), "{r1}"(dest), "{r2}"(control)
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x0C`) "GetBiosChecksum" (Undocumented)
|
||||||
|
///
|
||||||
|
/// Though we usually don't cover undocumented functionality, this one can make
|
||||||
|
/// it into the crate.
|
||||||
|
///
|
||||||
|
/// The function computes the checksum of the BIOS data. You should get either
|
||||||
|
/// `0xBAAE_187F` (GBA / GBA SP) or `0xBAAE_1880` (DS in GBA mode). If you get
|
||||||
|
/// some other value I guess you're probably running on an emulator that just
|
||||||
|
/// broke the fourth wall.
|
||||||
|
pub fn get_bios_checksum() -> u32 {
|
||||||
|
let out: u32;
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x0D"
|
||||||
|
:/* OUT */ "={r0}"(out)
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ // none
|
||||||
|
);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: these things will require that we build special structs
|
||||||
|
|
||||||
|
//BgAffineSet
|
||||||
|
//ObjAffineSet
|
||||||
|
//BitUnPack
|
||||||
|
//LZ77UnCompReadNormalWrite8bit
|
||||||
|
//LZ77UnCompReadNormalWrite16bit
|
||||||
|
//HuffUnCompReadNormal
|
||||||
|
//RLUnCompReadNormalWrite8bit
|
||||||
|
//Diff8bitUnFilterWrite8bit
|
||||||
|
//Diff8bitUnFilterWrite16bit
|
||||||
|
//Diff16bitUnFilter
|
||||||
|
|
||||||
|
/// (`swi 0x19`) "SoundBias", adjusts the volume level to a new level.
|
||||||
|
///
|
||||||
|
/// This increases or decreases the current level of the `SOUNDBIAS` register
|
||||||
|
/// (with short delays) until at the new target level. The upper bits of the
|
||||||
|
/// register are unaffected.
|
||||||
|
///
|
||||||
|
/// The final sound level setting will be `level` * `0x200`.
|
||||||
|
pub fn sound_bias(level: u32) {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x19"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ "{r0}"(level)
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//SoundDriverInit
|
||||||
|
|
||||||
|
/// (`swi 0x1B`) "SoundDriverMode", sets the sound driver operation mode.
|
||||||
|
///
|
||||||
|
/// The `mode` input uses the following flags and bits:
|
||||||
|
///
|
||||||
|
/// * Bits 0-6: Reverb value
|
||||||
|
/// * Bit 7: Reverb Enable
|
||||||
|
/// * Bits 8-11: Simultaneously-produced channel count (default=8)
|
||||||
|
/// * Bits 12-15: Master Volume (1-15, default=15)
|
||||||
|
/// * Bits 16-19: Playback Frequency Index (see below, default=4)
|
||||||
|
/// * Bits 20-23: "Final number of D/A converter bits (8-11 = 9-6bits, def. 9=8bits)" TODO: what the hek?
|
||||||
|
/// * Bits 24 and up: Not used
|
||||||
|
///
|
||||||
|
/// The frequency index selects a frequency from the following array:
|
||||||
|
/// * 0: 5734
|
||||||
|
/// * 1: 7884
|
||||||
|
/// * 2: 10512
|
||||||
|
/// * 3: 13379
|
||||||
|
/// * 4: 15768
|
||||||
|
/// * 5: 18157
|
||||||
|
/// * 6: 21024
|
||||||
|
/// * 7: 26758
|
||||||
|
/// * 8: 31536
|
||||||
|
/// * 9: 36314
|
||||||
|
/// * 10: 40137
|
||||||
|
/// * 11: 42048
|
||||||
|
pub fn sound_driver_mode(mode: u32) {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x1B"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ "{r0}"(mode)
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO(lokathor): newtype this mode business.
|
||||||
|
|
||||||
|
/// (`swi 0x1C`) "SoundDriverMain", main of the sound driver
|
||||||
|
///
|
||||||
|
/// You should call `SoundDriverVSync` immediately after the vblank interrupt
|
||||||
|
/// fires.
|
||||||
|
///
|
||||||
|
/// "After that, this routine is called after BG and OBJ processing is
|
||||||
|
/// executed." --what?
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn sound_driver_main() {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x1C"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x1D`) "SoundDriverVSync", resets the sound DMA.
|
||||||
|
///
|
||||||
|
/// The timing is critical, so you should call this _immediately_ after the
|
||||||
|
/// vblank interrupt (every 1/60th of a second).
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn sound_driver_vsync() {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x1D"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x1E`) "SoundChannelClear", clears the direct sound channels and stops
|
||||||
|
/// the sound.
|
||||||
|
///
|
||||||
|
/// "This function may not operate properly when the library which expands the
|
||||||
|
/// sound driver feature is combined afterwards. In this case, do not use it."
|
||||||
|
/// --what?
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn sound_channel_clear() {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x1E"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MidiKey2Freq
|
||||||
|
//MultiBoot
|
||||||
|
|
||||||
|
/// (`swi 0x28`) "SoundDriverVSyncOff", disables sound
|
||||||
|
///
|
||||||
|
/// If you can't use vblank interrupts to ensure that `sound_driver_vsync` is
|
||||||
|
/// called every 1/60th of a second for any reason you must use this function to
|
||||||
|
/// stop sound DMA. Otherwise the DMA will overrun its buffer and cause random
|
||||||
|
/// noise.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn sound_driver_vsync_off() {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x28"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (`swi 0x29`) "SoundDriverVSyncOn", enables sound that was stopped by
|
||||||
|
/// `sound_driver_vsync_off`.
|
||||||
|
///
|
||||||
|
/// Restarts sound DMA system. After restarting the sound you must have a vblank
|
||||||
|
/// interrupt followed by a `sound_driver_vsync` within 2/60th of a second.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn sound_driver_vsync_on() {
|
||||||
|
unsafe {
|
||||||
|
asm!(/* ASM */ "swi 0x29"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
77
src/builtins.rs
Normal file
77
src/builtins.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
//! The module to provide "builtin" functions that LLVM expects.
|
||||||
|
//!
|
||||||
|
//! You shouldn't need to call anything in here yourself, it just has to be in
|
||||||
|
//! the translation unit and LLVM will find it.
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
#[cfg(any(target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))]
|
||||||
|
pub extern "C" fn __clzsi2(mut x: usize) -> usize {
|
||||||
|
// TODO: const this? Requires const if
|
||||||
|
let mut y: usize;
|
||||||
|
let mut n: usize = {
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
{
|
||||||
|
64
|
||||||
|
}
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
{
|
||||||
|
32
|
||||||
|
}
|
||||||
|
#[cfg(target_pointer_width = "16")]
|
||||||
|
{
|
||||||
|
16
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
{
|
||||||
|
y = x >> 32;
|
||||||
|
if y != 0 {
|
||||||
|
n -= 32;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))]
|
||||||
|
{
|
||||||
|
y = x >> 16;
|
||||||
|
if y != 0 {
|
||||||
|
n -= 16;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y = x >> 8;
|
||||||
|
if y != 0 {
|
||||||
|
n -= 8;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 4;
|
||||||
|
if y != 0 {
|
||||||
|
n -= 4;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 2;
|
||||||
|
if y != 0 {
|
||||||
|
n -= 2;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
y = x >> 1;
|
||||||
|
if y != 0 {
|
||||||
|
n - 2
|
||||||
|
} else {
|
||||||
|
n - x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn __clzsi2_test() {
|
||||||
|
let mut i = 1 << 63;
|
||||||
|
while i > 0 {
|
||||||
|
assert_eq!(__clzsi2(i), i.leading_zeros() as usize);
|
||||||
|
i >>= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add some shims
|
||||||
|
// #[no_mangle] extern "aapcs" fn __aeabi_uidiv(num: u32: denom: u32) -> u32
|
||||||
|
// #[no_mangle] extern "aapcs" fn __aeabi_idiv(num: i32: denom: i32) -> u32
|
|
@ -1,39 +1,301 @@
|
||||||
//! Things that I wish were in core, but aren't.
|
//! Things that I wish were in core, but aren't.
|
||||||
|
|
||||||
/// A simple wrapper for any `*mut T` to adjust the basic operations.
|
//TODO(Lokathor): reorganize as gba::core_extras::fixed_point and gba::core_extras::volatile ?
|
||||||
|
|
||||||
|
use core::{cmp::Ordering, iter::FusedIterator, marker::PhantomData, num::NonZeroUsize};
|
||||||
|
|
||||||
|
/// Abstracts the use of a volatile hardware address.
|
||||||
///
|
///
|
||||||
/// Read and Write are made to be volatile. Offset is made to be
|
/// If you're trying to do anything other than abstract a volatile hardware
|
||||||
/// wrapping_offset. This makes it much easier to correctly work with IO
|
/// device then you _do not want to use this type_. Use one of the many other
|
||||||
/// Registers and all display related memory on the GBA.
|
/// smart pointer types.
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
///
|
||||||
|
/// A volatile address doesn't store a value in the normal way: It maps to some
|
||||||
|
/// real hardware _other than_ RAM, and that hardware might have any sort of
|
||||||
|
/// strange rules. The specifics of reading and writing depend on the hardware
|
||||||
|
/// being mapped. For example, a particular address might be read only (ignoring
|
||||||
|
/// writes), write only (returning some arbitrary value if you read it),
|
||||||
|
/// "normal" read write (where you read back what you wrote), or some complex
|
||||||
|
/// read-write situation where writes have an effect but you _don't_ read back
|
||||||
|
/// what you wrote.
|
||||||
|
///
|
||||||
|
/// As you imagine it can be very unsafe. The design of this type is set up so
|
||||||
|
/// that _creation_ is unsafe, and _use_ is safe. This gives an optimal
|
||||||
|
/// experience, since you'll use memory locations a lot more often than you try
|
||||||
|
/// to name them, on average.
|
||||||
|
///
|
||||||
|
/// `VolAddress` is _not_ a thread safe type. If your device is multi-threaded
|
||||||
|
/// then you must arrange for synchronization in some other way. A `VolAddress`
|
||||||
|
/// _can_ be used to share data between an interrupt running on a core and a
|
||||||
|
/// thread running on that core as long as all access of that location is
|
||||||
|
/// volatile (if you're using the `asm!` macro add the "volatile" option, if
|
||||||
|
/// you're linking in ASM with the linker that's effectively volatile since the
|
||||||
|
/// compiler doesn't get a chance to mess with it).
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// In order for values of this type to operate correctly they must follow quite
|
||||||
|
/// a few safety limits:
|
||||||
|
///
|
||||||
|
/// * The declared address must be non-null (it uses the `NonNull` optimization
|
||||||
|
/// for better iteration results). This shouldn't be a big problem, since
|
||||||
|
/// hardware can't really live at the null address.
|
||||||
|
/// * The declared address must be aligned for the declared type of `T`.
|
||||||
|
/// * The declared address must _always_ read as something that's a valid bit
|
||||||
|
/// pattern for `T`. Don't pick any enums or things like that if your hardware
|
||||||
|
/// doesn't back it up. If there's _any_ doubt at all, you must instead read
|
||||||
|
/// or write an unsigned int of the correct bit size and then parse the bits
|
||||||
|
/// by hand.
|
||||||
|
/// * The declared address must be a part of the address space that Rust's
|
||||||
|
/// allocator and/or stack frames will never use. If you're not sure, please
|
||||||
|
/// re-read the hardware specs of your device and its memory map until you
|
||||||
|
/// know.
|
||||||
|
///
|
||||||
|
/// The exact points of UB are if the address is ever 0, or if you ever `read`
|
||||||
|
/// or `write` with the invalid pointer. For example, if you offset to some
|
||||||
|
/// crazy (non-zero) value and then never use it that won't be an immediate
|
||||||
|
/// trigger of UB.
|
||||||
|
#[derive(Debug)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct VolatilePtr<T>(pub *mut T);
|
pub struct VolAddress<T> {
|
||||||
|
address: NonZeroUsize,
|
||||||
impl<T> core::fmt::Pointer for VolatilePtr<T> {
|
marker: PhantomData<*mut T>,
|
||||||
/// Formats exactly like the inner `*mut T`.
|
}
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
// Note(Lokathor): We have to hand implement all these traits because if we use
|
||||||
write!(f, "{:p}", self.0)
|
// `derive` then they only get derived if the inner `T` has the trait. However,
|
||||||
|
// since we're acting like a pointer to `T`, the capability we offer isn't
|
||||||
|
// affected by whatever type `T` ends up being.
|
||||||
|
impl<T> Clone for VolAddress<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Copy for VolAddress<T> {}
|
||||||
|
impl<T> PartialEq for VolAddress<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.address == other.address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Eq for VolAddress<T> {}
|
||||||
|
impl<T> PartialOrd for VolAddress<T> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.address.cmp(&other.address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Ord for VolAddress<T> {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.address.cmp(&other.address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> VolatilePtr<T> {
|
impl<T> VolAddress<T> {
|
||||||
/// Performs a `read_volatile`.
|
/// Constructs a new address.
|
||||||
pub unsafe fn read(&self) -> T {
|
///
|
||||||
self.0.read_volatile()
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must follow the standard safety rules as outlined in the type docs.
|
||||||
|
pub const unsafe fn new_unchecked(address: usize) -> Self {
|
||||||
|
VolAddress {
|
||||||
|
address: NonZeroUsize::new_unchecked(address),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a `write_volatile`.
|
/// Casts the type of `T` into type `Z`.
|
||||||
pub unsafe fn write(&self, data: T) {
|
///
|
||||||
self.0.write_volatile(data);
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must follow the standard safety rules as outlined in the type docs.
|
||||||
|
pub const unsafe fn cast<Z>(self) -> VolAddress<Z> {
|
||||||
|
VolAddress {
|
||||||
|
address: self.address,
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a `wrapping_offset`.
|
/// Offsets the address by `offset` slots (like `pointer::wrapping_offset`).
|
||||||
pub fn offset(self, count: isize) -> Self {
|
///
|
||||||
VolatilePtr(self.0.wrapping_offset(count))
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must follow the standard safety rules as outlined in the type docs.
|
||||||
|
pub unsafe fn offset(self, offset: isize) -> Self {
|
||||||
|
// TODO: const this
|
||||||
|
VolAddress {
|
||||||
|
address: NonZeroUsize::new_unchecked(self.address.get().wrapping_add(offset as usize * core::mem::size_of::<T>())),
|
||||||
|
marker: PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a cast into some new pointer type.
|
/// Checks that the current target type of this address is aligned at this
|
||||||
pub fn cast<Z>(self) -> VolatilePtr<Z> {
|
/// address value.
|
||||||
VolatilePtr(self.0 as *mut Z)
|
///
|
||||||
|
/// Technically it's a safety violation to even make a `VolAddress` that isn't
|
||||||
|
/// aligned. However, I know you're gonna try doing the bad thing, and it's
|
||||||
|
/// better to give you a chance to call `is_aligned` and potentially back off
|
||||||
|
/// from the operation or throw a `debug_assert!` or something instead of
|
||||||
|
/// triggering UB. Eventually this will be `const fn`, which will potentially
|
||||||
|
/// let you spot errors without even having to run your program.
|
||||||
|
pub fn is_aligned(self) -> bool {
|
||||||
|
// TODO: const this
|
||||||
|
self.address.get() % core::mem::align_of::<T>() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes an iterator starting here across the given number of slots.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The normal safety rules must be correct for each address iterated over.
|
||||||
|
pub const unsafe fn iter_slots(self, slots: usize) -> VolAddressIter<T> {
|
||||||
|
VolAddressIter { vol_address: self, slots }
|
||||||
|
}
|
||||||
|
|
||||||
|
// non-const and never can be.
|
||||||
|
|
||||||
|
/// Reads a `Copy` value out of the address.
|
||||||
|
///
|
||||||
|
/// The `Copy` bound is actually supposed to be `!Drop`, but rust doesn't
|
||||||
|
/// allow negative trait bounds. If your type isn't `Copy` you can use the
|
||||||
|
/// `read_non_copy` fallback to do an unsafe read.
|
||||||
|
///
|
||||||
|
/// That said, I don't think that you legitimately have hardware that maps to
|
||||||
|
/// a Rust type with a `Drop` impl. If you do please tell me, I'm interested
|
||||||
|
/// to hear about it.
|
||||||
|
pub fn read(self) -> T
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
unsafe { (self.address.get() as *mut T).read_volatile() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a value out of the address with no trait bound.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This is _not_ a move, it forms a bit duplicate of the current address
|
||||||
|
/// value. If `T` has a `Drop` trait that does anything it is up to you to
|
||||||
|
/// ensure that repeated drops do not cause UB (such as a double free).
|
||||||
|
pub unsafe fn read_non_copy(self) -> T {
|
||||||
|
(self.address.get() as *mut T).read_volatile()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a value to the address.
|
||||||
|
///
|
||||||
|
/// Semantically, the value is moved into the `VolAddress` and then forgotten,
|
||||||
|
/// so if `T` has a `Drop` impl then that will never get executed. This is
|
||||||
|
/// "safe" under Rust's safety rules, but could cause something unintended
|
||||||
|
/// (eg: a memory leak).
|
||||||
|
pub fn write(self, val: T) {
|
||||||
|
unsafe { (self.address.get() as *mut T).write_volatile(val) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator that produces a series of `VolAddress` values.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VolAddressIter<T> {
|
||||||
|
vol_address: VolAddress<T>,
|
||||||
|
slots: usize,
|
||||||
|
}
|
||||||
|
impl<T> Clone for VolAddressIter<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
VolAddressIter {
|
||||||
|
vol_address: self.vol_address,
|
||||||
|
slots: self.slots,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> PartialEq for VolAddressIter<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.vol_address == other.vol_address && self.slots == other.slots
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Eq for VolAddressIter<T> {}
|
||||||
|
impl<T> Iterator for VolAddressIter<T> {
|
||||||
|
type Item = VolAddress<T>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.slots > 0 {
|
||||||
|
let out = self.vol_address;
|
||||||
|
unsafe {
|
||||||
|
self.slots -= 1;
|
||||||
|
self.vol_address = self.vol_address.offset(1);
|
||||||
|
}
|
||||||
|
Some(out)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> FusedIterator for VolAddressIter<T> {}
|
||||||
|
|
||||||
|
/// This type is like `VolAddress`, but for when you have a block of values all
|
||||||
|
/// in a row.
|
||||||
|
///
|
||||||
|
/// This is similar to the idea of an array or a slice, but called a "block"
|
||||||
|
/// because you could _also_ construct a `[VolAddress]`, and we want to avoid
|
||||||
|
/// any accidental confusion.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VolAddressBlock<T> {
|
||||||
|
vol_address: VolAddress<T>,
|
||||||
|
slots: usize,
|
||||||
|
}
|
||||||
|
impl<T> Clone for VolAddressBlock<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
VolAddressBlock {
|
||||||
|
vol_address: self.vol_address,
|
||||||
|
slots: self.slots,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> PartialEq for VolAddressBlock<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.vol_address == other.vol_address && self.slots == other.slots
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Eq for VolAddressBlock<T> {}
|
||||||
|
|
||||||
|
impl<T> VolAddressBlock<T> {
|
||||||
|
/// Constructs a new `VolAddressBlock`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The given `VolAddress` must be valid when offset by each of `0 .. slots`
|
||||||
|
pub const unsafe fn new_unchecked(vol_address: VolAddress<T>, slots: usize) -> Self {
|
||||||
|
VolAddressBlock { vol_address, slots }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives an iterator over this block's slots.
|
||||||
|
pub const fn iter(self) -> VolAddressIter<T> {
|
||||||
|
VolAddressIter {
|
||||||
|
vol_address: self.vol_address,
|
||||||
|
slots: self.slots,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unchecked indexing into the block.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The slot given must be in bounds.
|
||||||
|
pub unsafe fn index_unchecked(self, slot: usize) -> VolAddress<T> {
|
||||||
|
// TODO: const this
|
||||||
|
self.vol_address.offset(slot as isize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checked "indexing" style access of the block, giving either a `VolAddress` or a panic.
|
||||||
|
pub fn index(self, slot: usize) -> VolAddress<T> {
|
||||||
|
if slot < self.slots {
|
||||||
|
unsafe { self.vol_address.offset(slot as isize) }
|
||||||
|
} else {
|
||||||
|
panic!("Index Requested: {} >= Bound: {}", slot, self.slots)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checked "getting" style access of the block, giving an Option value.
|
||||||
|
pub fn get(self, slot: usize) -> Option<VolAddress<T>> {
|
||||||
|
if slot < self.slots {
|
||||||
|
unsafe { Some(self.vol_address.offset(slot as isize)) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
295
src/fixed.rs
Normal file
295
src/fixed.rs
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
|
//! Module for fixed point math types and operations.
|
||||||
|
|
||||||
|
use core::{
|
||||||
|
marker::PhantomData,
|
||||||
|
ops::{Add, Div, Mul, Neg, Shl, Shr, Sub},
|
||||||
|
};
|
||||||
|
use typenum::{consts::False, marker_traits::Unsigned, type_operators::IsEqual, U8};
|
||||||
|
|
||||||
|
/// Fixed point `T` value with `F` fractional bits.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct Fx<T, F: Unsigned> {
|
||||||
|
num: T,
|
||||||
|
phantom: PhantomData<F>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, F: Unsigned> Fx<T, F> {
|
||||||
|
/// Uses the provided value directly.
|
||||||
|
pub fn from_raw(r: T) -> Self {
|
||||||
|
Fx {
|
||||||
|
num: r,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unwraps the inner value.
|
||||||
|
pub fn into_raw(self) -> T {
|
||||||
|
self.num
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Casts the base type, keeping the fractional bit quantity the same.
|
||||||
|
pub fn cast_inner<Z, C: Fn(T) -> Z>(self, op: C) -> Fx<Z, F> {
|
||||||
|
Fx {
|
||||||
|
num: op(self.num),
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Add<Output = T>, F: Unsigned> Add for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Fx<T, F>) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: self.num + rhs.num,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Sub<Output = T>, F: Unsigned> Sub for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn sub(self, rhs: Fx<T, F>) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: self.num - rhs.num,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Shl<u32, Output = T>, F: Unsigned> Shl<u32> for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn shl(self, rhs: u32) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: self.num << rhs,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Shr<u32, Output = T>, F: Unsigned> Shr<u32> for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn shr(self, rhs: u32) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: self.num >> rhs,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Neg<Output = T>, F: Unsigned> Neg for Fx<T, F> {
|
||||||
|
type Output = Self;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: -self.num,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! fixed_point_methods {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Fx<$t, F> {
|
||||||
|
/// Gives the smallest positive non-zero value.
|
||||||
|
pub fn precision() -> Self {
|
||||||
|
Fx {
|
||||||
|
num: 1,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a value with the integer part shifted into place.
|
||||||
|
pub fn from_int_part(i: $t) -> Self {
|
||||||
|
Fx {
|
||||||
|
num: i << F::U8,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the fractional bit quantity, keeping the base type the same.
|
||||||
|
pub fn adjust_fractional_bits<Y: Unsigned + IsEqual<F, Output = False>>(self) -> Fx<$t, Y> {
|
||||||
|
let leftward_movement: i32 = Y::to_i32() - F::to_i32();
|
||||||
|
Fx {
|
||||||
|
num: if leftward_movement > 0 {
|
||||||
|
self.num << leftward_movement
|
||||||
|
} else {
|
||||||
|
self.num >> (-leftward_movement)
|
||||||
|
},
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_methods! {u8}
|
||||||
|
fixed_point_methods! {i8}
|
||||||
|
fixed_point_methods! {i16}
|
||||||
|
fixed_point_methods! {u16}
|
||||||
|
fixed_point_methods! {i32}
|
||||||
|
fixed_point_methods! {u32}
|
||||||
|
|
||||||
|
macro_rules! fixed_point_signed_multiply {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Mul for Fx<$t, F> {
|
||||||
|
type Output = Self;
|
||||||
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||||
|
fn mul(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
|
let pre_shift = (self.num as i32).wrapping_mul(rhs.num as i32);
|
||||||
|
if pre_shift < 0 {
|
||||||
|
if pre_shift == core::i32::MIN {
|
||||||
|
Fx {
|
||||||
|
num: core::$t::MIN,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Fx {
|
||||||
|
num: (-((-pre_shift) >> F::U8)) as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Fx {
|
||||||
|
num: (pre_shift >> F::U8) as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_signed_multiply! {i8}
|
||||||
|
fixed_point_signed_multiply! {i16}
|
||||||
|
fixed_point_signed_multiply! {i32}
|
||||||
|
|
||||||
|
macro_rules! fixed_point_unsigned_multiply {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Mul for Fx<$t, F> {
|
||||||
|
type Output = Self;
|
||||||
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||||
|
fn mul(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
|
Fx {
|
||||||
|
num: ((self.num as u32).wrapping_mul(rhs.num as u32) >> F::U8) as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_unsigned_multiply! {u8}
|
||||||
|
fixed_point_unsigned_multiply! {u16}
|
||||||
|
fixed_point_unsigned_multiply! {u32}
|
||||||
|
|
||||||
|
macro_rules! fixed_point_signed_division {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Div for Fx<$t, F> {
|
||||||
|
type Output = Self;
|
||||||
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||||
|
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
|
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
||||||
|
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
||||||
|
Fx {
|
||||||
|
num: divide_result as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_signed_division! {i8}
|
||||||
|
fixed_point_signed_division! {i16}
|
||||||
|
fixed_point_signed_division! {i32}
|
||||||
|
|
||||||
|
macro_rules! fixed_point_unsigned_division {
|
||||||
|
($t:ident) => {
|
||||||
|
impl<F: Unsigned> Div for Fx<$t, F> {
|
||||||
|
type Output = Self;
|
||||||
|
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||||
|
fn div(self, rhs: Fx<$t, F>) -> Self::Output {
|
||||||
|
let mul_output: i32 = (self.num as i32).wrapping_mul(1 << F::U8);
|
||||||
|
let divide_result: i32 = crate::bios::div(mul_output, rhs.num as i32);
|
||||||
|
Fx {
|
||||||
|
num: divide_result as $t,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_point_unsigned_division! {u8}
|
||||||
|
fixed_point_unsigned_division! {u16}
|
||||||
|
fixed_point_unsigned_division! {u32}
|
||||||
|
|
||||||
|
/// Alias for an `i16` fixed point value with 8 fractional bits.
|
||||||
|
pub type fx8_8 = Fx<i16, U8>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod fixed_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add() {
|
||||||
|
use typenum::U4;
|
||||||
|
let one = Fx::<u16, U4>::from_int_part(1);
|
||||||
|
let two = Fx::<u16, U4>::from_int_part(2);
|
||||||
|
assert!(one + one == two)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sub() {
|
||||||
|
use typenum::U4;
|
||||||
|
let one = Fx::<u16, U4>::from_int_part(1);
|
||||||
|
let two = Fx::<u16, U4>::from_int_part(2);
|
||||||
|
assert!(two - one == one)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shl() {
|
||||||
|
use typenum::U4;
|
||||||
|
let one = Fx::<u16, U4>::from_int_part(1);
|
||||||
|
let two = Fx::<u16, U4>::from_int_part(2);
|
||||||
|
assert!(one << 1 == two)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shr() {
|
||||||
|
use typenum::U4;
|
||||||
|
let one = Fx::<u16, U4>::from_int_part(1);
|
||||||
|
let two = Fx::<u16, U4>::from_int_part(2);
|
||||||
|
assert!(two >> 1 == one)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_neg() {
|
||||||
|
use typenum::U4;
|
||||||
|
let one = Fx::<i16, U4>::from_int_part(1);
|
||||||
|
let neg_one = Fx::<i16, U4>::from_int_part(-1);
|
||||||
|
assert!(-one == neg_one);
|
||||||
|
assert!(-(-one) == one);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mul() {
|
||||||
|
use typenum::U4;
|
||||||
|
let half = Fx::<u16, U4>::from_int_part(1) >> 1;
|
||||||
|
let two = Fx::<u16, U4>::from_int_part(2);
|
||||||
|
let three = Fx::<u16, U4>::from_int_part(3);
|
||||||
|
let twelve = Fx::<u16, U4>::from_int_part(12);
|
||||||
|
assert!(two * three == twelve * half);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_div() {
|
||||||
|
use typenum::U4;
|
||||||
|
let two = Fx::<u16, U4>::from_int_part(2);
|
||||||
|
let six = Fx::<u16, U4>::from_int_part(6);
|
||||||
|
let twelve = Fx::<u16, U4>::from_int_part(12);
|
||||||
|
assert!(twelve / two == six);
|
||||||
|
}
|
||||||
|
}
|
13
src/io.rs
Normal file
13
src/io.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
//! This module contains definitions and types for the IO Registers.
|
||||||
|
//!
|
||||||
|
//! ## Naming
|
||||||
|
//!
|
||||||
|
//! In the interest of making things easy to search for, all io register
|
||||||
|
//! constants are given the names used in the
|
||||||
|
//! [GBATEK](https://problemkaputt.de/gbatek.htm) technical description.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use gba_proc_macro::register_bit;
|
||||||
|
|
||||||
|
pub mod keypad;
|
121
src/io/keypad.rs
Normal file
121
src/io/keypad.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
//! Allows access to the keypad.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// The Key Input Register.
|
||||||
|
///
|
||||||
|
/// This register follows the "low-active" convention. If you want your code to
|
||||||
|
/// follow the "high-active" convention (hint: you probably do, it's far easier
|
||||||
|
/// to work with) then call `read_key_input()` rather than reading this register
|
||||||
|
/// directly. It will perform the necessary bit flip operation for you.
|
||||||
|
pub const KEYINPUT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0130) };
|
||||||
|
|
||||||
|
/// A "tribool" value helps us interpret the arrow pad.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum TriBool {
|
||||||
|
Minus = -1,
|
||||||
|
Neutral = 0,
|
||||||
|
Plus = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype! {
|
||||||
|
/// Records a particular key press combination.
|
||||||
|
///
|
||||||
|
/// Methods here follow the "high-active" convention, where a bit is enabled
|
||||||
|
/// when it's part of the set.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
|
KeyInput, u16
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
impl KeyInput {
|
||||||
|
register_bit!(A_BIT, u16, 1, a_pressed);
|
||||||
|
register_bit!(B_BIT, u16, 1 << 1, b_pressed);
|
||||||
|
register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed);
|
||||||
|
register_bit!(START_BIT, u16, 1 << 3, start_pressed);
|
||||||
|
register_bit!(RIGHT_BIT, u16, 1 << 4, right_pressed);
|
||||||
|
register_bit!(LEFT_BIT, u16, 1 << 5, left_pressed);
|
||||||
|
register_bit!(UP_BIT, u16, 1 << 6, up_pressed);
|
||||||
|
register_bit!(DOWN_BIT, u16, 1 << 7, down_pressed);
|
||||||
|
register_bit!(R_BIT, u16, 1 << 8, r_pressed);
|
||||||
|
register_bit!(L_BIT, u16, 1 << 9, l_pressed);
|
||||||
|
|
||||||
|
/// Takes the set difference between these keys and another set of keys.
|
||||||
|
pub fn difference(self, other: Self) -> Self {
|
||||||
|
KeyInput(self.0 ^ other.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives the arrow pad value as a tribool, with Plus being increased column
|
||||||
|
/// value (right).
|
||||||
|
pub fn column_direction(self) -> TriBool {
|
||||||
|
if self.right_pressed() {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.left_pressed() {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives the arrow pad value as a tribool, with Plus being increased row
|
||||||
|
/// value (down).
|
||||||
|
pub fn row_direction(self) -> TriBool {
|
||||||
|
if self.down_pressed() {
|
||||||
|
TriBool::Plus
|
||||||
|
} else if self.up_pressed() {
|
||||||
|
TriBool::Minus
|
||||||
|
} else {
|
||||||
|
TriBool::Neutral
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current state of the keys
|
||||||
|
pub fn read_key_input() -> KeyInput {
|
||||||
|
// Note(Lokathor): The 10 used bits are "low when pressed" style, but the 6
|
||||||
|
// unused bits are always low, so we XOR with this mask to get a result where
|
||||||
|
// the only active bits are currently pressed keys.
|
||||||
|
KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111)
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype! {
|
||||||
|
/// Allows configuration of when a keypad interrupt fires.
|
||||||
|
///
|
||||||
|
/// * The most important bit here is the `irq_enabled` bit, which determines
|
||||||
|
/// if an interrupt happens at all.
|
||||||
|
/// * The second most important bit is the `irq_logical_and` bit. If this bit
|
||||||
|
/// is set, _all_ the selected buttons are required to be set for the
|
||||||
|
/// interrupt to be fired (logical AND). If it's not set then _any_ of the
|
||||||
|
/// buttons selected can be pressed to fire the interrupt (logical OR).
|
||||||
|
/// * All other bits select a particular button to be required or not as part
|
||||||
|
/// of the interrupt firing.
|
||||||
|
///
|
||||||
|
/// NOTE: This _only_ configures the operation of when keypad interrupts can
|
||||||
|
/// fire. You must still set the `IME` to have interrupts at all, and you must
|
||||||
|
/// further set `IE` for keypad interrupts to be possible.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
|
KeyInterruptSetting, u16
|
||||||
|
}
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
impl KeyInterruptSetting {
|
||||||
|
register_bit!(A_BIT, u16, 1, a_pressed);
|
||||||
|
register_bit!(B_BIT, u16, 1 << 1, b_pressed);
|
||||||
|
register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed);
|
||||||
|
register_bit!(START_BIT, u16, 1 << 3, start_pressed);
|
||||||
|
register_bit!(RIGHT_BIT, u16, 1 << 4, right_pressed);
|
||||||
|
register_bit!(LEFT_BIT, u16, 1 << 5, left_pressed);
|
||||||
|
register_bit!(UP_BIT, u16, 1 << 6, up_pressed);
|
||||||
|
register_bit!(DOWN_BIT, u16, 1 << 7, down_pressed);
|
||||||
|
register_bit!(R_BIT, u16, 1 << 8, r_pressed);
|
||||||
|
register_bit!(L_BIT, u16, 1 << 9, l_pressed);
|
||||||
|
//
|
||||||
|
register_bit!(IRQ_ENABLE_BIT, u16, 1 << 14, irq_enabled);
|
||||||
|
register_bit!(IRQ_AND_BIT, u16, 1 << 15, irq_logical_and);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use this to configure when a keypad interrupt happens.
|
||||||
|
///
|
||||||
|
/// See the `KeyInterruptSetting` type for more.
|
||||||
|
pub const KEYCNT: VolAddress<KeyInterruptSetting> = unsafe { VolAddress::new_unchecked(0x400_0132) };
|
|
@ -15,19 +15,20 @@
|
||||||
|
|
||||||
// TODO(lokathor): IO Register newtypes.
|
// TODO(lokathor): IO Register newtypes.
|
||||||
|
|
||||||
use gba_proc_macro::{newtype, register_bit};
|
use gba_proc_macro::register_bit;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// LCD Control. Read/Write.
|
/// LCD Control. Read/Write.
|
||||||
///
|
///
|
||||||
/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
/// * [gbatek entry](http://problemkaputt.de/gbatek.htm#lcdiodisplaycontrol)
|
||||||
pub const DISPCNT: VolatilePtr<u16> = VolatilePtr(0x400_0000 as *mut u16);
|
pub const DISPCNT: VolAddress<DisplayControlSetting> = unsafe { VolAddress::new_unchecked(0x400_0000) };
|
||||||
|
|
||||||
newtype!(
|
newtype!(
|
||||||
|
/// A newtype over the various display control options that you have on a GBA.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
DisplayControlSetting,
|
DisplayControlSetting,
|
||||||
u16,
|
u16
|
||||||
"A newtype over the various display control options that you have on a GBA."
|
|
||||||
);
|
);
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
|
@ -97,24 +98,19 @@ pub enum DisplayControlMode {
|
||||||
|
|
||||||
/// Assigns the given display control setting.
|
/// Assigns the given display control setting.
|
||||||
pub fn set_display_control(setting: DisplayControlSetting) {
|
pub fn set_display_control(setting: DisplayControlSetting) {
|
||||||
unsafe {
|
DISPCNT.write(setting);
|
||||||
DISPCNT.write(setting.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/// Obtains the current display control setting.
|
/// Obtains the current display control setting.
|
||||||
pub fn display_control() -> DisplayControlSetting {
|
pub fn display_control() -> DisplayControlSetting {
|
||||||
unsafe { DisplayControlSetting(DISPCNT.read()) }
|
DISPCNT.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// General LCD Status (STAT,LYC)
|
|
||||||
pub const DISPSTAT: VolatilePtr<u16> = VolatilePtr(0x400_0004 as *mut u16);
|
|
||||||
|
|
||||||
/// Vertical Counter (LY)
|
/// Vertical Counter (LY)
|
||||||
pub const VCOUNT: VolatilePtr<u16> = VolatilePtr(0x400_0006 as *mut u16);
|
pub const VCOUNT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0006) };
|
||||||
|
|
||||||
/// Obtains the current VCount value.
|
/// Obtains the current VCount value.
|
||||||
pub fn vcount() -> u16 {
|
pub fn vcount() -> u16 {
|
||||||
unsafe { VCOUNT.read() }
|
VCOUNT.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a busy loop until VBlank starts.
|
/// Performs a busy loop until VBlank starts.
|
||||||
|
@ -128,369 +124,3 @@ pub fn wait_until_vdraw() {
|
||||||
// TODO: make this the better version with BIOS and interrupts and such.
|
// TODO: make this the better version with BIOS and interrupts and such.
|
||||||
while vcount() >= SCREEN_HEIGHT as u16 {}
|
while vcount() >= SCREEN_HEIGHT as u16 {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BG0 Control
|
|
||||||
pub const BG0CNT: VolatilePtr<u16> = VolatilePtr(0x400_0008 as *mut u16);
|
|
||||||
|
|
||||||
/// BG1 Control
|
|
||||||
pub const BG1CNT: VolatilePtr<u16> = VolatilePtr(0x400_000A as *mut u16);
|
|
||||||
|
|
||||||
/// BG2 Control
|
|
||||||
pub const BG2CNT: VolatilePtr<u16> = VolatilePtr(0x400_000C as *mut u16);
|
|
||||||
|
|
||||||
/// BG3 Control
|
|
||||||
pub const BG3CNT: VolatilePtr<u16> = VolatilePtr(0x400_000E as *mut u16);
|
|
||||||
|
|
||||||
/// BG0 X-Offset
|
|
||||||
pub const BG0HOFS: VolatilePtr<u16> = VolatilePtr(0x400_0010 as *mut u16);
|
|
||||||
|
|
||||||
/// BG0 Y-Offset
|
|
||||||
pub const BG0VOFS: VolatilePtr<u16> = VolatilePtr(0x400_0012 as *mut u16);
|
|
||||||
|
|
||||||
/// BG1 X-Offset
|
|
||||||
pub const BG1HOFS: VolatilePtr<u16> = VolatilePtr(0x400_0014 as *mut u16);
|
|
||||||
|
|
||||||
/// BG1 Y-Offset
|
|
||||||
pub const BG1VOFS: VolatilePtr<u16> = VolatilePtr(0x400_0016 as *mut u16);
|
|
||||||
|
|
||||||
/// BG2 X-Offset
|
|
||||||
pub const BG2HOFS: VolatilePtr<u16> = VolatilePtr(0x400_0018 as *mut u16);
|
|
||||||
|
|
||||||
/// BG2 Y-Offset
|
|
||||||
pub const BG2VOFS: VolatilePtr<u16> = VolatilePtr(0x400_001A as *mut u16);
|
|
||||||
|
|
||||||
/// BG3 X-Offset
|
|
||||||
pub const BG3HOFS: VolatilePtr<u16> = VolatilePtr(0x400_001C as *mut u16);
|
|
||||||
|
|
||||||
/// BG3 Y-Offset
|
|
||||||
pub const BG3VOFS: VolatilePtr<u16> = VolatilePtr(0x400_001E as *mut u16);
|
|
||||||
|
|
||||||
/// BG2 Rotation/Scaling Parameter A (dx)
|
|
||||||
pub const BG2PA: VolatilePtr<u16> = VolatilePtr(0x400_0020 as *mut u16);
|
|
||||||
|
|
||||||
/// BG2 Rotation/Scaling Parameter B (dmx)
|
|
||||||
pub const BG2PB: VolatilePtr<u16> = VolatilePtr(0x400_0022 as *mut u16);
|
|
||||||
|
|
||||||
/// BG2 Rotation/Scaling Parameter C (dy)
|
|
||||||
pub const BG2PC: VolatilePtr<u16> = VolatilePtr(0x400_0024 as *mut u16);
|
|
||||||
|
|
||||||
/// BG2 Rotation/Scaling Parameter D (dmy)
|
|
||||||
pub const BG2PD: VolatilePtr<u16> = VolatilePtr(0x400_0026 as *mut u16);
|
|
||||||
|
|
||||||
/// BG2 Reference Point X-Coordinate
|
|
||||||
pub const BG2X: VolatilePtr<u32> = VolatilePtr(0x400_0028 as *mut u32);
|
|
||||||
|
|
||||||
/// BG2 Reference Point Y-Coordinate
|
|
||||||
pub const BG2Y: VolatilePtr<u32> = VolatilePtr(0x400_002C as *mut u32);
|
|
||||||
|
|
||||||
/// BG3 Rotation/Scaling Parameter A (dx)
|
|
||||||
pub const BG3PA: VolatilePtr<u16> = VolatilePtr(0x400_0030 as *mut u16);
|
|
||||||
|
|
||||||
/// BG3 Rotation/Scaling Parameter B (dmx)
|
|
||||||
pub const BG3PB: VolatilePtr<u16> = VolatilePtr(0x400_0032 as *mut u16);
|
|
||||||
|
|
||||||
/// BG3 Rotation/Scaling Parameter C (dy)
|
|
||||||
pub const BG3PC: VolatilePtr<u16> = VolatilePtr(0x400_0034 as *mut u16);
|
|
||||||
|
|
||||||
/// BG3 Rotation/Scaling Parameter D (dmy)
|
|
||||||
pub const BG3PD: VolatilePtr<u16> = VolatilePtr(0x400_0036 as *mut u16);
|
|
||||||
|
|
||||||
/// BG3 Reference Point X-Coordinate
|
|
||||||
pub const BG3X: VolatilePtr<u32> = VolatilePtr(0x400_0038 as *mut u32);
|
|
||||||
|
|
||||||
/// BG3 Reference Point Y-Coordinate
|
|
||||||
pub const BG3Y: VolatilePtr<u32> = VolatilePtr(0x400_003C as *mut u32);
|
|
||||||
|
|
||||||
/// Window 0 Horizontal Dimensions
|
|
||||||
pub const WIN0H: VolatilePtr<u16> = VolatilePtr(0x400_0040 as *mut u16);
|
|
||||||
|
|
||||||
/// Window 1 Horizontal Dimensions
|
|
||||||
pub const WIN1H: VolatilePtr<u16> = VolatilePtr(0x400_0042 as *mut u16);
|
|
||||||
|
|
||||||
/// Window 0 Vertical Dimensions
|
|
||||||
pub const WIN0V: VolatilePtr<u16> = VolatilePtr(0x400_0044 as *mut u16);
|
|
||||||
|
|
||||||
/// Window 1 Vertical Dimensions
|
|
||||||
pub const WIN1V: VolatilePtr<u16> = VolatilePtr(0x400_0046 as *mut u16);
|
|
||||||
|
|
||||||
/// Inside of Window 0 and 1
|
|
||||||
pub const WININ: VolatilePtr<u16> = VolatilePtr(0x400_0048 as *mut u16);
|
|
||||||
|
|
||||||
/// Inside of OBJ Window & Outside of Windows
|
|
||||||
pub const WINOUT: VolatilePtr<u16> = VolatilePtr(0x400_004A as *mut u16);
|
|
||||||
|
|
||||||
/// Mosaic Size
|
|
||||||
pub const MOSAIC: VolatilePtr<u16> = VolatilePtr(0x400_004C as *mut u16);
|
|
||||||
|
|
||||||
/// Color Special Effects Selection
|
|
||||||
pub const BLDCNT: VolatilePtr<u16> = VolatilePtr(0x400_0050 as *mut u16);
|
|
||||||
|
|
||||||
/// Alpha Blending Coefficients
|
|
||||||
pub const BLDALPHA: VolatilePtr<u16> = VolatilePtr(0x400_0052 as *mut u16);
|
|
||||||
|
|
||||||
/// Brightness (Fade-In/Out) Coefficient
|
|
||||||
pub const BLDY: VolatilePtr<u16> = VolatilePtr(0x400_0054 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 1 Sweep register (NR10)
|
|
||||||
pub const UND1CNT_L: VolatilePtr<u16> = VolatilePtr(0x400_0060 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 1 Duty/Length/Envelope (NR11, NR12)
|
|
||||||
pub const UND1CNT_H: VolatilePtr<u16> = VolatilePtr(0x400_0062 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 1 Frequency/Control (NR13, NR14)
|
|
||||||
pub const UND1CNT_X: VolatilePtr<u16> = VolatilePtr(0x400_0064 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 2 Duty/Length/Envelope (NR21, NR22)
|
|
||||||
pub const UND2CNT_L: VolatilePtr<u16> = VolatilePtr(0x400_0068 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 2 Frequency/Control (NR23, NR24)
|
|
||||||
pub const UND2CNT_H: VolatilePtr<u16> = VolatilePtr(0x400_006C as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Stop/Wave RAM select (NR30)
|
|
||||||
pub const UND3CNT_L: VolatilePtr<u16> = VolatilePtr(0x400_0070 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Length/Volume (NR31, NR32)
|
|
||||||
pub const UND3CNT_H: VolatilePtr<u16> = VolatilePtr(0x400_0072 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Frequency/Control (NR33, NR34)
|
|
||||||
pub const UND3CNT_X: VolatilePtr<u16> = VolatilePtr(0x400_0074 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 4 Length/Envelope (NR41, NR42)
|
|
||||||
pub const UND4CNT_L: VolatilePtr<u16> = VolatilePtr(0x400_0078 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 4 Frequency/Control (NR43, NR44)
|
|
||||||
pub const UND4CNT_H: VolatilePtr<u16> = VolatilePtr(0x400_007C as *mut u16);
|
|
||||||
|
|
||||||
/// Control Stereo/Volume/Enable (NR50, NR51)
|
|
||||||
pub const UNDCNT_L: VolatilePtr<u16> = VolatilePtr(0x400_0080 as *mut u16);
|
|
||||||
|
|
||||||
/// Control Mixing/DMA Control
|
|
||||||
pub const UNDCNT_H: VolatilePtr<u16> = VolatilePtr(0x400_0082 as *mut u16);
|
|
||||||
|
|
||||||
/// Control Sound on/off (NR52)
|
|
||||||
pub const UNDCNT_X: VolatilePtr<u16> = VolatilePtr(0x400_0084 as *mut u16);
|
|
||||||
|
|
||||||
/// Sound PWM Control
|
|
||||||
pub const UNDBIAS: VolatilePtr<u16> = VolatilePtr(0x400_0088 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
|
||||||
pub const WAVE_RAM0_L: VolatilePtr<u16> = VolatilePtr(0x400_0090 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
|
||||||
pub const WAVE_RAM0_H: VolatilePtr<u16> = VolatilePtr(0x400_0092 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
|
||||||
pub const WAVE_RAM1_L: VolatilePtr<u16> = VolatilePtr(0x400_0094 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
|
||||||
pub const WAVE_RAM1_H: VolatilePtr<u16> = VolatilePtr(0x400_0096 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
|
||||||
pub const WAVE_RAM2_L: VolatilePtr<u16> = VolatilePtr(0x400_0098 as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
|
||||||
pub const WAVE_RAM2_H: VolatilePtr<u16> = VolatilePtr(0x400_009A as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
|
||||||
pub const WAVE_RAM3_L: VolatilePtr<u16> = VolatilePtr(0x400_009C as *mut u16);
|
|
||||||
|
|
||||||
/// Channel 3 Wave Pattern RAM (W/R)
|
|
||||||
pub const WAVE_RAM3_H: VolatilePtr<u16> = VolatilePtr(0x400_009E as *mut u16);
|
|
||||||
|
|
||||||
/// Channel A FIFO, Data 0-3
|
|
||||||
pub const FIFO_A: VolatilePtr<u32> = VolatilePtr(0x400_00A0 as *mut u32);
|
|
||||||
|
|
||||||
/// Channel B FIFO, Data 0-3
|
|
||||||
pub const FIFO_B: VolatilePtr<u32> = VolatilePtr(0x400_00A4 as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 0 Source Address
|
|
||||||
pub const DMA0SAD: VolatilePtr<u32> = VolatilePtr(0x400_00B0 as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 0 Destination Address
|
|
||||||
pub const DMA0DAD: VolatilePtr<u32> = VolatilePtr(0x400_00B4 as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 0 Word Count
|
|
||||||
pub const DMA0CNT_L: VolatilePtr<u16> = VolatilePtr(0x400_00B8 as *mut u16);
|
|
||||||
|
|
||||||
/// DMA 0 Control
|
|
||||||
pub const DMA0CNT_H: VolatilePtr<u16> = VolatilePtr(0x400_00BA as *mut u16);
|
|
||||||
|
|
||||||
/// DMA 1 Source Address
|
|
||||||
pub const DMA1SAD: VolatilePtr<u32> = VolatilePtr(0x400_00BC as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 1 Destination Address
|
|
||||||
pub const DMA1DAD: VolatilePtr<u32> = VolatilePtr(0x400_00C0 as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 1 Word Count
|
|
||||||
pub const DMA1CNT_L: VolatilePtr<u16> = VolatilePtr(0x400_00C4 as *mut u16);
|
|
||||||
|
|
||||||
/// DMA 1 Control
|
|
||||||
pub const DMA1CNT_H: VolatilePtr<u16> = VolatilePtr(0x400_00C6 as *mut u16);
|
|
||||||
|
|
||||||
/// DMA 2 Source Address
|
|
||||||
pub const DMA2SAD: VolatilePtr<u32> = VolatilePtr(0x400_00C8 as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 2 Destination Address
|
|
||||||
pub const DMA2DAD: VolatilePtr<u32> = VolatilePtr(0x400_00CC as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 2 Word Count
|
|
||||||
pub const DMA2CNT_L: VolatilePtr<u16> = VolatilePtr(0x400_00D0 as *mut u16);
|
|
||||||
|
|
||||||
/// DMA 2 Control
|
|
||||||
pub const DMA2CNT_H: VolatilePtr<u16> = VolatilePtr(0x400_00D2 as *mut u16);
|
|
||||||
|
|
||||||
/// DMA 3 Source Address
|
|
||||||
pub const DMA3SAD: VolatilePtr<u32> = VolatilePtr(0x400_00D4 as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 3 Destination Address
|
|
||||||
pub const DMA3DAD: VolatilePtr<u32> = VolatilePtr(0x400_00D8 as *mut u32);
|
|
||||||
|
|
||||||
/// DMA 3 Word Count
|
|
||||||
pub const DMA3CNT_L: VolatilePtr<u16> = VolatilePtr(0x400_00DC as *mut u16);
|
|
||||||
|
|
||||||
/// DMA 3 Control
|
|
||||||
pub const DMA3CNT_H: VolatilePtr<u16> = VolatilePtr(0x400_00DE as *mut u16);
|
|
||||||
|
|
||||||
/// Timer 0 Counter/Reload
|
|
||||||
pub const TM0D: VolatilePtr<u16> = VolatilePtr(0x400_0100 as *mut u16);
|
|
||||||
|
|
||||||
/// Timer 0 Control
|
|
||||||
pub const TM0CNT: VolatilePtr<u16> = VolatilePtr(0x400_0102 as *mut u16);
|
|
||||||
|
|
||||||
/// Timer 1 Counter/Reload
|
|
||||||
pub const TM1D: VolatilePtr<u16> = VolatilePtr(0x400_0104 as *mut u16);
|
|
||||||
|
|
||||||
/// Timer 1 Control
|
|
||||||
pub const TM1CNT: VolatilePtr<u16> = VolatilePtr(0x400_0106 as *mut u16);
|
|
||||||
|
|
||||||
/// Timer 2 Counter/Reload
|
|
||||||
pub const TM2D: VolatilePtr<u16> = VolatilePtr(0x400_0108 as *mut u16);
|
|
||||||
|
|
||||||
/// Timer 2 Control
|
|
||||||
pub const TM2CNT: VolatilePtr<u16> = VolatilePtr(0x400_010A as *mut u16);
|
|
||||||
|
|
||||||
/// Timer 3 Counter/Reload
|
|
||||||
pub const TM3D: VolatilePtr<u16> = VolatilePtr(0x400_010C as *mut u16);
|
|
||||||
|
|
||||||
/// Timer 3 Control
|
|
||||||
pub const TM3CNT: VolatilePtr<u16> = VolatilePtr(0x400_010E as *mut u16);
|
|
||||||
|
|
||||||
/// SIO Data (Normal-32bit Mode; shared with below)
|
|
||||||
pub const SIODATA32: VolatilePtr<u32> = VolatilePtr(0x400_0120 as *mut u32);
|
|
||||||
|
|
||||||
/// SIO Data 0 (Parent) (Multi-Player Mode)
|
|
||||||
pub const SIOMULTI0: VolatilePtr<u16> = VolatilePtr(0x400_0120 as *mut u16);
|
|
||||||
|
|
||||||
/// SIO Data 1 (1st Child) (Multi-Player Mode)
|
|
||||||
pub const SIOMULTI1: VolatilePtr<u16> = VolatilePtr(0x400_0122 as *mut u16);
|
|
||||||
|
|
||||||
/// SIO Data 2 (2nd Child) (Multi-Player Mode)
|
|
||||||
pub const SIOMULTI2: VolatilePtr<u16> = VolatilePtr(0x400_0124 as *mut u16);
|
|
||||||
|
|
||||||
/// SIO Data 3 (3rd Child) (Multi-Player Mode)
|
|
||||||
pub const SIOMULTI3: VolatilePtr<u16> = VolatilePtr(0x400_0126 as *mut u16);
|
|
||||||
|
|
||||||
/// SIO Control Register
|
|
||||||
pub const SIOCNT: VolatilePtr<u16> = VolatilePtr(0x400_0128 as *mut u16);
|
|
||||||
|
|
||||||
/// D SIO Data (Local of MultiPlayer; shared below)
|
|
||||||
pub const SIOMLT_SEN: VolatilePtr<u16> = VolatilePtr(0x400_012A as *mut u16);
|
|
||||||
|
|
||||||
/// SIO Data (Normal-8bit and UART Mode)
|
|
||||||
pub const SIODATA8: VolatilePtr<u16> = VolatilePtr(0x400_012A as *mut u16);
|
|
||||||
|
|
||||||
/// Key Status
|
|
||||||
pub const KEYINPUT: VolatilePtr<u16> = VolatilePtr(0x400_0130 as *mut u16);
|
|
||||||
|
|
||||||
/// A "tribool" value helps us interpret the arrow pad.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(i32)]
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
pub enum TriBool {
|
|
||||||
Minus = -1,
|
|
||||||
Neutral = 0,
|
|
||||||
Plus = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
newtype!(KeyInputSetting, u16, "A newtype over the key input state of the GBA");
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
|
||||||
impl KeyInputSetting {
|
|
||||||
register_bit!(A_BIT, u16, 1, a_pressed);
|
|
||||||
register_bit!(B_BIT, u16, 1 << 1, b_pressed);
|
|
||||||
register_bit!(SELECT_BIT, u16, 1 << 2, select_pressed);
|
|
||||||
register_bit!(START_BIT, u16, 1 << 3, start_pressed);
|
|
||||||
register_bit!(RIGHT_BIT, u16, 1 << 4, right_pressed);
|
|
||||||
register_bit!(LEFT_BIT, u16, 1 << 5, left_pressed);
|
|
||||||
register_bit!(UP_BIT, u16, 1 << 6, up_pressed);
|
|
||||||
register_bit!(DOWN_BIT, u16, 1 << 7, down_pressed);
|
|
||||||
register_bit!(R_BIT, u16, 1 << 8, r_pressed);
|
|
||||||
register_bit!(L_BIT, u16, 1 << 9, l_pressed);
|
|
||||||
|
|
||||||
/// Takes the difference between these keys and another set of keys.
|
|
||||||
pub fn difference(self, other: KeyInputSetting) -> KeyInputSetting {
|
|
||||||
KeyInputSetting(self.0 ^ other.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gives the arrow pad value as a tribool, with Plus being increased column
|
|
||||||
/// value (right).
|
|
||||||
pub fn column_direction(self) -> TriBool {
|
|
||||||
if self.right_pressed() {
|
|
||||||
TriBool::Plus
|
|
||||||
} else if self.left_pressed() {
|
|
||||||
TriBool::Minus
|
|
||||||
} else {
|
|
||||||
TriBool::Neutral
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gives the arrow pad value as a tribool, with Plus being increased row
|
|
||||||
/// value (down).
|
|
||||||
pub fn row_direction(self) -> TriBool {
|
|
||||||
if self.down_pressed() {
|
|
||||||
TriBool::Plus
|
|
||||||
} else if self.up_pressed() {
|
|
||||||
TriBool::Minus
|
|
||||||
} else {
|
|
||||||
TriBool::Neutral
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the current state of the keys
|
|
||||||
pub fn key_input() -> KeyInputSetting {
|
|
||||||
// Note(Lokathor): The 10 used bits are "low when pressed" style, but the 6
|
|
||||||
// unused bits are always low, so we XOR with this mask to get a result where
|
|
||||||
// the only active bits are currently pressed keys.
|
|
||||||
unsafe { KeyInputSetting(KEYINPUT.read() ^ 0b0000_0011_1111_1111) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Key Interrupt Control
|
|
||||||
pub const KEYCNT: VolatilePtr<u16> = VolatilePtr(0x400_0132 as *mut u16);
|
|
||||||
|
|
||||||
/// SIO Mode Select/General Purpose Data
|
|
||||||
pub const RCNT: VolatilePtr<u16> = VolatilePtr(0x400_0134 as *mut u16);
|
|
||||||
|
|
||||||
/// SIO JOY Bus Control
|
|
||||||
pub const JOYCNT: VolatilePtr<u16> = VolatilePtr(0x400_0140 as *mut u16);
|
|
||||||
|
|
||||||
/// SIO JOY Bus Receive Data
|
|
||||||
pub const JOY_RECV: VolatilePtr<u32> = VolatilePtr(0x400_0150 as *mut u32);
|
|
||||||
|
|
||||||
/// SIO JOY Bus Transmit Data
|
|
||||||
pub const JOY_TRANS: VolatilePtr<u32> = VolatilePtr(0x400_0154 as *mut u32);
|
|
||||||
|
|
||||||
/// SIO JOY Bus Receive Status
|
|
||||||
pub const JOYSTAT: VolatilePtr<u16> = VolatilePtr(0x400_0158 as *mut u16);
|
|
||||||
|
|
||||||
/// Interrupt Enable Register
|
|
||||||
pub const IE: VolatilePtr<u16> = VolatilePtr(0x400_0200 as *mut u16);
|
|
||||||
|
|
||||||
/// Interrupt Request Flags / IRQ Acknowledge
|
|
||||||
pub const IF: VolatilePtr<u16> = VolatilePtr(0x400_0202 as *mut u16);
|
|
||||||
|
|
||||||
/// Game Pak Waitstate Control
|
|
||||||
pub const WAITCNT: VolatilePtr<u16> = VolatilePtr(0x400_0204 as *mut u16);
|
|
||||||
|
|
||||||
/// Interrupt Master Enable Register
|
|
||||||
pub const IME: VolatilePtr<u16> = VolatilePtr(0x400_0208 as *mut u16);
|
|
||||||
|
|
249
src/lib.rs
249
src/lib.rs
|
@ -1,12 +1,14 @@
|
||||||
#![cfg_attr(not(test), no_std)]
|
#![cfg_attr(not(test), no_std)]
|
||||||
#![cfg_attr(not(test), feature(asm))]
|
#![feature(asm)]
|
||||||
|
#![feature(const_int_wrapping)]
|
||||||
|
#![feature(min_const_unsafe_fn)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![allow(clippy::cast_lossless)]
|
#![allow(clippy::cast_lossless)]
|
||||||
#![deny(clippy::float_arithmetic)]
|
#![deny(clippy::float_arithmetic)]
|
||||||
|
|
||||||
//! This crate helps you write GBA ROMs.
|
//! This crate helps you write GBA ROMs.
|
||||||
//!
|
//!
|
||||||
//! # SAFETY POLICY
|
//! ## SAFETY POLICY
|
||||||
//!
|
//!
|
||||||
//! Some parts of this crate are safe wrappers around unsafe operations. This is
|
//! Some parts of this crate are safe wrappers around unsafe operations. This is
|
||||||
//! good, and what you'd expect from a Rust crate.
|
//! good, and what you'd expect from a Rust crate.
|
||||||
|
@ -16,78 +18,211 @@
|
||||||
//!
|
//!
|
||||||
//! **Do not** use this crate in programs that aren't running on the GBA. If you
|
//! **Do not** use this crate in programs that aren't running on the GBA. If you
|
||||||
//! do, it's a giant bag of Undefined Behavior.
|
//! do, it's a giant bag of Undefined Behavior.
|
||||||
//!
|
|
||||||
//! # TESTING POLICY
|
/// Assists in defining a newtype wrapper over some base type.
|
||||||
//!
|
///
|
||||||
//! It is the intent of the crate authors that as much of the crate as possible
|
/// Note that rustdoc and derives are all the "meta" stuff, so you can write all
|
||||||
//! be written so that you can use `cargo test` for at least some parts of your
|
/// of your docs and derives in front of your newtype in the same way you would
|
||||||
//! code without everything exploding instantly. To that end, where possible we
|
/// for a normal struct. Then the inner type to be wrapped it name.
|
||||||
//! attempt to use `cfg` flags to make things safe for `cargo test`. Hopefully
|
///
|
||||||
//! we got it all.
|
/// The macro _assumes_ that you'll be using it to wrap zero safe numeric types,
|
||||||
|
/// so it automatically provides a `const fn` method for `new` that just wraps
|
||||||
|
/// `0`. If this is not desired you can add `, no frills` to the invocation.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```
|
||||||
|
/// newtype! {
|
||||||
|
/// /// Records a particular key press combination.
|
||||||
|
/// #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
|
/// KeyInput, u16
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! newtype {
|
||||||
|
($(#[$attr:meta])* $new_name:ident, $old_name:ident) => {
|
||||||
|
$(#[$attr])*
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $new_name($old_name);
|
||||||
|
impl $new_name {
|
||||||
|
/// A `const` "zero value" constructor
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
$new_name(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($(#[$attr:meta])* $new_name:ident, $old_name:ident, no frills) => {
|
||||||
|
$(#[$attr])*
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct $new_name($old_name);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod builtins;
|
||||||
|
|
||||||
|
pub mod fixed;
|
||||||
|
|
||||||
|
pub mod bios;
|
||||||
|
|
||||||
pub mod core_extras;
|
pub mod core_extras;
|
||||||
pub(crate) use crate::core_extras::*;
|
pub(crate) use crate::core_extras::*;
|
||||||
|
|
||||||
pub mod io_registers;
|
pub mod io;
|
||||||
|
|
||||||
pub mod video_ram;
|
pub mod video_ram;
|
||||||
pub(crate) use crate::video_ram::*;
|
|
||||||
|
|
||||||
/// Combines the Red, Blue, and Green provided into a single color value.
|
/// Performs unsigned divide and remainder, gives None if dividing by 0.
|
||||||
pub const fn rgb16(red: u16, green: u16, blue: u16) -> u16 {
|
pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> {
|
||||||
blue << 10 | green << 5 | red
|
// TODO: const this? Requires const if
|
||||||
|
if denom == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(unsafe { divrem_u32_unchecked(numer, denom) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BIOS Call: Div (GBA SWI 0x06).
|
/// Performs divide and remainder, no check for 0 division.
|
||||||
///
|
///
|
||||||
/// Gives just the DIV output of `numerator / denominator`.
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// If you call this with a denominator of 0 the result is implementation
|
||||||
///
|
/// defined (not literal UB) including but not limited to: an infinite loop,
|
||||||
/// If `denominator` is 0.
|
/// panic on overflow, or incorrect output.
|
||||||
#[inline]
|
pub unsafe fn divrem_u32_unchecked(numer: u32, denom: u32) -> (u32, u32) {
|
||||||
pub fn div(numerator: i32, denominator: i32) -> i32 {
|
// TODO: const this? Requires const if
|
||||||
div_modulus(numerator, denominator).0
|
if (numer >> 5) < denom {
|
||||||
|
divrem_u32_simple(numer, denom)
|
||||||
|
} else {
|
||||||
|
divrem_u32_non_restoring(numer, denom)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BIOS Call: Div (GBA SWI 0x06).
|
/// The simplest form of division. If N is too much larger than D this will be
|
||||||
///
|
/// extremely slow. If N is close enough to D then it will likely be faster than
|
||||||
/// Gives just the MOD output of `numerator / denominator`.
|
/// the non_restoring form.
|
||||||
///
|
fn divrem_u32_simple(mut numer: u32, denom: u32) -> (u32, u32) {
|
||||||
/// # Panics
|
// TODO: const this? Requires const if
|
||||||
///
|
let mut quot = 0;
|
||||||
/// If `denominator` is 0.
|
while numer >= denom {
|
||||||
#[inline]
|
numer -= denom;
|
||||||
pub fn modulus(numerator: i32, denominator: i32) -> i32 {
|
quot += 1;
|
||||||
div_modulus(numerator, denominator).1
|
}
|
||||||
|
(quot, numer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BIOS Call: Div (GBA SWI 0x06).
|
/// Takes a fixed quantity of time based on the bit width of the number (in this
|
||||||
///
|
/// case 32).
|
||||||
/// Gives both the DIV and MOD output of `numerator / denominator`.
|
fn divrem_u32_non_restoring(numer: u32, denom: u32) -> (u32, u32) {
|
||||||
///
|
// TODO: const this? Requires const if
|
||||||
/// # Panics
|
let mut r: i64 = numer as i64;
|
||||||
///
|
let d: i64 = (denom as i64) << 32;
|
||||||
/// If `denominator` is 0.
|
let mut q: u32 = 0;
|
||||||
#[inline]
|
let mut i = 1 << 31;
|
||||||
pub fn div_modulus(numerator: i32, denominator: i32) -> (i32, i32) {
|
while i > 0 {
|
||||||
assert!(denominator != 0);
|
if r >= 0 {
|
||||||
#[cfg(not(test))]
|
q |= i;
|
||||||
{
|
r = 2 * r - d;
|
||||||
let div_out: i32;
|
} else {
|
||||||
let mod_out: i32;
|
r = 2 * r + d;
|
||||||
unsafe {
|
|
||||||
asm!(/* assembly template */ "swi 0x06"
|
|
||||||
:/* output operands */ "={r0}"(div_out), "={r1}"(mod_out)
|
|
||||||
:/* input operands */ "{r0}"(numerator), "{r1}"(denominator)
|
|
||||||
:/* clobbers */ "r3"
|
|
||||||
:/* options */
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
(div_out, mod_out)
|
i >>= 1;
|
||||||
}
|
}
|
||||||
|
q -= !q;
|
||||||
|
if r < 0 {
|
||||||
|
q -= 1;
|
||||||
|
r += d;
|
||||||
|
}
|
||||||
|
r >>= 32;
|
||||||
|
// TODO: remove this once we've done more checks here.
|
||||||
|
debug_assert!(r >= 0);
|
||||||
|
debug_assert!(r <= core::u32::MAX as i64);
|
||||||
|
(q, r as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs signed divide and remainder, gives None if dividing by 0 or
|
||||||
|
/// computing `MIN/-1`
|
||||||
|
pub fn divrem_i32(numer: i32, denom: i32) -> Option<(i32, i32)> {
|
||||||
|
if denom == 0 || (numer == core::i32::MIN && denom == -1) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(unsafe { divrem_i32_unchecked(numer, denom) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs signed divide and remainder, no check for 0 division or `MIN/-1`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// * If you call this with a denominator of 0 the result is implementation
|
||||||
|
/// defined (not literal UB) including but not limited to: an infinite loop,
|
||||||
|
/// panic on overflow, or incorrect output.
|
||||||
|
/// * If you call this with `MIN/-1` you'll get a panic in debug or just `MIN`
|
||||||
|
/// in release (which is incorrect), because of how twos-compliment works.
|
||||||
|
pub unsafe fn divrem_i32_unchecked(numer: i32, denom: i32) -> (i32, i32) {
|
||||||
|
// TODO: const this? Requires const if
|
||||||
|
let unsigned_numer = numer.abs() as u32;
|
||||||
|
let unsigned_denom = denom.abs() as u32;
|
||||||
|
let opposite_sign = (numer ^ denom) < 0;
|
||||||
|
let (udiv, urem) = if (numer >> 5) < denom {
|
||||||
|
divrem_u32_simple(unsigned_numer, unsigned_denom)
|
||||||
|
} else {
|
||||||
|
divrem_u32_non_restoring(unsigned_numer, unsigned_denom)
|
||||||
|
};
|
||||||
|
match (opposite_sign, numer < 0) {
|
||||||
|
(true, true) => (-(udiv as i32), -(urem as i32)),
|
||||||
|
(true, false) => (-(udiv as i32), urem as i32),
|
||||||
|
(false, true) => (udiv as i32, -(urem as i32)),
|
||||||
|
(false, false) => (udiv as i32, urem as i32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
{
|
mod tests {
|
||||||
(numerator / denominator, numerator % denominator)
|
use super::*;
|
||||||
|
use quickcheck::quickcheck;
|
||||||
|
|
||||||
|
// We have an explicit property on the non_restoring division
|
||||||
|
quickcheck! {
|
||||||
|
fn divrem_u32_non_restoring_prop(num: u32, denom: u32) -> bool {
|
||||||
|
if denom > 0 {
|
||||||
|
divrem_u32_non_restoring(num, denom) == (num / denom, num % denom)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have an explicit property on the simple division
|
||||||
|
quickcheck! {
|
||||||
|
fn divrem_u32_simple_prop(num: u32, denom: u32) -> bool {
|
||||||
|
if denom > 0 {
|
||||||
|
divrem_u32_simple(num, denom) == (num / denom, num % denom)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the u32 wrapper
|
||||||
|
quickcheck! {
|
||||||
|
fn divrem_u32_prop(num: u32, denom: u32) -> bool {
|
||||||
|
if denom > 0 {
|
||||||
|
divrem_u32(num, denom).unwrap() == (num / denom, num % denom)
|
||||||
|
} else {
|
||||||
|
divrem_u32(num, denom).is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test the i32 wrapper
|
||||||
|
quickcheck! {
|
||||||
|
fn divrem_i32_prop(num: i32, denom: i32) -> bool {
|
||||||
|
if denom == 0 || num == core::i32::MIN && denom == -1 {
|
||||||
|
divrem_i32(num, denom).is_none()
|
||||||
|
} else {
|
||||||
|
divrem_i32(num, denom).unwrap() == (num / denom, num % denom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
//! Module for all macros.
|
|
||||||
//!
|
|
||||||
//! Macros are the only thing in Rust where declaration order matters, so we
|
|
||||||
//! place all of them here regardless of what they do so that the macros module
|
|
||||||
//! can appear at the "top" of the library and all other modules can see them
|
|
||||||
//! properly.
|
|
||||||
|
|
||||||
// no macros yet!
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
pub use super::*;
|
pub use super::*;
|
||||||
|
|
||||||
|
// TODO: kill all this too
|
||||||
|
|
||||||
/// The physical width in pixels of the GBA screen.
|
/// The physical width in pixels of the GBA screen.
|
||||||
pub const SCREEN_WIDTH: isize = 240;
|
pub const SCREEN_WIDTH: isize = 240;
|
||||||
|
|
||||||
|
@ -28,6 +30,8 @@ pub const SCREEN_HEIGHT: isize = 160;
|
||||||
/// value as just being a `usize`.
|
/// value as just being a `usize`.
|
||||||
pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000;
|
pub const VRAM_BASE_ADDRESS: usize = 0x0600_0000;
|
||||||
|
|
||||||
|
const MODE3_VRAM: VolAddress<u16> = unsafe { VolAddress::new_unchecked(VRAM_BASE_ADDRESS) };
|
||||||
|
|
||||||
/// Draws a pixel to the screen while in Display Mode 3, with bounds checks.
|
/// Draws a pixel to the screen while in Display Mode 3, with bounds checks.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
@ -51,7 +55,7 @@ pub fn mode3_draw_pixel(col: isize, row: isize, color: u16) {
|
||||||
/// * `col` must be in `0..SCREEN_WIDTH`
|
/// * `col` must be in `0..SCREEN_WIDTH`
|
||||||
/// * `row` must be in `0..SCREEN_HEIGHT`
|
/// * `row` must be in `0..SCREEN_HEIGHT`
|
||||||
pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) {
|
pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) {
|
||||||
VolatilePtr(VRAM_BASE_ADDRESS as *mut u16).offset(col + row * SCREEN_WIDTH).write(color);
|
MODE3_VRAM.offset(col + row * SCREEN_WIDTH).write(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the given pixel of video memory according to Mode 3 placement.
|
/// Reads the given pixel of video memory according to Mode 3 placement.
|
||||||
|
@ -61,7 +65,7 @@ pub unsafe fn mode3_draw_pixel_unchecked(col: isize, row: isize, color: u16) {
|
||||||
/// If the location is out of bounds you get `None`.
|
/// If the location is out of bounds you get `None`.
|
||||||
pub fn mode3_read_pixel(col: isize, row: isize) -> Option<u16> {
|
pub fn mode3_read_pixel(col: isize, row: isize) -> Option<u16> {
|
||||||
if col >= 0 && col < SCREEN_WIDTH && row >= 0 && row < SCREEN_HEIGHT {
|
if col >= 0 && col < SCREEN_WIDTH && row >= 0 && row < SCREEN_HEIGHT {
|
||||||
unsafe { Some(VolatilePtr(VRAM_BASE_ADDRESS as *mut u16).offset(col + row * SCREEN_WIDTH).read()) }
|
unsafe { Some(MODE3_VRAM.offset(col + row * SCREEN_WIDTH).read()) }
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -72,9 +76,8 @@ pub unsafe fn mode3_clear_screen(color: u16) {
|
||||||
// TODO: use DMA?
|
// TODO: use DMA?
|
||||||
let color = color as u32;
|
let color = color as u32;
|
||||||
let bulk_color = color << 16 | color;
|
let bulk_color = color << 16 | color;
|
||||||
let mut ptr = VolatilePtr(VRAM_BASE_ADDRESS as *mut u32);
|
let block: VolAddressBlock<u32> = VolAddressBlock::new_unchecked(MODE3_VRAM.cast::<u32>(), (SCREEN_HEIGHT * SCREEN_WIDTH / 2) as usize);
|
||||||
for _ in 0..(SCREEN_HEIGHT * SCREEN_WIDTH / 2) {
|
for b in block.iter() {
|
||||||
ptr.write(bulk_color);
|
b.write(bulk_color);
|
||||||
ptr = ptr.offset(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
todo_check.bat
Normal file
12
todo_check.bat
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
@echo off
|
||||||
|
|
||||||
|
echo -------
|
||||||
|
echo -------
|
||||||
|
|
||||||
|
set Wildcard=*.rs
|
||||||
|
|
||||||
|
echo TODOS FOUND:
|
||||||
|
findstr -s -n -i -l "TODO" %Wildcard%
|
||||||
|
|
||||||
|
echo -------
|
||||||
|
echo -------
|
Loading…
Reference in a new issue