mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-24 03:11:29 +11:00
trying a commit but i think nightly is broked
This commit is contained in:
parent
f372923bad
commit
c3f62b1ab5
|
@ -27,8 +27,12 @@ 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 tests, both modes
|
||||||
- cargo test --lib && cargo test --lib --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
|
||||||
|
# cargo make defaults to both debug and release builds of all examples
|
||||||
- cargo make
|
- cargo make
|
||||||
# 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
|
||||||
|
|
|
@ -15,6 +15,9 @@ publish = false
|
||||||
typenum = "1.10"
|
typenum = "1.10"
|
||||||
gba-proc-macro = "0.2.1"
|
gba-proc-macro = "0.2.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
quickcheck="0.7"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
|
@ -58,6 +58,9 @@ fn main() -> std::io::Result<()> {
|
||||||
[tasks.build]
|
[tasks.build]
|
||||||
dependencies = ["build-examples-debug", "build-examples-release", "pack-roms"]
|
dependencies = ["build-examples-debug", "build-examples-release", "pack-roms"]
|
||||||
|
|
||||||
|
[tasks.justrelease]
|
||||||
|
dependencies = ["build-examples-release", "pack-roms"]
|
||||||
|
|
||||||
[tasks.test]
|
[tasks.test]
|
||||||
command = "cargo"
|
command = "cargo"
|
||||||
args = ["test", "--lib"]
|
args = ["test", "--lib"]
|
||||||
|
|
|
@ -237,3 +237,5 @@ 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!
|
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
|
Instead, we'll simply mention them when whenever they're relevent to the task at
|
||||||
hand (such as controlling sound or waiting for vblank).
|
hand (such as controlling sound or waiting for vblank).
|
||||||
|
|
||||||
|
//TODO: list/name all BIOS functions as well as what they relate to elsewhere.
|
||||||
|
|
|
@ -5,14 +5,40 @@
|
||||||
//! You shouldn't need to call anything in here yourself, it just has to be in
|
//! You shouldn't need to call anything in here yourself, it just has to be in
|
||||||
//! the translation unit and LLVM will find it.
|
//! the translation unit and LLVM will find it.
|
||||||
|
|
||||||
|
//TODO: make 64 bit too
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize {
|
#[cfg(any(target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64"))]
|
||||||
|
pub extern "C" fn __clzsi2(mut x: usize) -> usize {
|
||||||
let mut y: usize;
|
let mut y: usize;
|
||||||
let mut n: usize = 32;
|
let mut n: usize = {
|
||||||
y = x >> 16;
|
#[cfg(target_pointer_width = "64")]
|
||||||
if y != 0 {
|
{
|
||||||
n = n - 16;
|
64
|
||||||
x = y;
|
}
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
{
|
||||||
|
32
|
||||||
|
}
|
||||||
|
#[cfg(target_pointer_width = "16")]
|
||||||
|
{
|
||||||
|
16
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
{
|
||||||
|
y = x >> 32;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 32;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
{
|
||||||
|
y = x >> 16;
|
||||||
|
if y != 0 {
|
||||||
|
n = n - 16;
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
y = x >> 8;
|
y = x >> 8;
|
||||||
if y != 0 {
|
if y != 0 {
|
||||||
|
@ -36,3 +62,16 @@ pub unsafe extern "C" fn __clzsi2(mut x: usize) -> usize {
|
||||||
n - x
|
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,41 +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::fixed and gba::core::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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: kill all this with fire
|
/// 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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 "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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gives an iterator over the block's slots.
|
||||||
|
pub const fn iter(self) -> VolAddressIter<T> {
|
||||||
|
VolAddressIter {
|
||||||
|
vol_address: self.vol_address,
|
||||||
|
slots: self.slots,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ 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.
|
/// A newtype over the various display control options that you have on a GBA.
|
||||||
|
@ -98,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.
|
||||||
|
@ -130,278 +125,8 @@ pub fn wait_until_vdraw() {
|
||||||
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
|
/// Key Status
|
||||||
pub const KEYINPUT: VolatilePtr<u16> = VolatilePtr(0x400_0130 as *mut u16);
|
const KEYINPUT: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_0130) };
|
||||||
|
|
||||||
/// A "tribool" value helps us interpret the arrow pad.
|
/// A "tribool" value helps us interpret the arrow pad.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -467,35 +192,5 @@ pub fn key_input() -> KeyInput {
|
||||||
// Note(Lokathor): The 10 used bits are "low when pressed" style, but the 6
|
// 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
|
// unused bits are always low, so we XOR with this mask to get a result where
|
||||||
// the only active bits are currently pressed keys.
|
// the only active bits are currently pressed keys.
|
||||||
unsafe { KeyInput(KEYINPUT.read() ^ 0b0000_0011_1111_1111) }
|
KeyInput(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);
|
|
||||||
|
|
160
src/lib.rs
160
src/lib.rs
|
@ -1,5 +1,7 @@
|
||||||
#![no_std]
|
#![cfg_attr(not(test), no_std)]
|
||||||
#![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)]
|
||||||
|
@ -68,3 +70,159 @@ pub mod io_registers;
|
||||||
|
|
||||||
pub mod video_ram;
|
pub mod video_ram;
|
||||||
pub(crate) use crate::video_ram::*;
|
pub(crate) use crate::video_ram::*;
|
||||||
|
|
||||||
|
/// Performs unsigned divide and remainder, gives None if dividing by 0.
|
||||||
|
pub fn divrem_u32(numer: u32, denom: u32) -> Option<(u32, u32)> {
|
||||||
|
if denom == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(unsafe { divrem_u32_unchecked(numer, denom) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs divide and remainder, no check for 0 division.
|
||||||
|
///
|
||||||
|
/// # 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.
|
||||||
|
pub unsafe fn divrem_u32_unchecked(numer: u32, denom: u32) -> (u32, u32) {
|
||||||
|
if (numer >> 5) < denom {
|
||||||
|
divrem_u32_simple(numer, denom)
|
||||||
|
} else {
|
||||||
|
divrem_u32_non_restoring(numer, denom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// the non_restoring form.
|
||||||
|
fn divrem_u32_simple(mut numer: u32, denom: u32) -> (u32, u32) {
|
||||||
|
let mut quot = 0;
|
||||||
|
while numer >= denom {
|
||||||
|
numer -= denom;
|
||||||
|
quot += 1;
|
||||||
|
}
|
||||||
|
(quot, numer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a fixed quantity of time based on the bit width of the number (in this
|
||||||
|
/// case 32).
|
||||||
|
fn divrem_u32_non_restoring(numer: u32, denom: u32) -> (u32, u32) {
|
||||||
|
let mut r: i64 = numer as i64;
|
||||||
|
let d: i64 = (denom as i64) << 32;
|
||||||
|
let mut q: u32 = 0;
|
||||||
|
let mut i = 1 << 31;
|
||||||
|
while i > 0 {
|
||||||
|
if r >= 0 {
|
||||||
|
q |= i;
|
||||||
|
r = 2 * r - d;
|
||||||
|
} else {
|
||||||
|
r = 2 * r + d;
|
||||||
|
}
|
||||||
|
i >>= 1;
|
||||||
|
}
|
||||||
|
q = q - !q;
|
||||||
|
if r < 0 {
|
||||||
|
q = q - 1;
|
||||||
|
r = r + d;
|
||||||
|
}
|
||||||
|
r = r >> 32;
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
if opposite_sign {
|
||||||
|
if numer < 0 {
|
||||||
|
(-(udiv as i32), -(urem as i32))
|
||||||
|
} else {
|
||||||
|
(-(udiv as i32), urem as i32)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if numer < 0 {
|
||||||
|
(udiv as i32, -(urem as i32))
|
||||||
|
} else {
|
||||||
|
(udiv as i32, urem as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,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
|
||||||
|
@ -53,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.
|
||||||
|
@ -63,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
|
||||||
}
|
}
|
||||||
|
@ -74,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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue