newtype_enum, and other updates

This commit is contained in:
Lokathor 2018-12-29 22:39:53 -07:00
parent 09b4c8804c
commit dfca52a079
6 changed files with 96 additions and 77 deletions

View file

@ -9,96 +9,102 @@ sometimes. Accordingly, you should know how assembly works on the GBA.
`ARMv4` ISA, the `ARMv4T` variant, and specifically the `ARM7TDMI` `ARMv4` ISA, the `ARMv4T` variant, and specifically the `ARM7TDMI`
microarchitecture. Someone at ARM decided that having both `ARM#` and `ARMv#` microarchitecture. Someone at ARM decided that having both `ARM#` and `ARMv#`
was a good way to [version things](https://en.wikichip.org/wiki/arm/versions), was a good way to [version things](https://en.wikichip.org/wiki/arm/versions),
even when the numbers don't match, and the rest of us have been sad ever even when the numbers don't match. The rest of us have been sad ever since.
since. The link there will take you to the correct book within the big pile of The link there will take you to the correct book specific to the GBA's
ARM books available within the ARM Infocenter. Note that there is also a [PDF microarchitecture. There's a whole big pile of ARM books available within the
ARM Infocenter, so if you just google it or whatever make sure you end up
looking at the correct one. Note that there is also a [PDF
Version](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0210c/DDI0210B.pdf) Version](http://infocenter.arm.com/help/topic/com.arm.doc.ddi0210c/DDI0210B.pdf)
of the documentation available, if you'd like that. of the documentation available, if you'd like that.
* The [GBATek: ARM CPU * The [GBATek: ARM CPU
Overview](https://problemkaputt.de/gbatek.htm#armcpuoverview) also has quite a Overview](https://problemkaputt.de/gbatek.htm#armcpuoverview) also has quite a
bit of info. Most of it is somewhat a duplication of what you'd find in the bit of info. Some of it is a duplication of what you'd find in the ARM
ARM Infocenter reference manual, but it's also somewhat specialized towards Infocenter reference manual. Some of it is specific to the GBA's chip. Some of
the GBA's specifics. It's in the usual, uh, "sparse" style that GBATEK is it is specific to the ARM chips within the DS and DSi. It's a bit of a jumbled
written in, so I wouldn't suggest that read it first. mess, and as with the rest of GBATEK, the explanations are in a "sparse" style
(to put it nicely), so I wouldn't take it as your only source.
* The [Compiler Explorer](https://rust.godbolt.org/z/ndCnk3) can be used to * The [Compiler Explorer](https://rust.godbolt.org/z/ndCnk3) can be used to
quickly look at assembly output of your Rust code. That link there will load quickly look at assembly versions of your Rust code. That link there will load
up an essentially blank `no_std` file with `opt-level=3` set and targeting up an essentially blank `no_std` file with `opt-level=3` set and targeting
`thumbv6m-none-eabi`. That's _not_ the same as the GBA (it's two ISA revisions `thumbv6m-none-eabi`. That's _not_ the same target as the GBA (it's two ISA
later, ARMv6 instead of ARMv4), but it's the closest CPU target that ships revisions later, ARMv6 instead of ARMv4), but it's the closest CPU target that
with rustc, so it's the closest you can get with the compiler explorer is bundled with rustc, so it's the closest you can get with the compiler
website. If you're very dedicated I suppose you could setup a [local explorer website. If you're very dedicated I suppose you could setup a [local
instance](https://github.com/mattgodbolt/compiler-explorer#running-a-local-instance) instance](https://github.com/mattgodbolt/compiler-explorer#running-a-local-instance)
of compiler explorer and then add the extra target definition and so on, but of compiler explorer and then add the extra target definition and so on, but
that's _probably_ overkill. that's _probably_ overkill.
## ARM and THUMB ## ARM and Thumb
The "T" part in `ARMv4T` and `ARM7TDMI` means "Thumb". An ARM chip that supports The "T" part in `ARMv4T` and `ARM7TDMI` means "Thumb". An ARM chip that supports
Thumb mode has two different instruction sets instead of just one. The chip can Thumb has two different instruction sets instead of just one. The chip can run
run in ARM mode with 32-bit instructions, or it can run in THUMB mode with in ARM state with 32-bit instructions, or it can run in Thumb state with 16-bit
16-bit instructions. Apparently these modes are sometimes called `a32` and `t32` instructions. Note that the CPU _state_ (ARM or Thumb) is distinct from the
in a more modern context, but I will stick with ARM and THUMB because that's _mode_ (User, FIQ, IRQ, etc). Apparently these states are sometimes called
what other GBA references use (particularly GBATEK), and it's probably best to `a32` and `t32` in a more modern context, but I will stick with ARM and Thumb
be more in agreement with them than with stuff for Raspberry Pi programming or because that's what the official ARM7TDMI manual and GBATEK both use.
whatever other modern ARM thing.
On the GBA, the memory bus that physically transfers data from the game pak into On the GBA, the memory bus that physically transfers data from the cartridge into
the device is a 16-bit memory bus. This means that if you need to transfer more the device is a 16-bit memory bus. This means that if you need to transfer more
than 16 bits at a time you have to do more than one transfer. Since we'd like than 16 bits at a time you have to do more than one transfer. Since we'd like
our instructions to get to the CPU as fast as possible, we compile the majority our instructions to get to the CPU as fast as possible, we compile the majority
of our program with the THUMB instruction set. The ARM reference says that with of our program with the Thumb instruction set. The ARM reference says that with
THUMB instructions on a 16-bit memory bus system you get about 160% performance Thumb instructions on a 16-bit memory bus system you get about 160% performance
compared to using ARM instructions. That's absolutely something we want to take compared to using ARM instructions. That's absolutely something we want to take
advantage of. Also, your THUMB compiled code is about 65% of the same code advantage of. Also, your Thumb compiled code is about 65% of the same code
compiled with ARM. Since a game ROM can only be 32MB total, and we're trying to compiled with ARM. Since a game ROM can only be 32MB total, and we're trying to
fit in images and sound too, we want to get space savings where we can. fit in images and sound too, we want to get space savings where we can.
You may wonder, why is the THUMB code 65% as large if the instructions You may wonder, why is the Thumb code 65% as large if the instructions
themselves are 50% as large, and why have ARM mode at all if there's such a themselves are 50% as large, and why have ARM state at all if there's such a
benefit to be had with THUMB? Well, THUMB mode doesn't support as many different benefit to be had with Thumb? Well, Thumb state doesn't support as many different
instructions as ARM mode does. Some lines of source code that can compile to a instructions as ARM state does. Some lines of source code that can compile to a
single ARM instruction might need to compile into more than one THUMB single ARM instruction might need to compile into more than one Thumb
instruction. THUMB still has most of the really good instructions available, so instruction. Thumb still has most of the really good instructions available, so
it all averages out to about 65%. it all averages out to about 65%.
That said, some parts of a GBA program _must_ be written in ARM mode. Also, ARM That said, some parts of a GBA program _must_ be written for ARM state. Also,
mode does allow that increased instruction flexibility. So we _need_ to use ARM ARM state does allow that increased instruction flexibility. So we _need_ to use
some of the time, and we might just _want_ to use ARM even when we don't need ARM some of the time, and we might just _want_ to use ARM even when we don't
to. It is possible to switch modes on the fly, there's extremely minimal need to at other times. It is possible to switch states on the fly, there's
overhead, even less than doing some function calls. The only problem is the extremely minimal overhead, even less than doing some function calls. The only
16-bit memory bus of the game pak giving us a needless speed penalty with our problem is the 16-bit memory bus of the cartridge giving us a needless speed
ARM code. The CPU _executes_ the ARM instructions at full speed, but then it has penalty with our ARM code. The CPU _executes_ the ARM instructions at full
to wait while more instructions get sent in. What do we do? Well, code is speed, but then it has to wait while more instructions get sent in. What do we
ultimately just a different kind of data. We can copy parts of our code off the do? Well, code is ultimately just a different kind of data. We can copy parts of
game pak ROM and place it into a part of the RAM that has a 32-bit memory bus. our code off the cartridge ROM and place it into a part of the RAM that has a
Then the CPU can execute the code from there, going at full speed. Of course, 32-bit memory bus. Then the CPU can execute the code from there, going at full
there's only a very small amount of RAM compared to the size of a game pak, so speed. Of course, there's only a very small amount of RAM compared to the size
we'll only do this with a few select functions. Exactly which functions will of a cartridge, so we'll only do this with a few select functions. Exactly which
probably depend on your game. functions will probably depend on your game.
One problem with this process is that Rust doesn't currently offer a way to mark There's two problems that we face as Rust programmers:
individual functions for being ARM or THUMB. The whole program is compiled in a
single mode. That's not an automatic killer, since we can use the `asm!` macro
to write some inline assembly, then within our inline assembly we switch from
THUMB to ARM, do some ARM stuff, and switch back to THUMB mode before the inline
assembly is over. Rust is none the wiser to what happened. Yeah, it's clunky,
that's why [it's on the 2019
wishlist](https://github.com/rust-embedded/wg/issues/256#issuecomment-439677804)
to fix it (then LLVM can manage it automatically for you).
The bigger problem is that when we do that all of our functions still start off 1) Rust offers no way to specify individual functions as being ARM or Thumb. The
in THUMB mode, even if they temporarily use ARM mode. For the few bits of code whole program is compiled for one state or the other. Obviously this is no
that must start _already in_ ARM mode, we're stuck. Those parts have to be good, so it's on the [2019 embedded
written in external assembly files and then included with the linker. We were wishlist](https://github.com/rust-embedded/wg/issues/256#issuecomment-439677804),
already going to write some assembly, and we already use more than one file in and perhaps a fix will come.
our project all the time, those parts aren't a big problem. The big problem is
that using custom linker scripts isn't transitive between crates. 2) Rust offers no way to get a pointer to a function as well as the length of
the compiled function, so we can't copy a function from the ROM to some other
location because we can't even express statements about the function's data.
I also put this [on the
wishlist](https://github.com/rust-embedded/wg/issues/256#issuecomment-450539836),
but honestly I have much less hope that this becomes a part of rust.
What this ultimately means is that some parts of our program have to be written
in external assembly files and then added to the program with the linker. We
were already going to write some assembly, and we already use more than one file
in our project all the time, those parts aren't a big problem. The big problem
is that using custom linker scripts to get assembly code into our final program
isn't transitive between crates.
What I mean is that once we have a file full of custom assembly that we're What I mean is that once we have a file full of custom assembly that we're
linking in by hand, that's not "part of" the crate any more. At least not as linking in by hand, that's not "part of" the crate any more. At least not as
`cargo` see it. So we can't just upload it to `crates.io` and then depend on it `cargo` sees it. So we can't just upload it to `crates.io` and then depend on it
in other projects and have `cargo` download the right version and and include it in other projects and have `cargo` download the right version and and include it
all automatically. We're back to fully manually copying files from the old all automatically. We're back to fully manually copying files from the old
project into the new one, adding more lines to the linker script each time we project into the new one, adding more lines to the linker script each time we

1
src/ewram.rs Normal file
View file

@ -0,0 +1 @@
//! Module for External Work RAM (`EWRAM`).

View file

@ -49,10 +49,9 @@ impl DisplayControlSetting {
} }
} }
/// The six display modes available on the GBA. newtype_enum! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)] /// The six display modes available on the GBA.
#[repr(u16)] DisplayMode = u16,
pub enum DisplayMode {
/// * Affine: No /// * Affine: No
/// * Layers: 0/1/2/3 /// * Layers: 0/1/2/3
/// * Size(px): 256x256 to 512x512 /// * Size(px): 256x256 to 512x512

1
src/iwram.rs Normal file
View file

@ -0,0 +1 @@
//! Module for Internal Work RAM (`IWRAM`).

View file

@ -42,7 +42,7 @@ pub(crate) use gba_proc_macro::phantom_fields;
/// } /// }
/// newtype! { /// newtype! {
/// /// You can't derive most stuff above array size 32, so we add /// /// You can't derive most stuff above array size 32, so we add
/// /// the `, no frills` modifier. /// /// the `, no frills` modifier to this one.
/// BigArray, [u8; 200], no frills /// BigArray, [u8; 200], no frills
/// } /// }
/// ``` /// ```
@ -67,6 +67,26 @@ macro_rules! newtype {
}; };
} }
/// Assists in defining a newtype that's an enum.
///
/// First give `NewType = OldType,`, then define the tags and their explicit
/// values with zero or more entries of `TagName = base_value,`. In both cases
/// you can place doc comments or other attributes directly on to the type
/// declaration or the tag declaration.
///
/// The generated enum will get an appropriate `repr` attribute as well as Debug, Clone, Copy,
///
/// Example:
/// ```
/// newtype_enum! {
/// /// The Foo
/// Foo = u16,
/// /// The Bar
/// Bar = 0,
/// /// The Zap
/// Zap = 1,
/// }
/// ```
#[macro_export] #[macro_export]
macro_rules! newtype_enum { macro_rules! newtype_enum {
( (
@ -86,21 +106,14 @@ macro_rules! newtype_enum {
}; };
} }
newtype_enum! {
/// the Foo
Foo = u16,
/// the Bar
Bar = 0,
/// The Zap
Zap = 1,
}
pub mod base; pub mod base;
pub(crate) use self::base::*; pub(crate) use self::base::*;
pub mod bios; pub mod bios;
pub mod wram; pub mod iwram;
pub mod ewram;
pub mod io; pub mod io;

View file

@ -1 +0,0 @@
//! Module for things related to WRAM.