diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d35b7895..4a67181c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -367,6 +367,34 @@ jobs: run: | ./cargo.sh clippy --all-features -- -Dwarnings + rust-tests: + name: Rust Tests + runs-on: [self-hosted, compute] + defaults: + run: + shell: git-nix-shell {0} --ignore-environment + container: + image: ghcr.io/clash-lang/nixos-bittide-hardware:2025-12-17 + options: --memory=15g + needs: [build] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - run: cache pull cargo + - run: cache pull build + + - name: Run Rust tests (debug mode, little endian) + run: | + cd firmware-support/bittide-hal + cargo test --locked + + - name: Run Rust tests (release mode, little endian) + run: | + cd firmware-support/bittide-hal + cargo test --locked --release + firmware-support-tests: name: Firmware Support Unit Tests runs-on: [self-hosted, compute] @@ -753,6 +781,7 @@ jobs: firmware-support-tests, generate-control-reports, haskell-tests, + rust-tests, lint, mdbook, rust-lints, diff --git a/firmware-binaries/Cargo.lock b/firmware-binaries/Cargo.lock index 5de4f8d49..6477c6ecc 100644 --- a/firmware-binaries/Cargo.lock +++ b/firmware-binaries/Cargo.lock @@ -53,6 +53,7 @@ dependencies = [ name = "bittide-hal-c" version = "0.1.0" dependencies = [ + "bittide-macros", "cc", "memmap-generate", ] @@ -243,6 +244,7 @@ name = "elastic_buffer_wb_test" version = "0.1.0" dependencies = [ "bittide-hal", + "bittide-macros", "bittide-sys", "memmap-generate", "riscv-rt", @@ -468,6 +470,7 @@ name = "registerwb_test" version = "0.1.0" dependencies = [ "bittide-hal", + "bittide-macros", "bittide-sys", "heapless", "memmap-generate", @@ -670,6 +673,7 @@ version = "0.1.0" dependencies = [ "bittide-hal", "bittide-hal-c", + "bittide-macros", "bittide-sys", "cc", "memmap-generate", diff --git a/firmware-binaries/demos/clock-control/src/main.rs b/firmware-binaries/demos/clock-control/src/main.rs index 7789270bd..aed5a5ab1 100644 --- a/firmware-binaries/demos/clock-control/src/main.rs +++ b/firmware-binaries/demos/clock-control/src/main.rs @@ -5,6 +5,7 @@ // // SPDX-License-Identifier: Apache-2.0 +use bittide_hal::manual_additions::unsigned::Unsigned; use core::panic::PanicInfo; use itertools::izip; @@ -68,7 +69,7 @@ fn main() -> ! { ) .map(|(i, counter)| { if domain_diff_counters.enable(i).unwrap_or(false) { - Some(counter) + Some(counter.into_inner()) } else { None } @@ -81,7 +82,8 @@ fn main() -> ! { // Store debug information. Stop capturing samples if we are stable to // reduce plot sizes. - let has_sense_of_global_time = freeze.number_of_sync_pulses_seen() != 0; + let has_sense_of_global_time = + freeze.number_of_sync_pulses_seen() != Unsigned::new(0).unwrap(); if !prev_all_stable && has_sense_of_global_time { sample_store.store(&freeze, stability, callisto.accumulated_speed_requests); } diff --git a/firmware-binaries/demos/soft-ugn-mu/Cargo.toml b/firmware-binaries/demos/soft-ugn-mu/Cargo.toml index 1c77e9f0c..282a5b6d7 100644 --- a/firmware-binaries/demos/soft-ugn-mu/Cargo.toml +++ b/firmware-binaries/demos/soft-ugn-mu/Cargo.toml @@ -10,10 +10,11 @@ license = "Apache-2.0" authors = ["Google LLC"] [dependencies] -riscv-rt = "0.11.0" +bittide-macros = { path = "../../../firmware-support/bittide-macros" } +bittide-hal = { path = "../../../firmware-support/bittide-hal" } bittide-sys = { path = "../../../firmware-support/bittide-sys" } +riscv-rt = "0.11.0" ufmt = "0.2.0" -bittide-hal = { path = "../../../firmware-support/bittide-hal" } [build-dependencies] memmap-generate = { path = "../../../firmware-support/memmap-generate" } diff --git a/firmware-binaries/demos/soft-ugn-mu/src/main.rs b/firmware-binaries/demos/soft-ugn-mu/src/main.rs index 61edff2e4..6a9ac7f51 100644 --- a/firmware-binaries/demos/soft-ugn-mu/src/main.rs +++ b/firmware-binaries/demos/soft-ugn-mu/src/main.rs @@ -5,11 +5,11 @@ // // SPDX-License-Identifier: Apache-2.0 -use bittide_hal::hals::soft_ugn_demo_mu::DeviceInstances; -use bittide_hal::manual_additions::calendar::RingbufferCalendar; -use bittide_hal::shared_devices::Uart; -use bittide_sys::link_startup::LinkStartup; -use bittide_sys::stability_detector::Stability; +use bittide_hal::{ + hals::soft_ugn_demo_mu::DeviceInstances, manual_additions::calendar::RingbufferCalendar, + shared_devices::Uart, +}; +use bittide_sys::{link_startup::LinkStartup, stability_detector::Stability}; use core::panic::PanicInfo; use ufmt::uwriteln; diff --git a/firmware-binaries/demos/switch-demo1-mu/src/main.rs b/firmware-binaries/demos/switch-demo1-mu/src/main.rs index 525045ac2..054f68e79 100644 --- a/firmware-binaries/demos/switch-demo1-mu/src/main.rs +++ b/firmware-binaries/demos/switch-demo1-mu/src/main.rs @@ -6,8 +6,7 @@ // SPDX-License-Identifier: Apache-2.0 use bittide_hal::hals::switch_demo_mu::DeviceInstances; -use bittide_sys::link_startup::LinkStartup; -use bittide_sys::stability_detector::Stability; +use bittide_sys::{link_startup::LinkStartup, stability_detector::Stability}; use core::panic::PanicInfo; use ufmt::uwriteln; diff --git a/firmware-binaries/demos/switch-demo2-mu/src/main.rs b/firmware-binaries/demos/switch-demo2-mu/src/main.rs index be40eef6a..aafacc32f 100644 --- a/firmware-binaries/demos/switch-demo2-mu/src/main.rs +++ b/firmware-binaries/demos/switch-demo2-mu/src/main.rs @@ -106,7 +106,7 @@ fn main() -> ! { "Capture UGN {}: local = {}, remote = {}", i, capture_ugn.local_counter(), - capture_ugn.remote_counter() + capture_ugn.remote_counter(), ) .unwrap(); *done = true; diff --git a/firmware-binaries/hitl-tests/clock-board/src/main.rs b/firmware-binaries/hitl-tests/clock-board/src/main.rs index 281ec1300..621ff9956 100644 --- a/firmware-binaries/hitl-tests/clock-board/src/main.rs +++ b/firmware-binaries/hitl-tests/clock-board/src/main.rs @@ -78,7 +78,7 @@ fn check_frequency( domain_diff_counters.set_enable(0, false); let diff = match (start, end) { - (Some(s), Some(e)) => e - s, + (Some(s), Some(e)) => e.into_inner() - s.into_inner(), _ => return Err(FrequencyCheckError::CounterInaccessible), }; diff --git a/firmware-binaries/sim-tests/capture_ugn_test/src/main.rs b/firmware-binaries/sim-tests/capture_ugn_test/src/main.rs index c1242a9b3..fab145e5d 100644 --- a/firmware-binaries/sim-tests/capture_ugn_test/src/main.rs +++ b/firmware-binaries/sim-tests/capture_ugn_test/src/main.rs @@ -26,9 +26,9 @@ fn main() -> ! { } uwriteln!( uart, - "(0x{:16X},0x{:16X})", + "({},{})", capture_ugn.local_counter(), - capture_ugn.remote_counter() + capture_ugn.remote_counter(), ) .unwrap(); loop { diff --git a/firmware-binaries/sim-tests/clock-control-wb/src/main.rs b/firmware-binaries/sim-tests/clock-control-wb/src/main.rs index 71c2dcf2c..13836e9d0 100644 --- a/firmware-binaries/sim-tests/clock-control-wb/src/main.rs +++ b/firmware-binaries/sim-tests/clock-control-wb/src/main.rs @@ -74,7 +74,7 @@ fn main() -> ! { writeln!(uart, "]").unwrap(); // Mark end of transmission - should hopefully be unique enough? - for _ in 0..cc.link_mask_pop_count() { + for _ in 0..cc.link_mask_pop_count().into_inner() { cc.set_change_speed(SpeedChange::NoChange); } diff --git a/firmware-binaries/sim-tests/elastic_buffer_wb_test/Cargo.toml b/firmware-binaries/sim-tests/elastic_buffer_wb_test/Cargo.toml index e0ee35778..71738419b 100644 --- a/firmware-binaries/sim-tests/elastic_buffer_wb_test/Cargo.toml +++ b/firmware-binaries/sim-tests/elastic_buffer_wb_test/Cargo.toml @@ -12,9 +12,10 @@ authors = ["Google LLC"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -riscv-rt = "0.11.0" -bittide-sys = { path = "../../../firmware-support/bittide-sys" } bittide-hal = { path = "../../../firmware-support/bittide-hal" } +bittide-macros = { path = "../../../firmware-support/bittide-macros" } +bittide-sys = { path = "../../../firmware-support/bittide-sys" } +riscv-rt = "0.11.0" ufmt = "0.2.0" [build-dependencies] diff --git a/firmware-binaries/sim-tests/elastic_buffer_wb_test/src/main.rs b/firmware-binaries/sim-tests/elastic_buffer_wb_test/src/main.rs index 072391ed1..208b18511 100644 --- a/firmware-binaries/sim-tests/elastic_buffer_wb_test/src/main.rs +++ b/firmware-binaries/sim-tests/elastic_buffer_wb_test/src/main.rs @@ -4,9 +4,12 @@ #![no_std] #![cfg_attr(not(test), no_main)] -use bittide_hal::elastic_buffer_wb_test::DeviceInstances; -use bittide_hal::manual_additions::timer::Duration; -use bittide_hal::shared_devices::ElasticBuffer; +use bittide_hal::{ + elastic_buffer_wb_test::DeviceInstances, + manual_additions::{signed::Signed, timer::Duration, unsigned::Unsigned}, + shared_devices::ElasticBuffer, +}; +use bittide_macros::{signed, unsigned}; #[cfg(not(test))] use riscv_rt::entry; use ufmt::uwriteln; @@ -34,7 +37,7 @@ fn test_set_occupancy_to_midpoint( } // Action: Calculate how many elements to add/remove to reach 0 - let count_before = elastic_buffer.data_count(); + let count_before = elastic_buffer.data_count().into_inner(); let target = 0i8; let delta = target - count_before; @@ -60,7 +63,7 @@ fn test_set_occupancy_to_midpoint( .unwrap(); // Verify: Count is exactly at the midpoint (0) - if !underflow_initial && !overflow_initial && count_after == target { + if !underflow_initial && !overflow_initial && count_after.into_inner() == target { uwriteln!(uart, " PASS").unwrap(); true } else { @@ -88,7 +91,7 @@ fn test_zero_adjustment( uwriteln!(uart, " Before adjustment(0): count={}", count_before).unwrap(); // Test single zero adjustment - elastic_buffer.set_adjustment(0); + elastic_buffer.set_adjustment(signed!(0, n = 32)); timer.wait(Duration::from_micros(1)); let count_after_first = elastic_buffer.data_count(); @@ -106,9 +109,9 @@ fn test_zero_adjustment( } // Test multiple zero adjustments in succession - elastic_buffer.set_adjustment(0); - elastic_buffer.set_adjustment(0); - elastic_buffer.set_adjustment(0); + elastic_buffer.set_adjustment(signed!(0, n = 32)); + elastic_buffer.set_adjustment(signed!(0, n = 32)); + elastic_buffer.set_adjustment(signed!(0, n = 32)); timer.wait(Duration::from_micros(1)); let count_after_multiple = elastic_buffer.data_count(); @@ -156,12 +159,12 @@ fn test_multiple_drain_commands( } // Action: Use set_adjustment HAL function to remove 5 frames - let count_before = elastic_buffer.data_count() as i32; + let count_before = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " Before set_adjustment(-5): count={}", count_before).unwrap(); - elastic_buffer.set_adjustment(-5); + elastic_buffer.set_adjustment(signed!(-5, n = 32)); - let count_after = elastic_buffer.data_count() as i32; + let count_after = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " After set_adjustment(-5): count={}", count_after).unwrap(); // Verify: Count decreased by exactly 5 @@ -204,7 +207,7 @@ fn test_underflow_flag_sticky( } // Action 1: Drain past empty to trigger underflow - let count_before = elastic_buffer.data_count(); + let count_before = elastic_buffer.data_count().into_inner(); let drains_needed = count_before as i32 - (ElasticBuffer::MIN_OCCUPANCY as i32 - 1); uwriteln!( uart, @@ -214,7 +217,7 @@ fn test_underflow_flag_sticky( ) .unwrap(); - elastic_buffer.set_adjustment(-drains_needed); + elastic_buffer.set_adjustment(Signed::new(-drains_needed).unwrap()); let count_after_drain = elastic_buffer.data_count(); let underflow_after_drain = elastic_buffer.underflow(); @@ -237,7 +240,7 @@ fn test_underflow_flag_sticky( } // Action 2: Issue Fill adjustment to test stickiness - elastic_buffer.set_adjustment(1); + elastic_buffer.set_adjustment(signed!(1, n = 32)); timer.wait(Duration::from_micros(1)); let underflow_after_fill = elastic_buffer.underflow(); @@ -311,7 +314,7 @@ fn test_overflow_flag_sticky( } // Action 1: Fill past capacity to trigger overflow - let count_before = elastic_buffer.data_count() as i32; + let count_before = elastic_buffer.data_count().into_inner() as i32; let fills_needed = ElasticBuffer::MAX_OCCUPANCY as i32 - count_before + 1; uwriteln!( uart, @@ -321,9 +324,9 @@ fn test_overflow_flag_sticky( ) .unwrap(); - elastic_buffer.set_adjustment(fills_needed); + elastic_buffer.set_adjustment(Signed::new(fills_needed).unwrap()); - let count_after_fill = elastic_buffer.data_count() as i32; + let count_after_fill = elastic_buffer.data_count().into_inner() as i32; let overflow_after_fill = elastic_buffer.overflow(); uwriteln!( uart, @@ -344,7 +347,7 @@ fn test_overflow_flag_sticky( } // Action 2: Issue Drain adjustment to test stickiness - elastic_buffer.set_adjustment(-1); + elastic_buffer.set_adjustment(signed!(-1, n = 32)); timer.wait(Duration::from_micros(1)); let overflow_after_drain = elastic_buffer.overflow(); @@ -393,14 +396,14 @@ fn test_multiple_items_command( elastic_buffer.set_occupancy(0); timer.wait(Duration::from_micros(1)); - let count_before = elastic_buffer.data_count(); + let count_before = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " Initial count: {}", count_before).unwrap(); // Test 1: Fill multiple items (n=10) - elastic_buffer.set_adjustment(10); + elastic_buffer.set_adjustment(signed!(10, n = 32)); timer.wait(Duration::from_micros(1)); - let count_after_fill = elastic_buffer.data_count(); + let count_after_fill = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " After filling 10: count={}", count_after_fill).unwrap(); if count_after_fill != count_before + 10 { @@ -415,10 +418,10 @@ fn test_multiple_items_command( } // Test 2: Drain multiple items (n=5) - elastic_buffer.set_adjustment(-5); + elastic_buffer.set_adjustment(signed!(-5, n = 32)); timer.wait(Duration::from_micros(1)); - let count_after_drain = elastic_buffer.data_count(); + let count_after_drain = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " After draining 5: count={}", count_after_drain).unwrap(); if count_after_drain != count_after_fill - 5 { @@ -449,17 +452,17 @@ fn test_back_to_back( elastic_buffer.set_occupancy(0); timer.wait(Duration::from_micros(1)); - let count_initial = elastic_buffer.data_count(); + let count_initial = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " Initial count: {}", count_initial).unwrap(); // Submit two adjustments back-to-back without waiting between them // The second should stall until the first completes - elastic_buffer.set_adjustment_async(3); - elastic_buffer.set_adjustment_async(-2); + elastic_buffer.set_adjustment_async(signed!(3, n = 32)); + elastic_buffer.set_adjustment_async(signed!(-2, n = 32)); elastic_buffer.set_adjustment_wait(()); timer.wait(Duration::from_micros(1)); - let count_after = elastic_buffer.data_count(); + let count_after = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " After both adjustments: count={}", count_after).unwrap(); // Verify: count should be initial + 3 - 2 = initial + 1 @@ -491,15 +494,15 @@ fn test_async_wait_async_wait( elastic_buffer.set_occupancy(0); timer.wait(Duration::from_micros(1)); - let count_initial = elastic_buffer.data_count(); + let count_initial = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " Initial count: {}", count_initial).unwrap(); // First adjustment: submit and wait - elastic_buffer.set_adjustment_async(7); + elastic_buffer.set_adjustment_async(signed!(7, n = 32)); elastic_buffer.set_adjustment_wait(()); timer.wait(Duration::from_micros(1)); - let count_after_first = elastic_buffer.data_count(); + let count_after_first = elastic_buffer.data_count().into_inner(); uwriteln!( uart, " After first filling 7 + wait: count={}", @@ -508,11 +511,11 @@ fn test_async_wait_async_wait( .unwrap(); // Second adjustment: submit and wait - elastic_buffer.set_adjustment_async(-4); + elastic_buffer.set_adjustment_async(signed!(-4, n = 32)); elastic_buffer.set_adjustment_wait(()); timer.wait(Duration::from_micros(1)); - let count_after_second = elastic_buffer.data_count(); + let count_after_second = elastic_buffer.data_count().into_inner(); uwriteln!( uart, " After second draining 4 + wait: count={}", @@ -553,19 +556,19 @@ fn test_auto_center_basic_centering( // Set buffer to +10 elastic_buffer.set_occupancy(10); timer.wait(Duration::from_micros(1)); - let count_before = elastic_buffer.data_count(); + let count_before = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " Initial count: {}", count_before).unwrap(); // Set margin to 2 and enable auto-center - elastic_buffer.set_auto_center_margin(2); + elastic_buffer.set_auto_center_margin(unsigned!(2, n = 16)); elastic_buffer.set_auto_center_enable(true); // Wait for it to become idle elastic_buffer.wait_auto_center_idle(); timer.wait(Duration::from_micros(1)); - let count_after = elastic_buffer.data_count(); - let total_adjustments = elastic_buffer.auto_center_total_adjustments(); + let count_after = elastic_buffer.data_count().into_inner(); + let total_adjustments = elastic_buffer.auto_center_total_adjustments().into_inner(); uwriteln!( uart, " After auto-center: count={}, total_adjustments={}", @@ -620,14 +623,14 @@ fn test_auto_center_margin_parameter( uwriteln!(uart, " Testing margin={}", margin).unwrap(); // Set margin and enable - elastic_buffer.set_auto_center_margin(margin); + elastic_buffer.set_auto_center_margin(Unsigned::new(margin).unwrap()); elastic_buffer.set_auto_center_enable(true); // Wait for idle elastic_buffer.wait_auto_center_idle(); timer.wait(Duration::from_micros(1)); - let count_after = elastic_buffer.data_count(); + let count_after = elastic_buffer.data_count().into_inner(); let margin_signed = margin as i8; uwriteln!( @@ -723,7 +726,7 @@ fn test_auto_center_idle_state( timer.wait(Duration::from_micros(1)); // Enable auto-center - elastic_buffer.set_auto_center_margin(2); + elastic_buffer.set_auto_center_margin(unsigned!(2, n = 16)); elastic_buffer.set_auto_center_enable(true); // Wait for completion @@ -762,7 +765,7 @@ fn test_auto_center_total_adjustments_tracking( timer.wait(Duration::from_micros(1)); // Verify total starts at 0 - let total_initial = elastic_buffer.auto_center_total_adjustments(); + let total_initial = elastic_buffer.auto_center_total_adjustments().into_inner(); if total_initial != 0 { uwriteln!( uart, @@ -776,11 +779,11 @@ fn test_auto_center_total_adjustments_tracking( // Set buffer to +15 and center elastic_buffer.set_occupancy(15); timer.wait(Duration::from_micros(1)); - elastic_buffer.set_auto_center_margin(0); + elastic_buffer.set_auto_center_margin(unsigned!(0, n = 16)); elastic_buffer.set_auto_center_enable(true); elastic_buffer.wait_auto_center_idle(); - let total_after_first = elastic_buffer.auto_center_total_adjustments(); + let total_after_first = elastic_buffer.auto_center_total_adjustments().into_inner(); uwriteln!( uart, " After centering from +15: total={}", @@ -806,7 +809,7 @@ fn test_auto_center_total_adjustments_tracking( elastic_buffer.set_auto_center_enable(true); elastic_buffer.wait_auto_center_idle(); - let total_after_second = elastic_buffer.auto_center_total_adjustments(); + let total_after_second = elastic_buffer.auto_center_total_adjustments().into_inner(); uwriteln!( uart, " After centering from -10: total={}", @@ -842,11 +845,11 @@ fn test_auto_center_reset_functionality( elastic_buffer.reset_auto_center(); elastic_buffer.set_occupancy(12); timer.wait(Duration::from_micros(1)); - elastic_buffer.set_auto_center_margin(0); + elastic_buffer.set_auto_center_margin(unsigned!(0, n = 16)); elastic_buffer.set_auto_center_enable(true); elastic_buffer.wait_auto_center_idle(); - let total_before_reset = elastic_buffer.auto_center_total_adjustments(); + let total_before_reset = elastic_buffer.auto_center_total_adjustments().into_inner(); uwriteln!( uart, " Total adjustments before reset: {}", @@ -864,7 +867,7 @@ fn test_auto_center_reset_functionality( timer.wait(Duration::from_micros(1)); // Verify total is 0 - let total_after_reset = elastic_buffer.auto_center_total_adjustments(); + let total_after_reset = elastic_buffer.auto_center_total_adjustments().into_inner(); if total_after_reset != 0 { uwriteln!( uart, @@ -907,19 +910,19 @@ fn test_auto_center_with_manual_adjustments( timer.wait(Duration::from_micros(1)); // Enable auto-center - elastic_buffer.set_auto_center_margin(2); + elastic_buffer.set_auto_center_margin(unsigned!(2, n = 16)); elastic_buffer.set_auto_center_enable(true); timer.wait(Duration::from_micros(1)); // Submit a manual adjustment while auto-center is active - elastic_buffer.set_adjustment(5); + elastic_buffer.set_adjustment(signed!(5, n = 32)); timer.wait(Duration::from_micros(1)); // Wait for auto-center to finish elastic_buffer.wait_auto_center_idle(); - let count_final = elastic_buffer.data_count(); - let total_adjustments = elastic_buffer.auto_center_total_adjustments(); + let count_final = elastic_buffer.data_count().into_inner(); + let total_adjustments = elastic_buffer.auto_center_total_adjustments().into_inner(); uwriteln!( uart, @@ -962,15 +965,15 @@ fn test_auto_center_already_centered( elastic_buffer.set_occupancy(0); timer.wait(Duration::from_micros(1)); - let count_before = elastic_buffer.data_count(); + let count_before = elastic_buffer.data_count().into_inner(); // Enable auto-center with margin 2 - elastic_buffer.set_auto_center_margin(2); + elastic_buffer.set_auto_center_margin(unsigned!(2, n = 16)); elastic_buffer.set_auto_center_enable(true); elastic_buffer.wait_auto_center_idle(); - let count_after = elastic_buffer.data_count(); - let total_adjustments = elastic_buffer.auto_center_total_adjustments(); + let count_after = elastic_buffer.data_count().into_inner(); + let total_adjustments = elastic_buffer.auto_center_total_adjustments().into_inner(); uwriteln!( uart, @@ -1017,14 +1020,14 @@ fn test_auto_center_wait_idle( timer.wait(Duration::from_micros(1)); // Enable auto-center - elastic_buffer.set_auto_center_margin(2); + elastic_buffer.set_auto_center_margin(unsigned!(2, n = 16)); elastic_buffer.set_auto_center_enable(true); // Immediately call wait_idle elastic_buffer.wait_auto_center_idle(); // Should not return until idle, and buffer should be centered - let count_after = elastic_buffer.data_count(); + let count_after = elastic_buffer.data_count().into_inner(); let is_idle = elastic_buffer.auto_center_is_idle(); uwriteln!( @@ -1069,12 +1072,12 @@ fn test_auto_center_zero_margin( timer.wait(Duration::from_micros(1)); // Set margin to 0 - elastic_buffer.set_auto_center_margin(0); + elastic_buffer.set_auto_center_margin(unsigned!(0, n = 16)); elastic_buffer.set_auto_center_enable(true); elastic_buffer.wait_auto_center_idle(); timer.wait(Duration::from_micros(1)); - let count_after = elastic_buffer.data_count(); + let count_after = elastic_buffer.data_count().into_inner(); uwriteln!( uart, " After centering with margin=0: count={}", @@ -1109,7 +1112,7 @@ fn test_auto_center_persistence_across_multiple_perturbations( elastic_buffer.reset_auto_center(); elastic_buffer.set_occupancy(0); timer.wait(Duration::from_micros(1)); - elastic_buffer.set_auto_center_margin(2); + elastic_buffer.set_auto_center_margin(unsigned!(2, n = 16)); elastic_buffer.set_auto_center_enable(true); elastic_buffer.wait_auto_center_idle(); @@ -1119,13 +1122,13 @@ fn test_auto_center_persistence_across_multiple_perturbations( uwriteln!(uart, " Applying perturbation: {}", perturbation).unwrap(); // Manually adjust buffer off-center - elastic_buffer.set_adjustment(perturbation); + elastic_buffer.set_adjustment(Signed::new(perturbation).unwrap()); timer.wait(Duration::from_micros(1)); // Wait for auto-center to fix it elastic_buffer.wait_auto_center_idle(); - let count = elastic_buffer.data_count(); + let count = elastic_buffer.data_count().into_inner(); uwriteln!(uart, " After re-centering: count={}", count).unwrap(); // Verify it's centered again diff --git a/firmware-binaries/sim-tests/nested_interconnect_test/src/main.rs b/firmware-binaries/sim-tests/nested_interconnect_test/src/main.rs index a4cfbab76..ce5f1dc38 100644 --- a/firmware-binaries/sim-tests/nested_interconnect_test/src/main.rs +++ b/firmware-binaries/sim-tests/nested_interconnect_test/src/main.rs @@ -7,10 +7,9 @@ // // SPDX-License-Identifier: Apache-2.0 +use bittide_hal::{hals::nested_interconnect as hal, manual_additions::unsigned::Unsigned}; use ufmt::{uwrite, uwriteln}; -use bittide_hal::hals::nested_interconnect as hal; - use core::fmt::Write; #[cfg(not(test))] use riscv_rt::entry; @@ -71,23 +70,23 @@ fn main() -> ! { // Read initial values (should be 0) write!(name_buf, "peripheral{}.status.init", i).unwrap(); - expect(&name_buf, 0u32, peripheral.status()); + expect(&name_buf, 0u32, peripheral.status().into_inner()); name_buf.clear(); write!(name_buf, "peripheral{}.control.init", i).unwrap(); - expect(&name_buf, 0u32, peripheral.control()); + expect(&name_buf, 0u32, peripheral.control().into_inner()); name_buf.clear(); // Write and read back status - peripheral.set_status(*status_val); + peripheral.set_status(Unsigned::new(*status_val).unwrap()); write!(name_buf, "peripheral{}.status.readback", i).unwrap(); - expect(&name_buf, *status_val, peripheral.status()); + expect(&name_buf, *status_val, peripheral.status().into_inner()); name_buf.clear(); // Write and read back control - peripheral.set_control(*control_val); + peripheral.set_control(Unsigned::new(*control_val).unwrap()); write!(name_buf, "peripheral{}.control.readback", i).unwrap(); - expect(&name_buf, *control_val, peripheral.control()); + expect(&name_buf, *control_val, peripheral.control().into_inner()); } // Verify all peripherals are still accessible @@ -96,7 +95,7 @@ fn main() -> ! { { let mut name_buf = heapless::String::<50>::new(); write!(name_buf, "peripheral{}.status.final", i).unwrap(); - expect(&name_buf, *status_val, peripheral.status()); + expect(&name_buf, *status_val, peripheral.status().into_inner()); } test_ok() diff --git a/firmware-binaries/sim-tests/registerwb_test/Cargo.toml b/firmware-binaries/sim-tests/registerwb_test/Cargo.toml index 5aeecb9c9..504685fde 100644 --- a/firmware-binaries/sim-tests/registerwb_test/Cargo.toml +++ b/firmware-binaries/sim-tests/registerwb_test/Cargo.toml @@ -12,11 +12,12 @@ authors = ["Google LLC"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -riscv-rt = "0.11.0" bittide-sys = { path = "../../../firmware-support/bittide-sys" } -ufmt = "0.2.0" bittide-hal = { path = "../../../firmware-support/bittide-hal" } +bittide-macros = { path = "../../../firmware-support/bittide-macros" } heapless = "0.8.0" +riscv-rt = "0.11.0" +ufmt = "0.2.0" [build-dependencies] memmap-generate = { path = "../../../firmware-support/memmap-generate" } diff --git a/firmware-binaries/sim-tests/registerwb_test/src/main.rs b/firmware-binaries/sim-tests/registerwb_test/src/main.rs index f196f3206..d5ec16e78 100644 --- a/firmware-binaries/sim-tests/registerwb_test/src/main.rs +++ b/firmware-binaries/sim-tests/registerwb_test/src/main.rs @@ -1,16 +1,20 @@ +// SPDX-FileCopyrightText: 2025 Google LLC +// +// SPDX-License-Identifier: Apache-2.0 #![no_std] #![cfg_attr(not(test), no_main)] #![allow(const_item_mutation)] #![allow(clippy::empty_loop)] #![allow(clippy::approx_constant)] -// SPDX-FileCopyrightText: 2025 Google LLC -// -// SPDX-License-Identifier: Apache-2.0 use ufmt::{uWrite, uwrite, uwriteln}; -use bittide_hal::hals::register_wb as hal; -use bittide_hal::types; +use bittide_hal::{ + hals::register_wb as hal, + manual_additions::{bitvector::BitVector, index::Index, signed::Signed, unsigned::Unsigned}, + types, +}; +use bittide_macros::{bitvector, index, signed, unsigned}; use core::fmt::Write; #[cfg(not(test))] @@ -51,19 +55,18 @@ fn expect(msg: &str, expected: T, actual: T) { } } -fn bv16(v: u16) -> [u8; 2] { - v.to_le_bytes() -} -fn bv64(v: u64) -> [u8; 8] { - v.to_le_bytes() -} - #[cfg_attr(not(test), entry)] fn main() -> ! { let many_types = &mut INSTANCES.many_types; macro_rules! read_write { - ($name:literal, $read_fn:ident, $init_expected:expr, $write_fn:ident, $ret_expected:expr) => { + ( + $name:literal, + $read_fn:ident, + $init_expected:expr, + $write_fn:ident, + $ret_expected:expr$(,)? + ) => { let name = concat!("init.", $name); expect(name, $init_expected, many_types.$read_fn()); many_types.$write_fn($ret_expected); @@ -71,7 +74,13 @@ fn main() -> ! { expect(name, $ret_expected, many_types.$read_fn()); }; - (array => $name:literal, $read_fn:ident, [$($init_expected:expr),*], $write_fn:ident, [$($ret_expected:expr),*]) => { + ( + array => $name:literal, + $read_fn:ident, + [$($init_expected:expr),*$(,)?], + $write_fn:ident, + [$($ret_expected:expr),*$(,)?]$(,)? + ) => { let mut i = 0; $( let mut msg = heapless::String::<150>::new(); @@ -99,25 +108,61 @@ fn main() -> ! { } // Test initial values: - read_write!("s0", s0, -8, set_s0, -16); - read_write!("s1", s1, 8, set_s1, 16); - read_write!("s2", s2, 16, set_s2, 32); - read_write!("s3", s3, 3721049880298531338, set_s3, 7442099760597062676); - read_write!("s4", s4, -12, set_s4, -13); - - read_write!("u0", u0, 8, set_u0, 16); - read_write!("u1", u1, 16, set_u1, 32); - read_write!("u2", u2, 3721049880298531338, set_u2, 7442099760597062676); - read_write!("u3", u3, 0xBADC_0FEE, set_u3, 24); - - read_write!("bv0", bv0, [8], set_bv0, [16]); - read_write!("bv1", bv1, bv16(16), set_bv1, bv16(32)); + read_write!("s0", s0, signed!(-8, n = 8), set_s0, signed!(-16, n = 8)); + read_write!("s1", s1, signed!(8, n = 8), set_s1, signed!(16, n = 8)); + read_write!("s2", s2, signed!(16, n = 16), set_s2, signed!(32, n = 16)); + read_write!( + "s3", + s3, + signed!(3721049880298531338, n = 64), + set_s3, + signed!(7442099760597062676, n = 64), + ); + read_write!("s4", s4, signed!(-12, n = 8), set_s4, signed!(-13, n = 8)); + + read_write!("u0", u0, unsigned!(8, n = 8), set_u0, unsigned!(16, n = 8)); + read_write!( + "u1", + u1, + unsigned!(16, n = 16), + set_u1, + unsigned!(32, n = 16), + ); + read_write!( + "u2", + u2, + unsigned!(3721049880298531338, n = 64), + set_u2, + unsigned!(7442099760597062676, n = 64), + ); + read_write!( + "u3", + u3, + unsigned!(0xBADC_0FEE, n = 32), + set_u3, + unsigned!(24, n = 32), + ); + + read_write!( + "bv0", + bv0, + bitvector!(0x08, n = 8), + set_bv0, + bitvector!(0x10, n = 8), + ); + read_write!( + "bv1", + bv1, + bitvector!(0x10, n = 16), + set_bv1, + bitvector!(0x20, n = 16), + ); read_write!( "bv2", bv2, - bv64(3721049880298531338), + bitvector!(0x33A3D326B2B42A0A, n = 64), set_bv2, - bv64(7442099760597062676) + bitvector!(0x6747A64D65685414, n = 64), ); // floats/doubles don't implement uDebug, so go through bool instead :( @@ -139,14 +184,32 @@ fn main() -> ! { read_write!( array => "v0", v0, - [[0x8], [0x16], [0x24], [0x32], [0x40], [0x4E], [0x5C], [0x6A]], + [ + bitvector!(0x08, n = 8), + bitvector!(0x16, n = 8), + bitvector!(0x24, n = 8), + bitvector!(0x32, n = 8), + bitvector!(0x40, n = 8), + bitvector!(0x4E, n = 8), + bitvector!(0x5C, n = 8), + bitvector!(0x6A, n = 8), + ], set_v0, - [[16], [32], [64], [128], [3], [9], [27], [81]] + [ + bitvector!(0x10, n = 8), + bitvector!(0x20, n = 8), + bitvector!(0x40, n = 8), + bitvector!(0x80, n = 8), + bitvector!(0x03, n = 8), + bitvector!(0x09, n = 8), + bitvector!(0x1B, n = 8), + bitvector!(0x51, n = 8), + ], ); // also check with iterator interface { - let v0_ref = [16, 32, 64, 128, 3, 9, 27, 81]; + let v0_ref = [16, 32, 64, 128, 3, 9, 27, 81].map(|n| BitVector::new([n]).unwrap()); for (i, (got, expected)) in many_types .v0_volatile_iter() .zip(v0_ref.into_iter()) @@ -154,12 +217,30 @@ fn main() -> ! { { let mut msg = heapless::String::<64>::new(); uwrite!(msg, "rt.v0[{:?}]", i).unwrap(); - expect(&msg, [expected], got); + expect(&msg, expected, got); } } - read_write!(array => "v1", v1, [bv64(0x8), bv64(0x16), bv64(3721049880298531338u64)], set_v1, [bv64(1600), bv64(3200), bv64(7442099760597062676)]); - read_write!(array => "v2", v2, [[[0x8], [0x16]], [[0x24], [0x32]]], set_v2, [[[0xAB], [0xCD]], [[0x12], [0x34]]]); + read_write!( + array => "v1", + v1, + [bitvector!(0x8, n = 64), bitvector!(0x16, n = 64), bitvector!(0x33A3D326B2B42A0A, n = 64)], + set_v1, + [bitvector!(0x640, n = 64), bitvector!(0xC80, n = 64), bitvector!(0x6747A64D65685414, n = 64)], + ); + read_write!( + array => "v2", + v2, + [ + [bitvector!(0x08, n = 8), bitvector!(0x16, n = 8)], + [bitvector!(0x24, n = 8), bitvector!(0x32, n = 8)], + ], + set_v2, + [ + [bitvector!(0xAB, n = 8), bitvector!(0xCD, n = 8)], + [bitvector!(0x12, n = 8), bitvector!(0x34, n = 8)], + ], + ); expect("init.unitW", false, many_types.unit_w()); many_types.set_unit(()); @@ -181,104 +262,160 @@ fn main() -> ! { read_write!( "e0", e0, - types::Either::Left([8]), + types::Either::Left(bitvector!(0x08, n = 8)), set_e0, - types::Either::Right(bv16(0x12)) + types::Either::Right(bitvector!(0x12, n = 16)), ); read_write!( "oi", oi, types::Maybe::Just(types::Inner { - inner_a: [0x16], - inner_b: bv16(0x24), + inner_a: bitvector!(0x16, n = 8), + inner_b: bitvector!(0x24, n = 16), }), set_oi, types::Maybe::Just(types::Inner { - inner_a: [2], - inner_b: bv16(4), - }) + inner_a: bitvector!(0x02, n = 8), + inner_b: bitvector!(0x04, n = 16), + }), ); read_write!( "x2", x2, - types::X2([8], types::X3(bv16(16), [32], [64])), + types::X2( + bitvector!(0x08, n = 8), + types::X3( + bitvector!(0x10, n = 16), + bitvector!(0x20, n = 8), + bitvector!(0x40, n = 8) + ), + ), set_x2, - types::X2([16], types::X3(bv16(32), [64], [128])) + types::X2( + bitvector!(0x10, n = 8), + types::X3( + bitvector!(0x20, n = 16), + bitvector!(0x40, n = 8), + bitvector!(0x80, n = 8) + ), + ), ); read_write!( "me0", me0, - types::Maybe::Just(types::Either::Left([8])), + types::Maybe::Just(types::Either::Left(bitvector!(0x08, n = 8))), set_me0, - types::Maybe::Just(types::Either::Right(bv16(0x12))) + types::Maybe::Just(types::Either::Right(bitvector!(0x12, n = 16))), ); read_write!( "me1", me1, - types::Maybe::Just(types::Either::Left(bv16(8))), + types::Maybe::Just(types::Either::Left(bitvector!(0x08, n = 16))), set_me1, - types::Maybe::Just(types::Either::Right([0x12])) + types::Maybe::Just(types::Either::Right(bitvector!(0x12, n = 8))), ); read_write!( "p0", p0, - types::P0(0xBADC, 0x0F, 0xEE), + types::P0( + unsigned!(0xBADC, n = 16), + unsigned!(0x0F, n = 8), + unsigned!(0xEE, n = 8) + ), set_p0, - types::P0(0x1234, 0x56, 0xFE) + types::P0( + unsigned!(0x1234, n = 16), + unsigned!(0x56, n = 8), + unsigned!(0xFE, n = 8) + ), ); read_write!( "p1", p1, - types::P1(0xBADC, 0x0F, 0xBEAD), + types::P1( + unsigned!(0xBADC, n = 16), + unsigned!(0x0F, n = 8), + unsigned!(0xBEAD, n = 16) + ), set_p1, - types::P1(0x1234, 0x56, 0xFACE) + types::P1( + unsigned!(0x1234, n = 16), + unsigned!(0x56, n = 8), + unsigned!(0xFACE, n = 16) + ), ); read_write!( "p2", p2, - types::P2(0xBADC, 0x0F, 6), + types::P2( + unsigned!(0xBADC, n = 16), + unsigned!(0x0F, n = 8), + index!(6, n = 10) + ), set_p2, - types::P2(0x1234, 0x56, 9) + types::P2( + unsigned!(0x1234, n = 16), + unsigned!(0x56, n = 8), + index!(9, n = 10) + ), ); read_write!( "p3", p3, - types::P3(types::P2(0xBADC, 0x0F, 6,), 0xEE), + types::P3( + types::P2( + unsigned!(0xBADC, n = 16), + unsigned!(0x0F, n = 8), + index!(6, n = 10) + ), + unsigned!(0xEE, n = 8), + ), set_p3, - types::P3(types::P2(0x1234, 0x56, 9), 0xBA) + types::P3( + types::P2( + unsigned!(0x1234, n = 16), + unsigned!(0x56, n = 8), + index!(9, n = 10) + ), + unsigned!(0xBA, n = 8), + ), ); - read_write!("t0", t0, ([12], 584), set_t0, ([24], -948)); + read_write!( + "t0", + t0, + (bitvector!(0x0C, n = 8), signed!(584, n = 16)), + set_t0, + (bitvector!(0x18, n = 8), signed!(-948, n = 16)), + ); - read_write!("i20", i20, 17, set_i20, 3); + read_write!("i20", i20, index!(17, n = 20), set_i20, index!(3, n = 20)); read_write!( "mi12", mi12, - types::Maybe::Just(5), + types::Maybe::Just(index!(5, n = 12)), set_mi12, - types::Maybe::Nothing + types::Maybe::Nothing, ); read_write!( "maybe_b96", maybe_b96, - types::Maybe::Just([ - 0xF1, 0xEE, 0xDB, 0xEA, 0x5D, 0x55, 0x55, 0x55, 0xB5, 0xBA, 0xBA, 0x4A - ]), + types::Maybe::Just(bitvector!(0x4A_BA_BA_B5_55_55_55_5D_EA_DB_EE_F1, n = 96)), set_maybe_b96, - types::Maybe::Nothing + types::Maybe::Nothing, ); read_write!( "maybe_u96", maybe_u96, - types::Maybe::Just(0x4ABABAB55555555DEADBEEF1), + types::Maybe::Just(unsigned!(0x4A_BA_BA_B5_55_55_55_5D_EA_DB_EE_F1, n = 128)), set_maybe_u96, - types::Maybe::Nothing + types::Maybe::Nothing, ); // XXX: Writing a negative value here breaks because of lacking atomic operations. @@ -286,33 +423,39 @@ fn main() -> ! { read_write!( "maybe_s96", maybe_s96, - types::Maybe::Just(0x4ABABAB55555555DEADBEEF1), + types::Maybe::Just(signed!(0x4A_BA_BA_B5_55_55_55_5D_EA_DB_EE_F1, n = 128)), set_maybe_s96, - types::Maybe::Nothing + types::Maybe::Nothing, ); read_write!( "eitherAbc", either_abc, - types::Either::Left([0]), + types::Either::Left(bitvector!(0x0, n = 2)), set_either_abc, - types::Either::Left([0b11]) + types::Either::Left(bitvector!(0x3, n = 2)), ); read_write!( "eitherAbc", either_abc, - types::Either::Left([0b11]), + types::Either::Left(bitvector!(0x3, n = 2)), set_either_abc, - types::Either::Right(types::Abc::B) + types::Either::Right(types::Abc::B), ); read_write!( array => "only_referenced_in_vec", only_referenced_in_vec, - [types::OnlyReferencedInVec([2], bv16(3)), types::OnlyReferencedInVec([4], bv16(5))], + [ + types::OnlyReferencedInVec(bitvector!(0x02, n = 8), bitvector!(0x00_03, n = 13)), + types::OnlyReferencedInVec(bitvector!(0x04, n = 8), bitvector!(0x00_05, n = 13)), + ], set_only_referenced_in_vec, - [types::OnlyReferencedInVec([6], bv16(7)), types::OnlyReferencedInVec([8], bv16(9))] + [ + types::OnlyReferencedInVec(bitvector!(0x06, n = 8), bitvector!(0x00_07, n = 13)), + types::OnlyReferencedInVec(bitvector!(0x08, n = 8), bitvector!(0x00_09, n = 13)), + ], ); test_ok(); diff --git a/firmware-binaries/sim-tests/switch_calendar_test/src/main.rs b/firmware-binaries/sim-tests/switch_calendar_test/src/main.rs index e833163bc..bb9b221f1 100644 --- a/firmware-binaries/sim-tests/switch_calendar_test/src/main.rs +++ b/firmware-binaries/sim-tests/switch_calendar_test/src/main.rs @@ -10,7 +10,11 @@ // Non-aliased imports use bittide_hal::{ hals::switch_c as hal, - manual_additions::calendar::{CalendarEntryType, CalendarType}, + manual_additions::{ + calendar::{CalendarEntryType, CalendarType}, + index::Index, + unsigned::Unsigned, + }, types::ValidEntry_12, }; use ufmt::{uDebug, uwrite, uwriteln}; @@ -53,32 +57,32 @@ fn expect(msg: &str, expected: T, actual: T) { #[cfg_attr(not(test), entry)] fn main() -> ! { - let active_entry0: [u8; 16] = core::array::from_fn(|i| i as u8); - let mut active_entry1: [u8; 16] = active_entry0; + let active_entry0 = core::array::from_fn(|i| Index::new(i as u8).unwrap()); + let mut active_entry1 = active_entry0; active_entry1.reverse(); let cal_active = [ ValidEntry_12 { ve_entry: active_entry0, - ve_repeat: 8, + ve_repeat: Unsigned::new(8).unwrap(), }, ValidEntry_12 { ve_entry: active_entry1, - ve_repeat: 16, + ve_repeat: Unsigned::new(16).unwrap(), }, ]; let cal_shadow: [CalendarEntryType; 16] = core::array::from_fn(|i| ValidEntry_12 { - ve_entry: core::array::from_fn(|_j| i as u8), - ve_repeat: i as u16, + ve_entry: core::array::from_fn(|_j| Index::new(i as u8).unwrap()), + ve_repeat: Unsigned::new(i as u16).unwrap(), }); let calendar = &mut INSTANCES.switch_calendar; // Test End of metacycle register and metacycle count calendar.wait_for_end_of_metacycle(); - let bootmetacycle = calendar.metacycle_count(); + let bootmetacycle = calendar.metacycle_count().into_inner(); calendar.wait_for_end_of_metacycle(); - let next_metacycle = calendar.metacycle_count(); + let next_metacycle = calendar.metacycle_count().into_inner(); expect("(1) Metacycle increment", 1, next_metacycle - bootmetacycle); // diff --git a/firmware-support/Cargo.lock b/firmware-support/Cargo.lock index 24deb6ec4..245b7eb92 100644 --- a/firmware-support/Cargo.lock +++ b/firmware-support/Cargo.lock @@ -71,6 +71,7 @@ dependencies = [ name = "bittide-hal-c" version = "0.1.0" dependencies = [ + "bittide-macros", "cc", "memmap-generate", ] diff --git a/firmware-support/bittide-hal-c/Cargo.toml b/firmware-support/bittide-hal-c/Cargo.toml index 14039baaa..f5da2e121 100644 --- a/firmware-support/bittide-hal-c/Cargo.toml +++ b/firmware-support/bittide-hal-c/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [build-dependencies] +bittide-macros = { path = "../bittide-macros" } memmap-generate = { path = "../memmap-generate" } [dependencies] diff --git a/firmware-support/bittide-hal/build.rs b/firmware-support/bittide-hal/build.rs index d2cd6bb67..9bd5f3e8e 100644 --- a/firmware-support/bittide-hal/build.rs +++ b/firmware-support/bittide-hal/build.rs @@ -366,6 +366,30 @@ fn annotate_types( fn generate_type_ref_imports(ctx: &IrCtx, refs: &TypeReferences) -> TokenStream { let mut code = TokenStream::new(); + if refs.use_bitvec { + code.extend(quote! { + use crate::manual_additions::bitvector::BitVector; + use bittide_macros::BitVector; + }); + } + if refs.use_index { + code.extend(quote! { + use crate::manual_additions::index::Index; + use bittide_macros::Index; + }); + } + if refs.use_signed { + code.extend(quote! { + use crate::manual_additions::signed::Signed; + use bittide_macros::Signed; + }); + } + if refs.use_unsigned { + code.extend(quote! { + use crate::manual_additions::unsigned::Unsigned; + use bittide_macros::Unsigned; + }); + } for ty_ref in &refs.references { let name = &ctx.type_names[*ty_ref].base; let module = ident(IdentType::Module, name); diff --git a/firmware-support/bittide-hal/src/lib.rs b/firmware-support/bittide-hal/src/lib.rs index 41912e6e5..ff1ddccd2 100644 --- a/firmware-support/bittide-hal/src/lib.rs +++ b/firmware-support/bittide-hal/src/lib.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #![no_std] +#![recursion_limit = "146"] // Since this module gets generated, running rustfmt on a // non-built source tree will error. diff --git a/firmware-support/bittide-hal/src/manual_additions/bitvector.rs b/firmware-support/bittide-hal/src/manual_additions/bitvector.rs new file mode 100644 index 000000000..24dee12df --- /dev/null +++ b/firmware-support/bittide-hal/src/manual_additions/bitvector.rs @@ -0,0 +1,792 @@ +// SPDX-FileCopyrightText: 2026 Google LLC +// +// SPDX-License-Identifier: Apache-2.0 +//! Types, traits, and implementations for fixed-width arrays of bits +//! +//! # Note +//! +//! The way that this crate constrains [`BitVector`]s to the correct size is by ensuring that +//! evaluation of a constant succeeds. As such, when writing `impl`s on [`BitVector`], users +//! should make sure to force evaluation of [`BitVectorSizeCheck::SIZE_CHECK`]. +//! +//! Alternatively, one can make use of `bittide_macros::BitVector!` and `bittide_macros::bitvector!` +//! to make types/instances with the correct parameters. +#![deny(missing_docs)] + +use crate::manual_additions::{ + signed::{Signed, SignedInterface, SignedSizeCheck}, + unsigned::{Unsigned, UnsignedInterface, UnsignedSizeCheck}, +}; + +/// A fixed-width array of bits, backed by a byte array +/// +/// This is intended to be an equivalent to a [`packC`'d][bpc] [`BitVector n`][bvn] from Clash. +/// The bytes in this array are little-endian, which is to say that the least significant bit of the +/// bitvector is at array index 0, bit 0. +/// +/// [bpc]: https://github.com/QBayLogic/clash-protocols-memmap/blob/main/clash-bitpackc/src/Clash/Class/BitPackC.hs#L39-L44 +/// [bvn]: https://hackage-content.haskell.org/package/clash-prelude-1.8.4/docs/Clash-Sized-BitVector.html#t:BitVector +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct BitVector(pub(crate) [u8; N]); + +/// Trait for making guarantees about the size and validity of bitvectors. +pub trait BitVectorSizeCheck { + /// This `const` should be instantiated in methods implemented on [`BitVector`], since it + /// is how they are constrained to the correct size. If they're improperly sized, this `const` + /// will fail to evaluate and produce a compile error. + /// + /// # Examples + /// + /// Correctly sized bitvectors will compile successfully: + /// ``` + /// # use bittide_hal::manual_additions::bitvector::{BitVector, BitVectorSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for BitVector<8, 1> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for BitVector<9, 2> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for BitVector<32, 4> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for BitVector<255, 32> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// But improper sizes will not. For example, a bitvector with a backing array that's too large: + /// ```compile_fail + /// # use bittide_hal::manual_additions::bitvector::{BitVector, BitVectorSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for BitVector<1, 10> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// This produces the following error message: + /// ```text + /// error[E0080]: evaluation of ` as BitVectorSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/bitvector.rs + /// | + /// | / const_panic::concat_panic!( + /// | | const_panic::fmt::FmtArg::DISPLAY; + /// | | "Specified bit size `", + /// | | M, + /// ... | + /// | | "`." + /// | | ); + /// | |_____________^ evaluation panicked: Specified bit size `1` should be represented by `1` bytes, not `10`. + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/bitvector.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + /// And a bitvector with a backing array that's too small: + /// ```compile_fail + /// # use bittide_hal::manual_additions::bitvector::{BitVector, BitVectorSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for BitVector<16, 1> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// This produces the following error message: + /// ```text + /// error[E0080]: evaluation of ` as BitVectorSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/bitvector.rs + /// | + /// | / const_panic::concat_panic!( + /// | | const_panic::fmt::FmtArg::DISPLAY; + /// | | "Specified bit size `", + /// | | M, + /// ... | + /// | | "`." + /// | | ); + /// | |_____________^ evaluation panicked: Specified bit size `16` should be represented by `2` bytes, not `1`. + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/bitvector.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + /// And also makes sure that a bitvector is not zero-sized: + /// ```compile_fail + /// # use bittide_hal::manual_additions::bitvector::{BitVector, BitVectorSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for BitVector<0, 0> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// Which produces the error message + /// ```text + /// error[E0080]: evaluation of ` as BitVectorSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/bitvector.rs + /// | + /// | panic!("Cannot represent a `BitVector<0, 0>`!"); + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Cannot represent a `BitVector<0, 0>`! + /// + /// note: erroneous constant encountered + /// --> manual_additions/bitvector.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// + /// error: aborting due to 1 previous error + /// ``` + const SIZE_CHECK: (); + /// Backing type of a bitvector on a trait level + type Inner; + /// Perform a bounds check on an instance of a bitvector. Returns `true` if within bounds, + /// `false` if not. + fn inner_bounds_check(val: &Self::Inner) -> bool; +} + +impl BitVectorSizeCheck for BitVector { + const SIZE_CHECK: () = { + if M == 0 { + panic!("Cannot represent a `BitVector<0, 0>`!"); + } + let correct_size = M.div_ceil(8); + if correct_size != N { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Specified bit size `", + M, + "` should be represented by `", + correct_size, + "` bytes, not `", + N, + "`." + ); + } + }; + type Inner = [u8; N]; + #[inline] + fn inner_bounds_check(val: &[u8; N]) -> bool { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + + // Check that the most significant byte doesn't have bits set that shouldn't be. + if const { M.is_multiple_of(8) } { + // OPTIMIZATION: this cannot happen if the length is a multiple of 8, skip the check + true + } else { + // For `A * 8 + B` bits, create a constant `u8` where the first `B` bits are 1 and the + // rest are 0. Ensure that the most significant byte (at index `A - 1`) is less than or + // equal to this constant. For example, in a `BitVector<43, 6>` we will create the + // constant `0b0000_0111`, and ensure the value at `self.0[5]` is less than or equal to + // this value. + val[const { N - 1 }] <= const { !(!0 << (M % 8)) } + } + } +} + +impl BitVector +where + BitVector: BitVectorSizeCheck, +{ + /// Instantiate a new `BitVector` + /// + /// Byte arrays given to this function are treated as least significant byte first. So for a + /// `BitVector<14, 2>` the value `[0xff, 0x3f]` is in range, but `[0xff, 0x7f]` is not, since + /// that requires 15 bits to represent. + /// + /// This function returns `Some(_)` if `val` is in range and returns `None` otherwise. + /// + /// # Errors + /// + /// This function may error if invoked as `BitVector::::new(...)` and the wrong backing + /// length `N` is chosen. Please read the whole error message carefully, it should tell you what + /// to do to fix it. + #[inline] + pub fn new(val: [u8; N]) -> Option> { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + if BitVector::::inner_bounds_check(&val) { + Some(BitVector(val)) + } else { + None + } + } + + /// Instantiate a new `BitVector` without performing a bounds check + /// + /// # Errors + /// + /// This function may error if invoked as `BitVector::::new_unchecked(...)` and the wrong + /// backing length `N` is chosen. Please read the whole error message carefully, it should tell + /// you what to do to fix it. + /// + /// # Safety + /// + /// Due to the intended use-case of interfacing with Clash hardware, and there being no + /// guarantee of behaviour in the case that an out-of-bounds value is written to a register, + /// this function has been marked as `unsafe`. To make calling this function safe, you must + /// guarantee that `val` is in range. + #[inline] + pub unsafe fn new_unchecked(val: [u8; N]) -> BitVector { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + BitVector(val) + } + + /// Unwrap the inner value contained by this `BitVector` + #[inline] + pub fn into_inner(self) -> [u8; N] { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + self.0 + } +} + +impl core::fmt::Debug for BitVector +where + Self: BitVectorSizeCheck, +{ + #[inline] + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(fmt, "BitVector<{M}>(0x")?; + if const { M % 8 > 3 } { + write!(fmt, "{:02X}", self.0[const { N - 1 }])?; + } else { + write!(fmt, "{:01X}", self.0[const { N - 1 }])?; + } + for &byte in self.0[0..const { N - 1 }].iter().rev() { + write!(fmt, "{byte:02X}")?; + } + write!(fmt, ")") + } +} + +impl core::fmt::Display for BitVector +where + Self: BitVectorSizeCheck, +{ + #[inline] + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(fmt, "0x")?; + if const { M % 8 > 3 } { + write!(fmt, "{:02X}", self.0[const { N - 1 }])?; + } else { + write!(fmt, "{:01X}", self.0[const { N - 1 }])?; + } + for &byte in self.0[0..const { N - 1 }].iter().rev() { + write!(fmt, "{byte:02X}")?; + } + Ok(()) + } +} + +impl ufmt::uDebug for BitVector +where + Self: BitVectorSizeCheck, +{ + #[inline] + fn fmt(&self, fmt: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uwrite!(fmt, "BitVector<{}>(0x", M)?; + if const { M % 8 > 3 } { + ufmt::uwrite!(fmt, "{:02X}", self.0[const { N - 1 }])?; + } else { + ufmt::uwrite!(fmt, "{:01X}", self.0[const { N - 1 }])?; + } + for &byte in self.0[0..const { N - 1 }].iter().rev() { + ufmt::uwrite!(fmt, "{:02X}", byte)?; + } + ufmt::uwrite!(fmt, ")") + } +} + +impl ufmt::uDisplay for BitVector +where + Self: BitVectorSizeCheck, +{ + #[inline] + fn fmt(&self, fmt: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uwrite!(fmt, "0x")?; + if const { M % 8 > 3 } { + ufmt::uwrite!(fmt, "{:02X}", self.0[const { N - 1 }])?; + } else { + ufmt::uwrite!(fmt, "{:01X}", self.0[const { N - 1 }])?; + } + for &byte in self.0[0..const { N - 1 }].iter().rev() { + ufmt::uwrite!(fmt, "{:02X}", byte)?; + } + Ok(()) + } +} + +impl ufmt::uDisplayHex for BitVector +where + Self: BitVectorSizeCheck, +{ + #[inline] + fn fmt_hex( + &self, + fmt: &mut ufmt::Formatter<'_, W>, + options: ufmt::HexOptions, + ) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + if options.upper_case { + if options.ox_prefix { + ufmt::uwrite!(fmt, "0X")?; + if const { M % 8 > 3 } { + ufmt::uwrite!(fmt, "{:02X}", self.0[const { N - 1 }])?; + } else { + ufmt::uwrite!(fmt, "{:01X}", self.0[const { N - 1 }])?; + } + for &byte in self.0[0..const { N - 1 }].iter().rev() { + ufmt::uwrite!(fmt, "{:02X}", byte)?; + } + } + } else { + ufmt::uwrite!(fmt, "0x")?; + if const { M % 8 > 3 } { + ufmt::uwrite!(fmt, "{:02x}", self.0[const { N - 1 }])?; + } else { + ufmt::uwrite!(fmt, "{:01x}", self.0[const { N - 1 }])?; + } + for &byte in self.0[0..const { N - 1 }].iter().rev() { + ufmt::uwrite!(fmt, "{:02x}", byte)?; + } + } + Ok(()) + } +} + +impl AsRef<[u8; N]> for BitVector +where + Self: BitVectorSizeCheck, +{ + #[inline] + fn as_ref(&self) -> &[u8; N] { + &self.0 + } +} + +impl super::seal::Seal for BitVector {} + +/// Trait that guarantees parts of the [`BitVector`] inherent implementations +/// +/// Intended for use where it is not desirable to constrain a type to be `T = BitVector` but +/// rather to `T: BitVectorInterface`. +pub trait BitVectorInterface: Sized + BitVectorSizeCheck + super::seal::Seal { + /// Instantiate a new [`BitVector`] + /// + /// Byte arrays given to this function are treated as least significant byte first. So for a + /// `BitVector<14, 2>` the value `[0xff, 0x3f]` is in range, but `[0xff, 0x7f]` is not, since + /// that requires 15 bits to represent. + /// + /// This function returns `Some(_)` if `val` is in range and returns `None` otherwise. + /// + /// # Errors + /// + /// This function may error if invoked as `BitVector::::bv_new(...)` and the wrong backing + /// length `N` is chosen. Please read the whole error message carefully, it should tell you what + /// to do to fix it. + fn bv_new(val: Self::Inner) -> Option; + /// Instantiate a new [`BitVector`] without performing a bounds check + /// + /// # Errors + /// + /// This function may error if invoked as `BitVector::::bv_new_unchecked(...)` and the + /// wrong backing length `N` is chosen. Please read the whole error message carefully, it should + /// tell you what to do to fix it. + /// + /// # Safety + /// + /// Due to the intended use-case of interfacing with Clash hardware, and there being no + /// guarantee of behaviour in the case that an out-of-bounds value is written to a register, + /// this function has been marked as `unsafe`. To make calling this function safe, you must + /// guarantee that `val` is in range. + unsafe fn bv_new_unchecked(val: Self::Inner) -> Self; + /// Unwrap the inner value contained by this [`BitVector`] + fn bv_into_inner(self) -> Self::Inner; +} + +impl BitVectorInterface for BitVector +where + Self: BitVectorSizeCheck, +{ + #[inline] + fn bv_new(val: Self::Inner) -> Option { + BitVector::::new(val) + } + + #[inline] + unsafe fn bv_new_unchecked(val: Self::Inner) -> Self { + BitVector::::new_unchecked(val) + } + + #[inline] + fn bv_into_inner(self) -> Self::Inner { + self.into_inner() + } +} + +macro_rules! impl_bvc { + ($(@$kind:tt [$backer:ty] $sizelist:tt;)+) => { + $( + impl_bvc! { + @$kind + backer: $backer, + sizes: $sizelist, + } + )+ + }; + ( + @unsigned + backer: $backer:ty, + sizes: [$($size:literal),+$(,)?], + ) => { + $( + impl From> + for Unsigned + where + Self: UnsignedInterface, + BitVector: BitVectorSizeCheck, + { + #[inline] + fn from(other: BitVector) -> Unsigned { + let _size_check = const { + let _ = BitVector::::SIZE_CHECK; // READ THE REST OF THE MESSAGE + let _ = Unsigned::::SIZE_CHECK; // READ THE REST OF THE MESSAGE + if B > A as usize { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "`BitVector<", + B, + ", ", + stringify!($size), + ">` cannot be unconditionally converted into `Unsigned<", + A, + ", ", + stringify!($backer), + ">`", + ); + } + }; + let mut backer: $backer = 0; + unsafe { + (&mut backer as *mut $backer) + .cast::<[u8; $size]>() + .copy_from_nonoverlapping(other.0.as_ptr().cast(), 1); + } + if cfg!(target_endian = "big") { + Unsigned(backer.swap_bytes()) + } else { + Unsigned(backer) + } + } + } + + impl From> + for BitVector + where + Self: BitVectorSizeCheck, + Unsigned: UnsignedInterface, + { + #[inline] + fn from(other: Unsigned) -> BitVector { + let _size_check: () = const { + let _ = BitVector::::SIZE_CHECK; // READ THE REST OF THE MESSAGE + let _ = Unsigned::::SIZE_CHECK; // READ THE REST OF THE MESSAGE + if A as usize > B { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "`Unsigned<", + A, + ", ", + stringify!($backer), + ">` cannot be unconditionally converted into `BitVector<", + B, + ", ", + stringify!($size), + ">`", + ); + } + }; + let mut backer = [0; $size]; + unsafe { + backer.as_mut_ptr() + .copy_from_nonoverlapping( + &other as *const Unsigned as *const u8, + $size, + ); + } + if cfg!(target_endian = "big") { + backer[const { 0..core::mem::size_of::<$backer>() }].reverse(); + } + BitVector(backer) + } + } + )+ + }; + ( + @signed + backer: $backer:ty, + sizes: [$($size:literal),+$(,)?], + ) => { + $( + impl From> + for Signed + where + Self: SignedInterface, + BitVector: BitVectorSizeCheck, + { + #[inline] + fn from(other: BitVector) -> Signed { + let _size_check: () = const { + let _ = BitVector::::SIZE_CHECK; // READ THE REST OF THE MESSAGE + let _ = Signed::::SIZE_CHECK; // READ THE REST OF THE MESSAGE + if B > A as usize { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "`BitVector<", + B, + ", ", + stringify!($size), + ">` cannot be unconditionally converted into `Signed<", + A, + ", ", + stringify!($backer), + ">`", + ); + } + }; + let mut backer: $backer = 0; + unsafe { + (&mut backer as *mut $backer) + .cast::<[u8; $size]>() + .copy_from_nonoverlapping(other.0.as_ptr().cast(), 1); + } + let mut backer = if cfg!(target_endian = "big") { + backer.swap_bytes() + } else { + backer + }; + // Sign extend if most significant bit is set + if backer & const { 1 << (B - 1) } != 0 { + backer |= const { !0 << (B - 1) }; + } + Signed(backer) + } + } + + impl From> + for BitVector + where + Self: BitVectorSizeCheck, + Signed: SignedInterface, + { + #[inline] + fn from(other: Signed) -> BitVector { + let _size_check: () = const { + let _ = BitVector::::SIZE_CHECK; // READ THE REST OF THE MESSAGE + let _ = Signed::::SIZE_CHECK; // READ THE REST OF THE MESSAGE + if A as usize > B { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "`Signed<", + A, + ", ", + stringify!($backer), + ">` cannot be unconditionally converted into `BitVector<", + B, + ", ", + stringify!($size), + ">`", + ); + } + }; + let mut backer = [0; $size]; + unsafe { + backer.as_mut_ptr() + .copy_from_nonoverlapping( + &other as *const Signed as *const u8, + $size, + ); + } + if cfg!(target_endian = "big") { + backer[const { 0..core::mem::size_of::<$backer>() }].reverse(); + } + // Mask sign extension if number is negative and there's bits that could be set + // above the limit in the most significant byte + if other.0 < 0 && const { !A.is_multiple_of(8) } { + backer[const { $size - 1 }] &= const { !(!0 << (A % 8)) }; + } + BitVector(backer) + } + } + )+ + }; +} + +impl_bvc! { + @unsigned [u8] [1]; + @unsigned [u16] [1, 2]; + @unsigned [u32] [1, 2, 3, 4]; + @unsigned [u64] [1, 2, 3, 4, 5, 6, 7, 8]; + @unsigned [u128] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + @signed [i8] [1]; + @signed [i16] [1, 2]; + @signed [i32] [1, 2, 3, 4]; + @signed [i64] [1, 2, 3, 4, 5, 6, 7, 8]; + @signed [i128] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; +} + +impl core::ops::Index for BitVector +where + Self: BitVectorSizeCheck, + [u8; N]: core::ops::Index, +{ + type Output = <[u8; N] as core::ops::Index>::Output; + + #[inline] + fn index(&self, index: T) -> &Self::Output { + self.0.index(index) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn bv_us_conv_prim() { + // Check for n = 8 + let bv = bittide_macros::bitvector!(0xab, n = 8); + let us = bittide_macros::unsigned!(0xab, n = 8); + assert_eq!(bv, us.into(), "US -> BV conversion incorrect"); + assert_eq!(us, bv.into(), "BV -> US conversion incorrect"); + + // Check for n = 16 + let bv = bittide_macros::bitvector!(0xab_cd, n = 16); + let us = bittide_macros::unsigned!(0xab_cd, n = 16); + assert_eq!(bv, us.into(), "US -> BV conversion incorrect"); + assert_eq!(us, bv.into(), "BV -> US conversion incorrect"); + + // Check for n = 32 + let bv = bittide_macros::bitvector!(0xab_cd_ef_01, n = 32); + let us = bittide_macros::unsigned!(0xab_cd_ef_01, n = 32); + assert_eq!(bv, us.into(), "US -> BV conversion incorrect"); + assert_eq!(us, bv.into(), "BV -> US conversion incorrect"); + + // Check for n = 64 + let bv = bittide_macros::bitvector!(0xab_cd_ef_01_23_45_67_89, n = 64); + let us = bittide_macros::unsigned!(0xab_cd_ef_01_23_45_67_89, n = 64); + assert_eq!(bv, us.into(), "US -> BV conversion incorrect"); + assert_eq!(us, bv.into(), "BV -> US conversion incorrect"); + + // Check for n = 128 + let bv = + bittide_macros::bitvector!(0xab_cd_ef_01_23_45_67_89_ab_cd_ef_01_23_45_67_89, n = 128); + let us = + bittide_macros::unsigned!(0xab_cd_ef_01_23_45_67_89_ab_cd_ef_01_23_45_67_89, n = 128); + assert_eq!(bv, us.into(), "US -> BV conversion incorrect"); + assert_eq!(us, bv.into(), "BV -> US conversion incorrect"); + } + + #[test] + fn bv_us_conv_nonprim() { + // Check for n = 24 (non-primitive-sized) + let bv = bittide_macros::bitvector!(0xab_cd_ef, n = 24); + let us = bittide_macros::unsigned!(0xab_cd_ef, n = 24); + assert_eq!(bv, us.into(), "US -> BV conversion incorrect"); + assert_eq!(us, bv.into(), "BV -> US conversion incorrect"); + + // Check for n = 25 (non-primitive-sized) + let bv = bittide_macros::bitvector!(0x1_ab_cd_ef, n = 25); + let us = bittide_macros::unsigned!(0x1_ab_cd_ef, n = 25); + assert_eq!(bv, us.into(), "US -> BV conversion incorrect"); + assert_eq!(us, bv.into(), "BV -> US conversion incorrect"); + } + + #[test] + fn bv_sn_conv_prim() { + // Check for n = 8 + let bv = bittide_macros::bitvector!(0xab, n = 8); + let sn = bittide_macros::signed!(0xab, n = 8); + assert_eq!(bv, sn.into(), "SN -> BV conversion incorrect"); + assert_eq!(sn, bv.into(), "BV -> SN conversion incorrect"); + + // Check for n = 16 + let bv = bittide_macros::bitvector!(0xab_cd, n = 16); + let sn = bittide_macros::signed!(0xab_cd, n = 16); + assert_eq!(bv, sn.into(), "SN -> BV conversion incorrect"); + assert_eq!(sn, bv.into(), "BV -> SN conversion incorrect"); + + // Check for n = 32 + let bv = bittide_macros::bitvector!(0xab_cd_ef_01, n = 32); + let sn = bittide_macros::signed!(0xab_cd_ef_01, n = 32); + assert_eq!(bv, sn.into(), "SN -> BV conversion incorrect"); + assert_eq!(sn, bv.into(), "BV -> SN conversion incorrect"); + + // Check for n = 64 + let bv = bittide_macros::bitvector!(0xab_cd_ef_01_23_45_67_89, n = 64); + let sn = bittide_macros::signed!(0xab_cd_ef_01_23_45_67_89, n = 64); + assert_eq!(bv, sn.into(), "SN -> BV conversion incorrect"); + assert_eq!(sn, bv.into(), "BV -> SN conversion incorrect"); + + // Check for n = 128 + let bv = + bittide_macros::bitvector!(0xab_cd_ef_01_23_45_67_89_ab_cd_ef_01_23_45_67_89, n = 128); + let sn = + bittide_macros::signed!(0xab_cd_ef_01_23_45_67_89_ab_cd_ef_01_23_45_67_89, n = 128); + assert_eq!(bv, sn.into(), "SN -> BV conversion incorrect"); + assert_eq!(sn, bv.into(), "BV -> SN conversion incorrect"); + } + + #[test] + fn bv_sn_conv_nonprim() { + // Check for n = 24 (non-primitive-sized, no sign extension needed) + let bv = bittide_macros::bitvector!(0x2b_cd_ef, n = 24); + let sn = bittide_macros::signed!(0x2b_cd_ef, n = 24); + assert_eq!(bv, sn.into(), "SN -> BV conversion incorrect"); + assert_eq!(sn, bv.into(), "BV -> SN conversion incorrect"); + + // Check for n = 25 (non-primitive-sized, sign extension needed) + let bv = bittide_macros::bitvector!(0x1_ab_cd_ef, n = 25); + let sn = bittide_macros::signed!(0x1_ab_cd_ef, n = 25); + assert_eq!(bv, sn.into(), "SN -> BV conversion incorrect"); + assert_eq!(sn, bv.into(), "BV -> SN conversion incorrect"); + } +} diff --git a/firmware-support/bittide-hal/src/manual_additions/calendar.rs b/firmware-support/bittide-hal/src/manual_additions/calendar.rs index 6b355d46b..08c804148 100644 --- a/firmware-support/bittide-hal/src/manual_additions/calendar.rs +++ b/firmware-support/bittide-hal/src/manual_additions/calendar.rs @@ -7,24 +7,23 @@ The `calendar` module provides a hardware abstraction layer over based on the ge peripheral access code for the `Calendar` device. */ -use crate::manual_additions::{FromAs, IntoAs}; +use crate::manual_additions::{ + index::{Index, IndexInner, IndexInterface, IndexSizeCheck}, + unsigned::{Unsigned, UnsignedInterface, UnsignedSizeCheck}, + FromAs, IntoAs, +}; +use bittide_macros::{Index, Unsigned}; pub trait ValidEntryType { type Inner; type Repeat; - const REPEAT_MASK: Self::Repeat; - fn new(entry: Self::Inner, repeat: Self::Repeat) -> Self; } pub type ValidEntryInner = ::Inner; pub type ValidEntryRepeat = ::Repeat; -pub const fn valid_entry_repeat_mask() -> ValidEntryRepeat { - ::REPEAT_MASK -} - macro_rules! impl_valid_entry_type { ( type: [$($valid:tt)+], @@ -35,10 +34,8 @@ macro_rules! impl_valid_entry_type { type Inner = T; type Repeat = $repeat; - const REPEAT_MASK: Self::Repeat = $mask as $repeat; - + #[inline] fn new(entry: Self::Inner, repeat: Self::Repeat) -> Self { - let repeat = repeat & Self::REPEAT_MASK; Self { ve_entry: entry, ve_repeat: repeat, @@ -50,36 +47,28 @@ macro_rules! impl_valid_entry_type { impl_valid_entry_type! { type: [crate::types::ValidEntry_12], - repeat: u16, + repeat: Unsigned!(16), mask: 12, } impl_valid_entry_type! { type: [crate::types::ValidEntry_16], - repeat: u16, + repeat: Unsigned!(16), mask: 16, } /// Abstraction trait over all the methods that a calendar type should provide pub trait CalendarInterface { type EntryType: Copy + ValidEntryType; - type MetacycleCount; - - type ShadowIndex: Ord + FromAs + IntoAs + IntoAs; - const SHADOW_INDEX_MAX: Self::ShadowIndex; + type MetacycleCount: UnsignedInterface; + type Index: IndexInterface + PartialOrd> + IntoAs; - type WriteIndex: Ord + FromAs + IntoAs; - const WRITE_ADDR_MAX: Self::WriteIndex; - - type ReadIndex: Ord + FromAs + IntoAs; - const READ_ADDR_MAX: Self::ReadIndex; - - fn calint_shadow_depth_index(&self) -> Self::ShadowIndex; + fn calint_shadow_depth_index(&self) -> Self::Index; fn calint_shadow_entry(&self) -> Self::EntryType; - fn calint_metacycle_count(&self) -> u32; - fn calint_set_shadow_depth_index(&self, val: Self::ShadowIndex); - fn calint_set_write_addr(&self, val: Self::WriteIndex); - fn calint_set_read_addr(&self, val: Self::ReadIndex); + fn calint_metacycle_count(&self) -> Self::MetacycleCount; + fn calint_set_shadow_depth_index(&self, val: Self::Index); + fn calint_set_write_addr(&self, val: Self::Index); + fn calint_set_read_addr(&self, val: Self::Index); fn calint_set_shadow_entry(&self, val: Self::EntryType); fn calint_set_swap_active(&self, val: bool); fn calint_set_end_of_metacycle(&self, val: bool); @@ -91,22 +80,14 @@ pub type CalendarEntryType = ::EntryType; /// Type alias for retrieving the metacycle count type of a calendar type. pub type CalendarMetacycleCount = ::MetacycleCount; -/// Type alias for retrieving the shadow index type of a calendar type. -pub type CalendarShadowIndex = ::ShadowIndex; - -/// Type alias for retrieving the write index type of a calendar type. -pub type CalendarWriteIndex = ::WriteIndex; - -/// Type alias for retrieving the read index type of a calendar type. -pub type CalendarReadIndex = ::ReadIndex; +/// Type alias for retrieving the index type of a calendar type. +pub type CalendarIndex = ::Index; macro_rules! impl_calendar_interface { ( cal: $cty:ty, metacycle: $mc:ty, - shadow: $si:ty, - write: $wi:ty, - read: $ri:ty, + index: $ix:ty, entry: $et:ty, ) => { // Assertions that must be met for the implementation to be valid @@ -124,7 +105,7 @@ macro_rules! impl_calendar_interface { ); } // Assert that the shadow index type can hold the maximum shadow depth index - if (<$cty>::SHADOW_DEPTH_INDEX_SIZE as u128 - 1) > (<$si>::MAX as u128) { + if (<$cty>::SHADOW_DEPTH_INDEX_SIZE as u128 - 1) > (<$ix>::MAX as u128) { const_panic::concat_panic!( "Shadow index type ", stringify!($si), @@ -132,70 +113,53 @@ macro_rules! impl_calendar_interface { <$cty>::SHADOW_DEPTH_INDEX_SIZE as u128 - 1, ); } - // Assert that the write index type can hold the maximum write address - if <$cty>::WRITE_ADDR_SIZE as u128 - 1 > <$wi>::MAX as u128 { - const_panic::concat_panic!( - "Write index type ", - stringify!($wi), - " cannot fit the maximum value ", - <$cty>::WRITE_ADDR_SIZE as u128 - 1, - ); - } - // Assert that the read index type can hold the maximum read address - if <$cty>::READ_ADDR_SIZE as u128 - 1 > <$ri>::MAX as u128 { - const_panic::concat_panic!( - "Read index type ", - stringify!($ri), - " cannot fit the maximum value ", - <$cty>::READ_ADDR_SIZE as u128 - 1, - ); - } }; impl CalendarInterface for $cty { type EntryType = $et; type MetacycleCount = $mc; + type Index = $ix; - type ShadowIndex = $si; - const SHADOW_INDEX_MAX: $si = (<$cty>::SHADOW_DEPTH_INDEX_SIZE - 1) as $si; - - type WriteIndex = $wi; - const WRITE_ADDR_MAX: $wi = (<$cty>::WRITE_ADDR_SIZE - 1) as $wi; - - type ReadIndex = $ri; - const READ_ADDR_MAX: $ri = (<$cty>::READ_ADDR_SIZE - 1) as $ri; - - fn calint_shadow_depth_index(&self) -> Self::ShadowIndex { + #[inline] + fn calint_shadow_depth_index(&self) -> Self::Index { self.shadow_depth_index() } + #[inline] fn calint_shadow_entry(&self) -> Self::EntryType { self.shadow_entry() } + #[inline] fn calint_metacycle_count(&self) -> Self::MetacycleCount { self.metacycle_count() } - fn calint_set_shadow_depth_index(&self, val: Self::ShadowIndex) { + #[inline] + fn calint_set_shadow_depth_index(&self, val: Self::Index) { self.set_shadow_depth_index(val) } - fn calint_set_write_addr(&self, val: Self::WriteIndex) { + #[inline] + fn calint_set_write_addr(&self, val: Self::Index) { self.set_write_addr(val) } - fn calint_set_read_addr(&self, val: Self::ReadIndex) { + #[inline] + fn calint_set_read_addr(&self, val: Self::Index) { self.set_read_addr(val) } + #[inline] fn calint_set_shadow_entry(&self, val: Self::EntryType) { self.set_shadow_entry(val) } + #[inline] fn calint_set_swap_active(&self, val: bool) { self.set_swap_active(val) } + #[inline] fn calint_set_end_of_metacycle(&self, val: bool) { self.set_end_of_metacycle(val) } @@ -211,11 +175,12 @@ pub trait CalendarType: CalendarInterface { /// /// Panics if `n` is an invalid value for a `Self::ReadIndex` type instance. fn read_shadow_entry(&self, n: usize) -> Self::EntryType { + let n_inner = n.into_as(); assert!( - n as u128 <= Self::READ_ADDR_MAX.into_as(), + n_inner <= Self::Index::MAX, "Shadow entry read index is out of bounds." ); - self.calint_set_read_addr(Self::ReadIndex::from_as(n)); + self.calint_set_read_addr(unsafe { Self::Index::idx_new_unchecked(n_inner) }); self.calint_shadow_entry() } @@ -225,12 +190,13 @@ pub trait CalendarType: CalendarInterface { /// /// Panics if `n` is an invalid value for a `Self::WriteIndex` type instance. fn write_shadow_entry(&self, n: usize, entry: Self::EntryType) { + let n_inner = n.into_as(); assert!( - n as u128 <= Self::WRITE_ADDR_MAX.into_as(), + n_inner <= Self::Index::MAX, "Shadow entry write index is out of bounds." ); self.calint_set_shadow_entry(entry); - self.calint_set_write_addr(Self::WriteIndex::from_as(n)); + self.calint_set_write_addr(unsafe { Self::Index::idx_new_unchecked(n_inner) }); } /// Swaps the active and shadow calendar at the end of the metacycle. @@ -245,13 +211,13 @@ pub trait CalendarType: CalendarInterface { /// Returns the number of entries in the shadow calendar. fn shadow_depth(&self) -> usize { - >::into_as(self.calint_shadow_depth_index()) + 1 + self.calint_shadow_depth_index().into_as() + 1 } /// Returns an iterator over the shadow calendar entries. fn read_shadow_calendar<'a>(&'a self) -> impl Iterator + 'a { (0..self.shadow_depth()).map(|n| { - self.calint_set_read_addr(Self::ReadIndex::from_as(n)); + self.calint_set_read_addr(unsafe { Self::Index::idx_new_unchecked(n.into_as()) }); self.calint_shadow_entry() }) } @@ -267,63 +233,56 @@ pub trait CalendarType: CalendarInterface { return; } assert!( - entries.len() as u128 - <= >::into_as(Self::SHADOW_INDEX_MAX), - "Entries exceed shadow calendar size" + entries.len() <= Self::Index::IMAX.into_as(), + "Input slice (length {}) is too long for calendar of size {}", + entries.len(), + Self::Index::N_, ); for (n, entry) in entries.iter().enumerate() { self.calint_set_shadow_entry(*entry); - self.calint_set_write_addr(Self::WriteIndex::from_as(n)); + self.calint_set_write_addr(unsafe { Self::Index::idx_new_unchecked(n.into_as()) }); } - self.calint_set_shadow_depth_index(Self::ShadowIndex::from_as(entries.len() - 1)); + self.calint_set_shadow_depth_index(unsafe { + Self::Index::idx_new_unchecked((entries.len() - 1).into_as()) + }); } } -impl CalendarType for T {} +impl CalendarType for T where T: CalendarInterface {} impl_calendar_interface! { cal: crate::hals::switch_c::devices::Calendar, - metacycle: u32, - shadow: u8, - write: u8, - read: u8, - entry: crate::types::ValidEntry_12<[u8; 16]>, + metacycle: Unsigned!(32), + index: Index!(256), + entry: crate::types::ValidEntry_12<[Index!(17); 16]>, } impl_calendar_interface! { cal: crate::hals::switch_demo_mu::devices::Calendar, - metacycle: u32, - shadow: u8, - write: u8, - read: u8, - entry: crate::types::ValidEntry_12<[u8; 8]>, + metacycle: Unsigned!(32), + index: Index!(7), + entry: crate::types::ValidEntry_12<[Index!(9); 8]>, } impl_calendar_interface! { cal: crate::hals::switch_demo_gppe_mu::devices::Calendar, - metacycle: u32, - shadow: u16, - write: u16, - read: u16, - entry: crate::types::ValidEntry_12, + metacycle: Unsigned!(32), + index: Index!(1024), + entry: crate::types::ValidEntry_12, } impl_calendar_interface! { cal: crate::hals::scatter_gather_pe::devices::Calendar, - metacycle: u32, - shadow: u8, - write: u8, - read: u8, - entry: crate::types::ValidEntry_12, + metacycle: Unsigned!(32), + index: Index!(32), + entry: crate::types::ValidEntry_12, } impl_calendar_interface! { cal: crate::hals::soft_ugn_demo_mu::devices::Calendar, - metacycle: u32, - shadow: u16, - write: u16, - read: u16, - entry: crate::types::ValidEntry_16, + metacycle: Unsigned!(32), + index: Index!(4000), + entry: crate::types::ValidEntry_16, } pub trait RingbufferCalendar { @@ -333,7 +292,8 @@ pub trait RingbufferCalendar { impl RingbufferCalendar for T where T: CalendarType, - ValidEntryInner>: FromAs, + core::ops::Range>: IntoIterator>, + ValidEntryInner>: From, ValidEntryRepeat>: FromAs, { /// Initializes a calendar type which contains entries derivable from `usize` and repeats @@ -347,18 +307,19 @@ where /// calendar (compared against [`Self::SHADOW_INDEX_MAX`]) fn initialize_as_ringbuffer(&self, size: usize) { assert!(size > 0, "Cannot have a ringbuffer of size 0!"); - let size_max_index: CalendarShadowIndex = (size - 1).into_as(); - if size_max_index > Self::SHADOW_INDEX_MAX { - panic!( - "Size ({size}) exceeds calendar size ({})", - as IntoAs>::into_as(Self::SHADOW_INDEX_MAX) - ); + let size_max_index: IndexInner = (size - 1).into_as(); + if >>::into_as(size) > T::Index::N_AS_INNER { + panic!("Size ({size}) exceeds calendar size ({})", T::Index::N_); } - for n in 0..size { - let entry = CalendarEntryType::::new(n.into_as(), 0.into_as()); + for (n, idx) in (IndexInner::::from_as(0usize)..T::Index::N_AS_INNER) + .into_iter() + .map(|n| unsafe { T::Index::idx_new_unchecked(n) }) + .enumerate() + { + let entry = CalendarEntryType::::new(idx.into(), 0.into_as()); self.write_shadow_entry(n, entry); } - self.calint_set_shadow_depth_index(size_max_index); + self.calint_set_shadow_depth_index(unsafe { T::Index::idx_new_unchecked(size_max_index) }); self.calint_set_swap_active(true); } } diff --git a/firmware-support/bittide-hal/src/manual_additions/capture_ugn.rs b/firmware-support/bittide-hal/src/manual_additions/capture_ugn.rs index 7b803aae0..d0c3064e9 100644 --- a/firmware-support/bittide-hal/src/manual_additions/capture_ugn.rs +++ b/firmware-support/bittide-hal/src/manual_additions/capture_ugn.rs @@ -13,6 +13,6 @@ impl CaptureUgn { } pub fn ugn_unchecked(&self) -> u64 { - self.local_counter() - self.remote_counter() + self.local_counter().into_inner() - self.remote_counter().into_inner() } } diff --git a/firmware-support/bittide-hal/src/manual_additions/dna.rs b/firmware-support/bittide-hal/src/manual_additions/dna.rs index 280979d1b..3619075a8 100644 --- a/firmware-support/bittide-hal/src/manual_additions/dna.rs +++ b/firmware-support/bittide-hal/src/manual_additions/dna.rs @@ -10,7 +10,7 @@ impl Dna { pub fn dna(&self) -> [u8; 12] { loop { match self.maybe_dna() { - Maybe::Just(value) => return value, + Maybe::Just(value) => return value.into_inner(), Maybe::Nothing => continue, } } diff --git a/firmware-support/bittide-hal/src/manual_additions/elastic_buffer.rs b/firmware-support/bittide-hal/src/manual_additions/elastic_buffer.rs index e8d850a15..4d8ff6a99 100644 --- a/firmware-support/bittide-hal/src/manual_additions/elastic_buffer.rs +++ b/firmware-support/bittide-hal/src/manual_additions/elastic_buffer.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::shared_devices::elastic_buffer::ElasticBuffer; +use bittide_macros::Signed; impl ElasticBuffer { /// Minimum occupancy value for the elastic buffer (signed 8-bit). @@ -18,7 +19,7 @@ impl ElasticBuffer { /// 2. Waits for completion with `adjustment_wait` /// /// Negative values drain (remove frames), positive values fill (add frames). - pub fn set_adjustment(&self, adjustment: i32) { + pub fn set_adjustment(&self, adjustment: Signed!(32)) { self.set_adjustment_async(adjustment); self.set_adjustment_wait(()); } @@ -34,9 +35,9 @@ impl ElasticBuffer { /// /// Returns the number of frames added or dropped pub fn set_occupancy(&self, target: i8) -> i8 { - let current = self.data_count(); + let current = self.data_count().into_inner(); let delta = target - current; - self.set_adjustment(delta as i32); + self.set_adjustment(delta.into()); delta } @@ -50,9 +51,9 @@ impl ElasticBuffer { /// /// Returns the number of frames added or dropped pub fn set_occupancy_async(&self, target: i8) -> i8 { - let current = self.data_count(); + let current = self.data_count().into_inner(); let delta = target - current; - self.set_adjustment_async(delta as i32); + self.set_adjustment_async(delta.into()); delta } diff --git a/firmware-support/bittide-hal/src/manual_additions/index.rs b/firmware-support/bittide-hal/src/manual_additions/index.rs new file mode 100644 index 000000000..123b30d30 --- /dev/null +++ b/firmware-support/bittide-hal/src/manual_additions/index.rs @@ -0,0 +1,657 @@ +// SPDX-FileCopyrightText: 2026 Google LLC +// +// SPDX-License-Identifier: Apache-2.0 +//! Types, traits, and implementations for index types +//! +//! # Note +//! +//! The way that this crate constrains [`Index`]s to the correct size is by ensuring that +//! evaluation of a constant succeeds. As such, when writing `impl`s on [`Index`], users +//! should make sure to force evaluation of [`IndexSizeCheck::SIZE_CHECK`]. +//! +//! Alternatively, one can make use of `bittide_macros::Index!` and `bittide_macros::index!` to make +//! types/instances with the correct parameters. +#![deny(missing_docs)] + +use super::{signed::Signed, unsigned::Unsigned, FromAs}; + +/// Type used to represent values in the range `0..N`, backed by the smallest useable unsigned type +/// +/// This is an equivalent to a [`packC`'d][bpc] [`Index n`][idx] from Clash. This type should be +/// backed by the smallest unsigned integer capable of representing all values in `0..N`. For +/// example, an `Index<256, _>` should be `Index<256, u8>`, and `Index<257, _>` should be +/// `Index<257, u16>`. +/// +/// [bpc]: https://github.com/QBayLogic/clash-protocols-memmap/blob/main/clash-bitpackc/src/Clash/Class/BitPackC.hs#L39-L44 +/// [idx]: https://hackage-content.haskell.org/package/clash-prelude-1.8.4/docs/Clash-Sized-Index.html#t:Index +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Index(pub(crate) T); + +/// Trait for making guarantees about the size and validity of index types +pub trait IndexSizeCheck { + /// This `const` should be instantiated in methods implemented on [`Index`], since it is + /// how they are constrained to the correct size. If they're improperly sized, this `const` will + /// fail to evaluate and produce a compile error. + /// + /// # Examples + /// + /// Indices with an appropriate backing type will compile successfully: + /// ``` + /// # use bittide_hal::manual_additions::index::{Index, IndexSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Index<256, u8> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Index<257, u16> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Index<65_536, u16> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Index<65_537, u32> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// But indices with an improperly sized backing type will produce an error. For example, when + /// the backing type is too small: + /// ```compile_fail + /// # use bittide_hal::manual_additions::index::{Index, IndexSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Index<100_000, u8> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// This produces the error + /// ```text + /// error[E0080]: evaluation of ` as IndexSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/index.rs + /// | + /// | impl_isc!(u8, u16, u32, u64); // READ THE REST OF THE ERROR MESSAGE + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Max bound 100000 cannot fit in type `u8` (max value: 256) + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` which comes from the expansion of the macro `impl_isc` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/index.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + /// Or when the backing type is too large: + /// ```compile_fail + /// # use bittide_hal::manual_additions::index::{Index, IndexSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Index<10, u128> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// This produces the error + /// ```text + /// error[E0080]: evaluation of ` as IndexSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/index.rs + /// | + /// | / const_panic::concat_panic!( + /// | | const_panic::fmt::FmtArg::DISPLAY; + /// | | "Type u128 is not optimally sized for bound ", + /// | | N, + /// ... | + /// | | "` instead.", + /// | | ); + /// | |_____________^ evaluation panicked: Type u128 is not optimally sized for bound 10. Use type `u8` instead. + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/index.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + /// Or when the index bound is 0: + /// ```compile_fail + /// # use bittide_hal::manual_additions::index::{Index, IndexSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Index<0, u128> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// ```text + /// error[E0080]: evaluation of ` as IndexSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/index.rs:230:13 + /// | + /// | panic!("Cannot represent `Index<0, T>`!"); + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Cannot represent `Index<0, T>`! + /// + /// note: erroneous constant encountered + /// --> manual_additions/index.rs:143:17 + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + const SIZE_CHECK: (); + /// Generic parameter to the type in case it's not useable directly + const N_: u128; + /// Backing type of an index + type Inner: Copy; + /// Generic parameter to the type casted to the inner type of the index + const N_AS_INNER: Self::Inner; + /// Perform a bounds check on an instance of an index. Returns `true` if within bounds, `false` + /// if not. + fn inner_bounds_check(val: Self::Inner) -> bool; + /// The maximum value allowed in the index type (`N - 1`) + const MAX: Self::Inner; + /// Index instance containing the maximum allowable value + const IMAX: Self; +} + +/// Type alias for retrieving the backing type of an index type +pub type IndexInner = ::Inner; + +macro_rules! impl_isc { + ($($t:ty),+$(,)?) => { + $( + impl IndexSizeCheck for Index { + const SIZE_CHECK: () = { + if N == 0 { + panic!("Cannot represent `Index<0, T>`!"); + } + // `u128::BITS - N.leading_zeros()` is equivalent to `N.clog2()` + let correct_size = match + (u128::BITS - (N - 1).leading_zeros()).next_power_of_two() + { + size if size >= 8 => size, + _ => 8, + }; + if N - 1 > <$t>::MAX as u128 { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Max bound ", + N, + " cannot fit in type `", + stringify!($t), + "` (max value: ", + <$t>::MAX as u128 + 1, + ")" + ); + } else if correct_size != <$t>::BITS { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Type `", + stringify!($t), + "` is not optimally sized for bound ", + N, + ". Use type `u", + correct_size, + "` instead.", + ); + } + }; + const N_: u128 = N; + type Inner = $t; + // NOTE + // Casting `N` as `$t` will always work, because the above `const SIZE_CHECK` will + // produce a compile error if `N` doesn't fit into the type `$t`. + const N_AS_INNER: $t = { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + N as $t + }; + #[inline] + fn inner_bounds_check(val: $t) -> bool { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + if const { N - 1 == <$t>::MAX as u128 } { + true + } else { + // NOTE + // Casting `N` as `$t` will always work, because the above + // `const SIZE_CHECK` will produce a compile error if `N` doesn't fit into + // the type `$t`. + val < Self::N_AS_INNER + } + } + const MAX: $t = (N - 1) as $t; + const IMAX: Self = Index(Self::MAX); + } + )+ + }; +} + +impl_isc!(u8, u16, u32, u64); // READ THE REST OF THE ERROR MESSAGE + +impl IndexSizeCheck for Index { + const SIZE_CHECK: () = { + if N == 0 { + panic!("Cannot represent `Index<0, T>`!"); + } + // `u128::BITS - (N - 1).leading_zeros()` is equivalent to `N.clog2()` + let correct_size = match (u128::BITS - (N - 1).leading_zeros()).next_power_of_two() { + size if size >= 8 => size, + _ => 8, + }; + if correct_size != u128::BITS { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Type u128 is not optimally sized for bound ", + N, + ". Use type `u", + correct_size, + "` instead.", + ); + } + }; + const N_: u128 = N; + type Inner = u128; + const N_AS_INNER: u128 = { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + N + }; + #[inline] + fn inner_bounds_check(val: u128) -> bool { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + val < N + } + const MAX: u128 = N - 1; + const IMAX: Self = Index(Self::MAX); +} + +impl Index +where + T: Copy, + Index: IndexSizeCheck, +{ + /// Instantiate a new `Index` + /// + /// Returns `Some(_)` if `val` is in the range `0..N`, returns `None` otherwise. + /// + /// # Errors + /// + /// This function may error if invoked as `Index::::new(...)` and the wrong backing type + /// `T` is chosen. Please read the whole error message carefully, it should tell you what to do + /// to fix it. + #[inline] + pub fn new(val: T) -> Option> { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + if Index::::inner_bounds_check(val) { + Some(Index(val)) + } else { + None + } + } + + /// Instantiate a new `Index` without performing a bounds check + /// + /// # Errors + /// + /// This function may error if invoked as `Index::::new_unchecked(...)` and the wrong + /// backing type `T` is chosen. Please read the whole error message carefully, it should tell + /// you what to do to fix it. + /// + /// # Safety + /// + /// Due to the intended use-case of interfacing with Clash hardware, and there being no + /// guarantee of behaviour in the case that an out-of-bounds value is written to a register, + /// this function has been marked as `unsafe`. To make calling this function safe, you must + /// guarantee that `val` is in the range `0..N`. + #[inline] + pub unsafe fn new_unchecked(val: T) -> Index { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + Index(val) + } + + /// Unwrap the inner value contained by this `Index` + #[inline] + pub fn into_inner(self) -> T { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + self.0 + } +} + +impl core::fmt::Debug for Index +where + Self: IndexSizeCheck, + T: core::fmt::Debug, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Index<{N}>({:?})", self.0) + } +} + +impl core::fmt::Display for Index +where + Self: IndexSizeCheck, + T: core::fmt::Display, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + +#[inline] +const fn clog2_cgu128() -> usize { + let mut m = 0; + let mut n = 1; + while n - 1 < N { + m += 1; + let Some(n_next) = n.checked_mul(10) else { + break; + }; + n = n_next; + } + m +} + +const U128_MAXLEN: usize = clog2_cgu128::<{ u128::MAX }>() + 1; +type CGu128FmtData = [u8; U128_MAXLEN]; + +struct CGU128Formatter; + +impl CGU128Formatter { + const DATA: CGu128FmtData = { + let clog2 = clog2_cgu128::(); + let mut data = [0; U128_MAXLEN]; + let mut idx = 0; + let mut acc = N; + while idx < clog2 { + data[clog2 - idx - 1] = b'0' + (acc % 10) as u8; + acc /= 10; + idx += 1; + } + data + }; + const STR: &'static str = { + let clog2 = clog2_cgu128::(); + unsafe { + core::str::from_utf8_unchecked(core::slice::from_raw_parts(Self::DATA.as_ptr(), clog2)) + } + }; +} + +impl ufmt::uDebug for Index +where + T: ufmt::uDebug, +{ + #[inline] + fn fmt(&self, w: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uwrite!(w, "Index<{}>({:?})", CGU128Formatter::::STR, self.0) + } +} + +impl ufmt::uDisplay for Index +where + T: ufmt::uDisplay, +{ + #[inline] + fn fmt(&self, w: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uwrite!(w, "Index<{}>({})", CGU128Formatter::::STR, self.0) + } +} + +impl ufmt::uDisplayHex for Index +where + Self: IndexSizeCheck, + T: ufmt::uDisplayHex, +{ + #[inline] + fn fmt_hex( + &self, + fmt: &mut ufmt::Formatter<'_, W>, + options: ufmt::HexOptions, + ) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ::fmt_hex(&self.0, fmt, options) + } +} + +impl super::seal::Seal for Index where T: super::seal::Seal {} + +/// Trait that guarantees parts of the [`Index`] inherent implementations +/// +/// Intended for use where it is not desirable to constrain a type to be `T = Index` but +/// rather to `T: IndexInterface`. +pub trait IndexInterface: Sized + IndexSizeCheck + super::seal::Seal { + /// Instantiate a new [`Index`] + /// + /// Returns `Some(_)` if `val` is in the range `0..N`, returns `None` otherwise. + /// + /// # Errors + /// + /// This function may error if invoked as `Index::::idx_new(...)` and the wrong backing + /// type `T` is chosen. Please read the whole error message carefully, it should tell you what + /// to do to fix it. + fn idx_new(val: Self::Inner) -> Option; + /// Instantiate a new [`Index`] without performing a bounds check + /// + /// # Errors + /// + /// This function may error if invoked as `Index::::idx_new_unchecked(...)` and the wrong + /// backing type `T` is chosen. Please read the whole error message carefully, it should tell + /// you what to do to fix it. + /// + /// # Safety + /// + /// Due to the intended use-case of interfacing with Clash hardware, and there being no + /// guarantee of behaviour in the case that an out-of-bounds value is written to a register, + /// this function has been marked as `unsafe`. To make calling this function safe, you must + /// guarantee that `val` is in the range `0..N`. + unsafe fn idx_new_unchecked(val: Self::Inner) -> Self; + /// Unwrap the inner value contained by this [`Index`] + fn idx_into_inner(self) -> Self::Inner; +} + +impl IndexInterface for Index +where + T: Copy + super::seal::Seal, + Index: IndexSizeCheck, +{ + #[inline] + fn idx_new(val: Self::Inner) -> Option { + Index::::new(val) + } + + #[inline] + unsafe fn idx_new_unchecked(val: Self::Inner) -> Self { + Index::::new_unchecked(val) + } + + #[inline] + fn idx_into_inner(self) -> Self::Inner { + self.into_inner() + } +} + +macro_rules! def_index_from { + // Entry + ($($t:ty),+$(,)?) => { + def_index_from! { + @run + sup: [$($t),+], + sub: [], + } + }; + // Base case + ( + @run + sup: [], + sub: $sub:tt, + ) => {}; + // Recursive case + ( + @run + sup: [$h:ty$(, $t:ty)*], + sub: [$($sub:ty),*], + ) => { + // Expand defs for current head of big type + $( // for $sub in $($sub),*: + def_index_from! { + @def + sup: $h, + sub: $sub, + } + )* + + // Push big type to list of smaller types + def_index_from! { + @run + sup: [$($t),*], + sub: [$($sub,)* $h], + } + }; + // Expand to `impl`s + ( + @def + sup: $sup:ty, + sub: $sub:ty, + ) => { + impl From<$sub> for Index { + #[inline] + fn from(value: $sub) -> Self { + Index(value as $sup) + } + } + + impl From> for Index { + #[inline] + fn from(value: Index) -> Self { + Index(value.0 as $sup) + } + } + + impl From> for Index { + #[inline] + fn from(value: Unsigned) -> Self { + Index(value.0 as $sup) + } + } + + impl From> for Index { + #[inline] + fn from(value: Signed) -> Self { + Index(value.0 as $sup) + } + } + impl FromAs<$sub> for Index { + #[inline] + fn from_as(value: $sub) -> Self { + Index(value as $sup) + } + } + + impl FromAs> for Index { + #[inline] + fn from_as(value: Index) -> Self { + Index(value.0 as $sup) + } + } + + impl FromAs> for Index { + #[inline] + fn from_as(value: Unsigned) -> Self { + Index(value.0 as $sup) + } + } + + impl FromAs> for Index { + #[inline] + fn from_as(value: Signed) -> Self { + Index(value.0 as $sup) + } + } + } +} + +def_index_from!(u8, u16, u32, u64, u128); + +#[cfg(target_pointer_width = "16")] +def_index_from! { + @run + sup: [usize], + sub: [u8], +} + +#[cfg(target_pointer_width = "32")] +def_index_from! { + @run + sup: [usize], + sub: [u8, u16], +} + +#[cfg(target_pointer_width = "64")] +def_index_from! { + @run + sup: [usize], + sub: [u8, u16, u32], +} + +subst_macros::repeat_parallel_subst! { + groups: [ + [group [sub [INTO] = [u8]]] + [group [sub [INTO] = [u16]]] + [group [sub [INTO] = [u32]]] + [group [sub [INTO] = [u64]]] + [group [sub [INTO] = [u128]]] + [group [sub [INTO] = [usize]]] + ], + callback: NONE, + in: { + impl From> for INTO + where + Index: IndexSizeCheck, + INTO: From, + { + #[inline] + fn from(other: Index) -> INTO { + INTO::from(other.0) + } + } + + impl FromAs> for INTO + where + Index: IndexSizeCheck, + INTO: FromAs, + { + #[inline] + fn from_as(other: Index) -> INTO { + INTO::from_as(other.0) + } + } + } +} diff --git a/firmware-support/bittide-hal/src/manual_additions/mod.rs b/firmware-support/bittide-hal/src/manual_additions/mod.rs index 49ff0a090..28445dc14 100644 --- a/firmware-support/bittide-hal/src/manual_additions/mod.rs +++ b/firmware-support/bittide-hal/src/manual_additions/mod.rs @@ -2,15 +2,21 @@ // // SPDX-License-Identifier: Apache-2.0 +pub mod bitvector; pub mod calendar; pub mod capture_ugn; pub mod dna; pub mod elastic_buffer; +pub mod index; pub mod scatter_gather_pe; pub mod si539x_spi; +pub mod signed; pub mod soft_ugn_demo_mu; pub mod timer; pub mod uart; +pub mod unsigned; + +use crate::types::Maybe; // Sealing the `FromAs` and `IntoAs` traits with a supertrait mod seal { @@ -38,6 +44,30 @@ mod seal { } } +pub trait ConvertOptional { + fn conv_optional(self) -> Target; +} + +impl ConvertOptional> for Maybe { + #[inline] + fn conv_optional(self) -> Option { + match self { + Maybe::Just(val) => Some(val), + Maybe::Nothing => None, + } + } +} + +impl ConvertOptional> for Option { + #[inline] + fn conv_optional(self) -> Maybe { + match self { + Some(val) => Maybe::Just(val), + None => Maybe::Nothing, + } + } +} + /// Trait-level guarantee that `self as U` is valid for `self: T` pub trait FromAs: seal::Seal { fn from_as(other: T) -> Self; diff --git a/firmware-support/bittide-hal/src/manual_additions/scatter_gather_pe/scatter_gather.rs b/firmware-support/bittide-hal/src/manual_additions/scatter_gather_pe/scatter_gather.rs index 652bc59a0..321e5b25a 100644 --- a/firmware-support/bittide-hal/src/manual_additions/scatter_gather_pe/scatter_gather.rs +++ b/firmware-support/bittide-hal/src/manual_additions/scatter_gather_pe/scatter_gather.rs @@ -1,7 +1,10 @@ // SPDX-FileCopyrightText: 2025 Google LLC // // SPDX-License-Identifier: Apache-2.0 -use crate::hals::scatter_gather_pe::devices::{GatherUnit, ScatterUnit}; +use crate::{ + hals::scatter_gather_pe::devices::{GatherUnit, ScatterUnit}, + manual_additions::bitvector::BitVector, +}; impl GatherUnit { /// Write a slice to the gather memory. @@ -15,7 +18,7 @@ impl GatherUnit { let mut off = offset; for &val in src { unsafe { - self.set_gather_memory_unchecked(off, val); + self.set_gather_memory_unchecked(off, BitVector::new_unchecked(val)); } off += 1; } @@ -43,7 +46,7 @@ impl ScatterUnit { let mut off = offset; for val in dst { unsafe { - *val = self.scatter_memory_unchecked(off); + *val = self.scatter_memory_unchecked(off).into_inner(); } off += 1; } diff --git a/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs b/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs index 31a657fd4..fba5ed3bc 100644 --- a/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs +++ b/firmware-support/bittide-hal/src/manual_additions/si539x_spi.rs @@ -1,12 +1,11 @@ // SPDX-FileCopyrightText: 2025 Google LLC // // SPDX-License-Identifier: Apache-2.0 -use crate::manual_additions::timer::Duration; -use crate::shared_devices::Si539xSpi; -use crate::shared_devices::Timer; -use crate::types::Maybe::{Just, Nothing}; -use crate::types::RegisterOperation; - +use crate::{ + manual_additions::{bitvector::BitVector, timer::Duration, ConvertOptional}, + shared_devices::{Si539xSpi, Timer}, + types::{Maybe::Nothing, RegisterOperation}, +}; use ufmt::derive::uDebug; pub struct Config { @@ -26,9 +25,9 @@ pub struct ConfigEntry { impl Into for ConfigEntry { fn into(self) -> RegisterOperation { RegisterOperation { - page: [self.page], - address: [self.address], - write: Just([self.data]), + page: BitVector::new([self.page]).unwrap(), + address: BitVector::new([self.address]).unwrap(), + write: BitVector::new([self.data]).conv_optional(), } } } @@ -150,8 +149,8 @@ impl Si539xSpi { /// Perform a read operation. pub fn read(&self, page: u8, address: u8) -> u8 { let read_op = RegisterOperation { - page: [page], - address: [address], + page: BitVector::new([page]).unwrap(), + address: BitVector::new([address]).unwrap(), write: Nothing, }; self.set_register_operation(read_op); @@ -159,7 +158,7 @@ impl Si539xSpi { while self.commit() { continue; } - self.read_data()[0] + self.read_data().into_inner()[0] } /// Perform a write operation. diff --git a/firmware-support/bittide-hal/src/manual_additions/signed.rs b/firmware-support/bittide-hal/src/manual_additions/signed.rs new file mode 100644 index 000000000..5de55dd5e --- /dev/null +++ b/firmware-support/bittide-hal/src/manual_additions/signed.rs @@ -0,0 +1,570 @@ +// SPDX-FileCopyrightText: 2026 Google LLC +// +// SPDX-License-Identifier: Apache-2.0 +//! Types, traits, and implementations for signed types +//! +//! # Note +//! +//! The way that this crate constrains [`Signed`]s to the correct size is by ensuring that +//! evaulation of a constant succeeds. As such, when writing `impl`s on [`Signed`], users +//! should make sure to force evaluation of [`SignedSizeCheck::SIZE_CHECK`]. +//! +//! Alternatively, one can make use of `bittide_macros::Signed!` and `bittide_macros::signed!` to +//! make types/instances with the correct parameters. +#![deny(missing_docs)] + +use super::{index::Index, unsigned::Unsigned, FromAs}; + +/// Type representing a signed value of `N` bits, backed by the smallest useable signed type +/// +/// This is equivalent to a [`packC`'d][bpc] [`Signed n`][sgn] from Clash. This type should be +/// backed by the smallest signed integer of `N` bits or more. For example, a `Signed<7, _>` should +/// be `Signed<7, i8>` and `Signed<65, _>` should be `Signed<65, i128>`. +/// +/// [bpc]: https://github.com/QBayLogic/clash-protocols-memmap/blob/main/clash-bitpackc/src/Clash/Class/BitPackC.hs#L39-L44 +/// [sgn]: https://hackage-content.haskell.org/package/clash-prelude-1.8.4/docs/Clash-Sized-Signed.html#t:Signed +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Signed(pub(crate) T); + +/// Trait for making guarantees about the size and validity of signed types +pub trait SignedSizeCheck { + /// The correct size of the backing type in bits + const CORRECT_SIZE: u8; + /// This `const` should be instantiated in methods implemented on [`Signed`], since it is + /// how they are constrained to the correct size. If they're improperly sized, this `const` will + /// fail to evaluate and produce a compile error. + /// + /// # Examples + /// + /// Signed types with an appropriate backing type will compile successfully: + /// ``` + /// # use bittide_hal::manual_additions::signed::{Signed, SignedSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Signed<3, i8> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Signed<14, i16> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Signed<26, i32> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Signed<43, i64> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Signed<97, i128> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// But signed types with improperly sized backing types will produce an error. For example, + /// when the backing type is too small: + /// ```compile_fail + /// # use bittide_hal::manual_additions::signed::{Signed, SignedSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Signed<15, i8> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// This produces the error + /// ```text + /// error[E0080]: evaluation of ` as SignedSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/signed.rs + /// | + /// | impl_usc!(i8, i16, i32, i64, i128); + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Specified bit size `15` is too large for backing type `i8` + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` which comes from the expansion of the macro `impl_usc` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/signed.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + /// Or when the backing type is too large: + /// ```compile_fail + /// # use bittide_hal::manual_additions::signed::{Signed, SignedSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Signed<15, i32> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// This produces the error + /// ```text + /// error[E0080]: evaluation of ` as SignedSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/signed.rs + /// | + /// | impl_usc!(i8, i16, i32, i64, i128); + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Type `i32` is not optimally sized for bound 15. Use type `i16` instead. + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` which comes from the expansion of the macro `impl_usc` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/signed.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + /// Or when requesting a signed 0 bit number: + /// ```compile_fail + /// # use bittide_hal::manual_additions::signed::{Signed, SignedSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Signed<0, i32> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// ```text + /// error[E0080]: evaluation of ` as SignedSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/signed.rs + /// | + /// | impl_usc!(i8, i16, i32, i64, i128); + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Cannot represent Signed<0, T>! + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` which comes from the expansion of the macro `impl_usc` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/signed.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + const SIZE_CHECK: (); + /// Backing type of a signed number + type Inner: Copy; + /// Perform a bounds check on an instance of a signed number. Returns `true` if within bounds, + /// `false` if not. + fn inner_bounds_check(val: Self::Inner) -> bool; +} + +macro_rules! impl_snc { + ($($t:ty | $ut:ty),+$(,)?) => { + $( + impl SignedSizeCheck for Signed { + const CORRECT_SIZE: u8 = { + match N.div_ceil(8).next_power_of_two() * 8 { + size if size >= 8 => size, + _ => 8, + } + }; + const SIZE_CHECK: () = { + if N < 2 { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Cannot represent Signed<", N, ", T>!", + ); + } + if N > <$t>::BITS as u8 { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Specified bit size `", + N, + "` is too large for backing type `", + stringify!($t), + "`", + ); + } else if Self::CORRECT_SIZE != <$t>::BITS as u8 { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Type `", + stringify!($t), + "` is not optimally sized for bound ", + N, + ". Use type `i", + Self::CORRECT_SIZE, + "` instead.", + ); + } + }; + type Inner = $t; + #[inline] + fn inner_bounds_check(val: $t) -> bool { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + if const { Self::CORRECT_SIZE == N } { + true + } else if val.is_negative() { + // val >= const { 1 << (N - 1) } + val > const { + let ut: $ut = !0; + ut.unbounded_shl(N as u32) as $t + } + } else { + // val <= const { !(!0 << (N - 1)) } + val < const { + let t: $t = 1; + t.unbounded_shl(N as u32 - 1) + } + } + } + } + )+ + }; +} + +impl_snc!(i8 | u8, i16 | u16, i32 | u32, i64 | u64, i128 | u128); + +impl Signed +where + T: Copy, + Signed: SignedSizeCheck, +{ + /// Instantiate a new `Signed` + /// + /// Returns `Some(_)` if `val` fits within `N` bits, returns `None` otherwise. + /// + /// # Errors + /// + /// This function may error if invoked as `Signed::::new(...)` and the wrong backing type + /// `T` is chosen. Please read the whole error message carefully, it should tell you what + /// to do to fix it. + #[inline] + pub fn new(val: T) -> Option> { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + if Signed::::inner_bounds_check(val) { + Some(Signed(val)) + } else { + None + } + } + + /// Instantiate a new `Signed` without performing a bounds check + /// + /// # Errors + /// + /// This function may error if invoked as `Signed::::new_unchecked(...)` and the wrong + /// backing type `T` is chosen. Please read the whole error message carefully, it should tell + /// you what to do to fix it. + /// + /// # Safety + /// + /// Due to the intended use-case of interfacing with Clash hardware, and there being no + /// guarantee of behaviour in the case that an out-of-bounds value is written to a register, + /// this function has been marked as `unsafe`. To make calling this function safe, you must + /// guarantee that `val` is in the range `-2.pow(N - 1)..2.pow(N - 1)`. + #[inline] + pub unsafe fn new_unchecked(val: T) -> Signed { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + Signed(val) + } + + /// Unwrap the inner value contained by this `Signed` + #[inline] + pub fn into_inner(self) -> T { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + self.0 + } +} + +impl core::fmt::Debug for Signed +where + Self: SignedSizeCheck, + T: core::fmt::Debug, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Signed<{N}>({:?})", self.0) + } +} + +impl core::fmt::Display for Signed +where + Self: SignedSizeCheck, + T: core::fmt::Display, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl ufmt::uDebug for Signed +where + Self: SignedSizeCheck, + T: ufmt::uDebug, +{ + #[inline] + fn fmt(&self, fmt: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uwrite!(fmt, "Signed<{}>({:?})", N, self.0) + } +} + +impl ufmt::uDisplay for Signed +where + Self: SignedSizeCheck, + T: ufmt::uDisplay, +{ + #[inline] + fn fmt(&self, fmt: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uwrite!(fmt, "{}", self.0) + } +} + +impl ufmt::uDisplayHex for Signed +where + Self: SignedSizeCheck, + T: ufmt::uDisplayHex, +{ + #[inline] + fn fmt_hex( + &self, + fmt: &mut ufmt::Formatter<'_, W>, + options: ufmt::HexOptions, + ) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ::fmt_hex(&self.0, fmt, options) + } +} + +impl super::seal::Seal for Signed where T: super::seal::Seal {} + +/// Trait that guarantees parts of the [`Signed`] inherent implementations +/// +/// Intended for use where it is not desirable to constrain a type to be `T = Signed` but +/// rather to `T: SignedInterface`. +pub trait SignedInterface: Sized + SignedSizeCheck + super::seal::Seal { + /// Instantiate a new [`Signed`] + /// + /// Returns `Some(_)` if `val` fits within `N` bits, returns `None` otherwise. + /// + /// # Errors + /// + /// This function may error if invoked as `Signed::::sgn_new(...)` and the wrong backing + /// type `T` is chosen. Please read the whole error message carefully, it should tell you what + /// to do to fix it. + fn sgn_new(val: Self::Inner) -> Option; + /// Instantiate a new [`Signed`] without performing a bounds check + /// + /// # Errors + /// + /// This function may error if invoked as `Signed::::sgn_new_unchecked(...)` and the wrong + /// backing type `T` is chosen. Please read the whole error message carefully, it should tell + /// you what to do to fix it. + /// + /// # Safety + /// + /// Due to the intended use-case of interfacing with Clash hardware, and there being no + /// guarantee of behaviour in the case that an out-of-bounds value is written to a register, + /// this function has been marked as `unsafe`. To make calling this function safe, you must + /// guarantee that `val` is in the range `-2.pow(N - 1)..2.pow(N - 1)`. + unsafe fn sgn_new_unchecked(val: Self::Inner) -> Self; + /// Unwrap the inner value contained by this [`Signed`] + fn sgn_into_inner(self) -> Self::Inner; +} + +impl SignedInterface for Signed +where + T: Copy + super::seal::Seal, + Signed: SignedSizeCheck, +{ + #[inline] + fn sgn_new(val: Self::Inner) -> Option { + Self::new(val) + } + + #[inline] + unsafe fn sgn_new_unchecked(val: Self::Inner) -> Self { + Self::new_unchecked(val) + } + + #[inline] + fn sgn_into_inner(self) -> Self::Inner { + self.into_inner() + } +} + +macro_rules! def_signed_from { + // Entry + ($($t:ty),+$(,)?) => { + def_signed_from! { + @run + sup: [$($t),+], + sub: [], + } + }; + // Base case + ( + @run + sup: [], + sub: $sub:tt, + ) => {}; + // Recursive case + ( + @run + sup: [$h:ty$(, $t:ty)*], + sub: [$($sub:ty),*], + ) => { + // Expand defs for current head of big type + $( // for $sub in $($sub),*: + def_signed_from! { + @def + sup: $h, + sub: $sub, + } + )* + + // Push big type to list of smaller types + def_signed_from! { + @run + sup: [$($t),*], + sub: [$($sub,)* $h], + } + }; + // Expand to `impl`s + ( + @def + sup: $sup:ty, + sub: $sub:ty, + ) => { + impl From<$sub> for Signed { + #[inline] + fn from(value: $sub) -> Self { + Signed(value as $sup) + } + } + + impl From> for Signed { + #[inline] + fn from(value: Signed) -> Self { + Signed(value.0 as $sup) + } + } + + impl From> for Signed { + #[inline] + fn from(value: Index) -> Self { + Signed(value.0 as $sup) + } + } + + impl From> for Signed { + #[inline] + fn from(value: Unsigned) -> Self { + Signed(value.0 as $sup) + } + } + impl FromAs<$sub> for Signed { + fn from_as(value: $sub) -> Self { + Signed(value as $sup) + } + } + + impl FromAs> for Signed { + #[inline] + fn from_as(value: Signed) -> Self { + Signed(value.0 as $sup) + } + } + + impl FromAs> for Signed { + #[inline] + fn from_as(value: Index) -> Self { + Signed(value.0 as $sup) + } + } + + impl FromAs> for Signed { + #[inline] + fn from_as(value: Unsigned) -> Self { + Signed(value.0 as $sup) + } + } + } +} + +def_signed_from!(i8, i16, i32, i64, i128); + +#[cfg(target_pointer_width = "16")] +def_signed_from! { + @run + sup: [isize], + sub: [i8], +} + +#[cfg(target_pointer_width = "32")] +def_signed_from! { + @run + sup: [isize], + sub: [i8, i16], +} + +#[cfg(target_pointer_width = "64")] +def_signed_from! { + @run + sup: [isize], + sub: [i8, i16, i32], +} + +subst_macros::repeat_parallel_subst! { + groups: [ + [group [sub [INTO] = [i8]]] + [group [sub [INTO] = [i16]]] + [group [sub [INTO] = [i32]]] + [group [sub [INTO] = [i64]]] + [group [sub [INTO] = [i128]]] + [group [sub [INTO] = [isize]]] + ], + callback: NONE, + in: { + impl From> for INTO + where + Signed: SignedSizeCheck, + INTO: From, + { + #[inline] + fn from(other: Signed) -> INTO { + INTO::from(other.0) + } + } + + impl FromAs> for INTO + where + Signed: SignedSizeCheck, + INTO: FromAs, + { + #[inline] + fn from_as(other: Signed) -> INTO { + INTO::from_as(other.0) + } + } + } +} diff --git a/firmware-support/bittide-hal/src/manual_additions/soft_ugn_demo_mu/scatter_gather.rs b/firmware-support/bittide-hal/src/manual_additions/soft_ugn_demo_mu/scatter_gather.rs index 2862ede62..c965ddb31 100644 --- a/firmware-support/bittide-hal/src/manual_additions/soft_ugn_demo_mu/scatter_gather.rs +++ b/firmware-support/bittide-hal/src/manual_additions/soft_ugn_demo_mu/scatter_gather.rs @@ -1,7 +1,10 @@ // SPDX-FileCopyrightText: 2025 Google LLC // // SPDX-License-Identifier: Apache-2.0 -use crate::soft_ugn_demo_mu::devices::{GatherUnit, ScatterUnit}; +use crate::{ + manual_additions::bitvector::BitVector, + soft_ugn_demo_mu::devices::{GatherUnit, ScatterUnit}, +}; impl GatherUnit { /// Write a slice to the gather memory. @@ -15,7 +18,7 @@ impl GatherUnit { let mut off = offset; for &val in src { unsafe { - self.set_gather_memory_unchecked(off, val); + self.set_gather_memory_unchecked(off, BitVector::new_unchecked(val)); } off += 1; } @@ -43,7 +46,7 @@ impl ScatterUnit { let mut off = offset; for val in dst { unsafe { - *val = self.scatter_memory_unchecked(off); + *val = self.scatter_memory_unchecked(off).into_inner(); } off += 1; } diff --git a/firmware-support/bittide-hal/src/manual_additions/timer.rs b/firmware-support/bittide-hal/src/manual_additions/timer.rs index 736f2fe5d..c3e725367 100644 --- a/firmware-support/bittide-hal/src/manual_additions/timer.rs +++ b/firmware-support/bittide-hal/src/manual_additions/timer.rs @@ -13,9 +13,9 @@ insignificant for timing purposes. - [`Clock`] Manages time and provides utility methods for waiting and updating time. */ -use crate::shared_devices::Timer; -use crate::types::TimeCmd; +use crate::{manual_additions::unsigned::Unsigned, shared_devices::Timer, types::TimeCmd}; +use bittide_macros::Unsigned; use core::cmp; use core::fmt; use core::ops; @@ -292,7 +292,7 @@ impl Timer { /// /// Returns: /// - The current value of the time counter as a `u64`. - fn get_counter(&self) -> u64 { + fn get_counter(&self) -> Unsigned!(64) { self.freeze(); self.scratchpad() } @@ -301,30 +301,30 @@ impl Timer { pub fn now(&self) -> Instant { let cycles = self.get_counter(); let frequency = self.frequency(); - Instant::from_cycles(cycles, frequency) + Instant::from_cycles(cycles.into(), frequency.into()) } /// Wait for a `Duration` without stalling. pub fn wait(&self, duration: Duration) { - let now = self.get_counter(); - let cycles = duration.cycles(self.frequency()); + let now = self.get_counter().into_inner(); + let cycles = duration.cycles(self.frequency().into_inner()); let _ = self.wait_until_raw(now + cycles); } /// Wait until we have passed an `Instant`. #[must_use] pub fn wait_until(&self, target: Instant) -> WaitResult { - let target_cycles = target.get_cycles(self.frequency()); + let target_cycles = target.get_cycles(self.frequency().into()); self.wait_until_raw(target_cycles) } #[must_use] pub fn wait_until_raw(&self, target: u64) -> WaitResult { - let now = self.get_counter(); + let now = self.get_counter().into_inner(); if now > target { return WaitResult::AlreadyPassed; } - self.set_scratchpad(target); + self.set_scratchpad(Unsigned::new(target).unwrap()); while !self.cmp_result() { continue; } @@ -333,24 +333,24 @@ impl Timer { /// Stall the CPU until the comparison set by `timer_cmp` is `True`. pub fn wait_stall(&self, duration: Duration) { - let now = self.get_counter(); - let duration = duration.cycles(self.frequency()); + let now = self.get_counter().into_inner(); + let duration = duration.cycles(self.frequency().into()); let _ = self.wait_until_stall_raw(now + duration); } #[must_use] pub fn wait_until_stall(&self, target: Instant) -> WaitResult { - let target_cycles = target.get_cycles(self.frequency()); + let target_cycles = target.get_cycles(self.frequency().into_inner()); self.wait_until_stall_raw(target_cycles) } #[must_use] pub fn wait_until_stall_raw(&self, target: u64) -> WaitResult { - let now = self.get_counter(); + let now = self.get_counter().into_inner(); if now > target { return WaitResult::AlreadyPassed; } - self.set_scratchpad(target); + self.set_scratchpad(Unsigned::new(target).unwrap()); self.wait_for_cmp(); WaitResult::Success } diff --git a/firmware-support/bittide-hal/src/manual_additions/timer/self_test.rs b/firmware-support/bittide-hal/src/manual_additions/timer/self_test.rs index 669e107b8..d19e714f9 100644 --- a/firmware-support/bittide-hal/src/manual_additions/timer/self_test.rs +++ b/firmware-support/bittide-hal/src/manual_additions/timer/self_test.rs @@ -75,7 +75,7 @@ pub fn self_test(timer: Timer) -> impl Iterator TestReturn { - let frequency = timer.frequency(); + let frequency = timer.frequency().into_inner(); let now = timer.now(); if now == Instant::from_cycles(0, frequency) { Some(( @@ -89,7 +89,7 @@ pub fn now_not_null(timer: Timer) -> TestReturn { /// Read the frequency value, check if it's not 0. pub fn freq_not_null(timer: Timer) -> TestReturn { - let frequency: u64 = timer.frequency(); + let frequency: u64 = timer.frequency().into_inner(); if frequency == 0 { Some(("freq_not_null test failed: frequency is null", None)) } else { diff --git a/firmware-support/bittide-hal/src/manual_additions/uart.rs b/firmware-support/bittide-hal/src/manual_additions/uart.rs index 0ccc00d87..58614d19c 100644 --- a/firmware-support/bittide-hal/src/manual_additions/uart.rs +++ b/firmware-support/bittide-hal/src/manual_additions/uart.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -use crate::shared_devices::uart::Uart; +use crate::{manual_additions::bitvector::BitVector, shared_devices::uart::Uart}; pub struct UartStatus { pub receive_buffer_empty: bool, @@ -65,7 +65,7 @@ impl Uart { if self.read_status().transmit_buffer_full { Err(TransmitBufferFull) } else { - self.set_data([data]); + self.set_data(BitVector::new([data]).unwrap()); Ok(()) } } diff --git a/firmware-support/bittide-hal/src/manual_additions/unsigned.rs b/firmware-support/bittide-hal/src/manual_additions/unsigned.rs new file mode 100644 index 000000000..d177ec724 --- /dev/null +++ b/firmware-support/bittide-hal/src/manual_additions/unsigned.rs @@ -0,0 +1,565 @@ +// SPDX-FileCopyrightText: 2026 Google LLC +// +// SPDX-License-Identifier: Apache-2.0 +//! Types, traits, and implementations for unsigned types +//! +//! # Note +//! +//! The way that this crate constrains [`Unsigned`]s to the correct size is by ensuring that +//! evaulation of a constant succeeds. As such, when writing `impl`s on [`Unsigned`], users +//! should make sure to force evaluation of [`UnsignedSizeCheck::SIZE_CHECK`]. +//! +//! Alternatively, one can make use of `bittide_macros::Unsigned!` and `bittide_macros::unsigned!` +//! to make types/instances with the correct parameters. +#![deny(missing_docs)] + +use super::{index::Index, signed::Signed, FromAs}; + +/// Type representing an unsigned value of `N` bits, backed by the smallest useable unsigned type +/// +/// This is equivalent to a [`packC`'d][bpc] [`Unsigned n`][usn] from Clash. This type should be +/// backed by the smallest signed integer of `N` bits or more. For example, a `Unsigned<7, _>` should +/// be `Unsigned<7, u8>` and `Unsigned<65, _>` should be `Unsigned<65, u128>`. +/// +/// [bpc]: https://github.com/QBayLogic/clash-protocols-memmap/blob/main/clash-bitpackc/src/Clash/Class/BitPackC.hs#L39-L44 +/// [usn]: https://hackage-content.haskell.org/package/clash-prelude-1.8.4/docs/Clash-Sized-Unsigned.html#t:Unsigned +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Unsigned(pub(crate) T); + +/// Trait for making guarantees about the size and validity of unsigned types +pub trait UnsignedSizeCheck { + /// The correct size of the backing type in bits + const CORRECT_SIZE: u8; + /// Generic parameter to the unsigned type exposed as a `u32` + const BITS: u32; + /// This `const` should be instantiated in methods implemented on [`Unsigned`], since it is + /// how they are constrained to the correct size. If they're improperly sized, this `const` will + /// fail to evaluate and produce a compile error. + /// + /// # Examples + /// + /// Unsigned types with an appropriate backing type will compile successfully: + /// ``` + /// # use bittide_hal::manual_additions::unsigned::{Unsigned, UnsignedSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Unsigned<3, u8> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Unsigned<14, u16> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Unsigned<26, u32> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Unsigned<43, u64> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// + /// impl Marker for Unsigned<97, u128> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// But unsigned types with improperly sized backing types will produce an error. For example, + /// when the backing type is too small: + /// ```compile_fail + /// # use bittide_hal::manual_additions::unsigned::{Unsigned, UnsignedSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Unsigned<15, u8> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// This produces the error + /// ```text + /// error[E0080]: evaluation of ` as UnsignedSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/unsigned.rs + /// | + /// | impl_usc!(u8, u16, u32, u64, u128); + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Specified bit size `15` is too large for backing type `u8` + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` which comes from the expansion of the macro `impl_usc` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/unsigned.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + /// Or when the backing type is too large: + /// ```compile_fail + /// # use bittide_hal::manual_additions::unsigned::{Unsigned, UnsignedSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Unsigned<15, u32> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// This produces the error + /// ```text + /// error[E0080]: evaluation of ` as UnsignedSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/unsigned.rs + /// | + /// | impl_usc!(u8, u16, u32, u64, u128); + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Type `u32` is not optimally sized for bound 15. Use type `u16` instead. + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` which comes from the expansion of the macro `impl_usc` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/unsigned.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + /// Or when requesting a unsigned 0 bit number: + /// ```compile_fail + /// # use bittide_hal::manual_additions::unsigned::{Unsigned, UnsignedSizeCheck}; + /// trait Marker { + /// fn nothing(); + /// } + /// + /// impl Marker for Unsigned<0, u32> { + /// fn nothing() { + /// let _ = Self::SIZE_CHECK; + /// } + /// } + /// ``` + /// ```text + /// error[E0080]: evaluation of ` as UnsignedSizeCheck>::SIZE_CHECK` failed + /// --> manual_additions/unsigned.rs + /// | + /// | impl_usc!(u8, u16, u32, u64, u128); + /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: Cannot represent Unsigned<0, T>! + /// | + /// = note: this error originates in the macro `const_panic::concat_panic` which comes from the expansion of the macro `impl_usc` (in Nightly builds, run with -Z macro-backtrace for more info) + /// + /// note: erroneous constant encountered + /// --> manual_additions/unsigned.rs + /// | + /// | let _ = Self::SIZE_CHECK; + /// | ^^^^^^^^^^^^^^^^ + /// ``` + const SIZE_CHECK: (); + /// Backing type of this unsigned number + type Inner: Copy; + /// Perform a bounds check on an instance of an unsigned number. Returns `true` if within + /// bounds, `false` if not. + fn inner_bounds_check(val: Self::Inner) -> bool; +} + +/// Type alias to access the backing type of an unsigned number +pub type UnsignedInner = ::Inner; + +macro_rules! impl_usc { + ($($t:ty),+$(,)?) => { + $( + impl UnsignedSizeCheck for Unsigned { + const CORRECT_SIZE: u8 = { + match N.div_ceil(8).next_power_of_two() * 8 { + size if size >= 8 => size, + _ => 8, + } + }; + const BITS: u32 = N as u32; + const SIZE_CHECK: () = { + if N == 0 { + panic!("Cannot represent Unsigned<0, T>!"); + } + if N > <$t>::BITS as u8 { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Specified bit size `", + N, + "` is too large for backing type `", + stringify!($t), + "`", + ); + } else if Self::CORRECT_SIZE != <$t>::BITS as u8 { + const_panic::concat_panic!( + const_panic::fmt::FmtArg::DISPLAY; + "Type `", + stringify!($t), + "` is not optimally sized for bound ", + N, + ". Use type `u", + Self::CORRECT_SIZE, + "` instead.", + ); + } + }; + type Inner = $t; + #[inline] + fn inner_bounds_check(val: $t) -> bool { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + if const { Self::CORRECT_SIZE == N } { + true + } else { + val < const { 1 << (N - 1) } + } + } + } + )+ + }; +} + +impl_usc!(u8, u16, u32, u64, u128); + +impl Unsigned +where + T: Copy, + Unsigned: UnsignedSizeCheck, +{ + /// Instantiate a new `Unsigned` + /// + /// Returns `Some(_)` if `val` fits within `N` bits, returns `None` otherwise. + /// + /// # Errors + /// + /// This function may error if invoked as `Unsigned::::new(...)` and the wrong backing + /// type `T` is chosen. Please read the whole error message carefully, it should tell you what + /// to do to fix it. + #[inline] + pub fn new(val: T) -> Option> { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + if Unsigned::::inner_bounds_check(val) { + Some(Unsigned(val)) + } else { + None + } + } + + /// Instantiate a new `Unsigned` without performing a bounds check + /// + /// # Errors + /// + /// This function may error if invoked as `Unsigned::::new_unchecked(...)` and the wrong + /// backing type `T` is chosen. Please read the whole error message carefully, it should tell + /// you what to do to fix it. + /// + /// # Safety + /// + /// Due to the intended use-case of interfacing with Clash hardware, and there being no + /// guarantee of behaviour in the case that an out-of-bounds value is written to a register, + /// this function has been marked as `unsafe`. To make calling this function safe, you must + /// guarantee that `val` is in the range `0..2.pow(N)`. + #[inline] + pub unsafe fn new_unchecked(val: T) -> Unsigned { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + Unsigned(val) + } + + /// Unwrap the inner value contained by this `Unsigned` + #[inline] + pub fn into_inner(self) -> T { + let _: () = Self::SIZE_CHECK; // READ THE REST OF THE ERROR MESSAGE + self.0 + } +} + +impl core::fmt::Debug for Unsigned +where + Self: UnsignedSizeCheck, + T: core::fmt::Debug, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Unsigned<{N}>({:?})", self.0) + } +} + +impl core::fmt::Display for Unsigned +where + Self: UnsignedSizeCheck, + T: core::fmt::Display, +{ + #[inline] + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl ufmt::uDebug for Unsigned +where + Self: UnsignedSizeCheck, + T: ufmt::uDebug, +{ + #[inline] + fn fmt(&self, fmt: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uwrite!(fmt, "Unsigned<{}>({:?})", N, self.0) + } +} + +impl ufmt::uDisplay for Unsigned +where + Self: UnsignedSizeCheck, + T: ufmt::uDisplay, +{ + #[inline] + fn fmt(&self, fmt: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uwrite!(fmt, "{}", self.0) + } +} + +impl ufmt::uDisplayHex for Unsigned +where + Self: UnsignedSizeCheck, + T: ufmt::uDisplayHex, +{ + #[inline] + fn fmt_hex( + &self, + fmt: &mut ufmt::Formatter<'_, W>, + options: ufmt::HexOptions, + ) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ::fmt_hex(&self.0, fmt, options) + } +} + +impl super::seal::Seal for Unsigned where T: super::seal::Seal {} + +/// Trait that guarantees parts of the [`Unsigned`] inherent implementations +/// +/// Intended for use where it is not desirable to constrain a type to be `T = Unsigned` but +/// rather to `T: UnsignedInterface`. +pub trait UnsignedInterface: Sized + UnsignedSizeCheck + super::seal::Seal { + /// Instantiate a new [`Unsigned`] + /// + /// Returns `Some(_)` if `val` fits within `N` bits, returns `None` otherwise. + /// + /// # Errors + /// + /// This function may error if invoked as `Unsigned::::uns_new(...)` and the wrong backing + /// type `T` is chosen. Please read the whole error message carefully, it should tell you what + /// to do to fix it. + fn uns_new(val: Self::Inner) -> Option; + /// Instantiate a new [`Unsigned`] without performing a bounds check + /// + /// # Errors + /// + /// This function may error if invoked as `Unsigned::::uns_new_unchecked(...)` and the + /// wrong backing type `T` is chosen. Please read the whole error message carefully, it should + /// tell you what to do to fix it. + /// + /// # Safety + /// + /// Due to the intended use-case of interfacing with Clash hardware, and there being no + /// guarantee of behaviour in the case that an out-of-bounds value is written to a register, + /// this function has been marked as `unsafe`. To make calling this function safe, you must + /// guarantee that `val` is in the range `0..2.pow(N)`. + unsafe fn uns_new_unchecked(val: Self::Inner) -> Self; + /// Unwrap the inner value contained by this [`Unsigned`] + fn uns_into_inner(self) -> Self::Inner; +} + +impl UnsignedInterface for Unsigned +where + T: Copy + super::seal::Seal, + Unsigned: UnsignedSizeCheck, +{ + #[inline] + fn uns_new(val: Self::Inner) -> Option { + Self::new(val) + } + + #[inline] + unsafe fn uns_new_unchecked(val: Self::Inner) -> Self { + Self::new_unchecked(val) + } + + #[inline] + fn uns_into_inner(self) -> Self::Inner { + self.into_inner() + } +} + +macro_rules! def_unsigned_from { + // Entry + ($($t:ty),+$(,)?) => { + def_unsigned_from! { + @run + sup: [$($t),+], + sub: [], + } + }; + // Base case + ( + @run + sup: [], + sub: $sub:tt, + ) => {}; + // Recursive case + ( + @run + sup: [$h:ty$(, $t:ty)*], + sub: [$($sub:ty),*], + ) => { + // Expand defs for current head of big type + $( // for $sub in $($sub),*: + def_unsigned_from! { + @def + sup: $h, + sub: $sub, + } + )* + + // Push big type to list of smaller types + def_unsigned_from! { + @run + sup: [$($t),*], + sub: [$($sub,)* $h], + } + }; + // Expand to `impl`s + ( + @def + sup: $sup:ty, + sub: $sub:ty, + ) => { + impl From<$sub> for Unsigned { + #[inline] + fn from(value: $sub) -> Self { + Unsigned(value as $sup) + } + } + + impl From> for Unsigned { + #[inline] + fn from(value: Unsigned) -> Self { + Unsigned(value.0 as $sup) + } + } + + impl From> for Unsigned { + #[inline] + fn from(value: Index) -> Self { + Unsigned(value.0 as $sup) + } + } + + impl From> for Unsigned { + #[inline] + fn from(value: Signed) -> Self { + Unsigned(value.0 as $sup) + } + } + + impl FromAs<$sub> for Unsigned { + #[inline] + fn from_as(value: $sub) -> Self { + Unsigned(value as $sup) + } + } + + impl FromAs> for Unsigned { + #[inline] + fn from_as(value: Unsigned) -> Self { + Unsigned(value.0 as $sup) + } + } + + impl FromAs> for Unsigned { + #[inline] + fn from_as(value: Index) -> Self { + Unsigned(value.0 as $sup) + } + } + + impl FromAs> for Unsigned { + #[inline] + fn from_as(value: Signed) -> Self { + Unsigned(value.0 as $sup) + } + } + } +} + +def_unsigned_from!(u8, u16, u32, u64, u128); + +#[cfg(target_pointer_width = "16")] +def_unsigned_from! { + @run + sup: [usize], + sub: [u8], +} + +#[cfg(target_pointer_width = "32")] +def_unsigned_from! { + @run + sup: [usize], + sub: [u8, u16], +} + +#[cfg(target_pointer_width = "64")] +def_unsigned_from! { + @run + sup: [usize], + sub: [u8, u16, u32], +} + +subst_macros::repeat_parallel_subst! { + groups: [ + [group [sub [INTO] = [u8]]] + [group [sub [INTO] = [u16]]] + [group [sub [INTO] = [u32]]] + [group [sub [INTO] = [u64]]] + [group [sub [INTO] = [u128]]] + [group [sub [INTO] = [usize]]] + ], + callback: NONE, + in: { + impl From> for INTO + where + Unsigned: UnsignedSizeCheck, + INTO: From, + { + #[inline] + fn from(other: Unsigned) -> INTO { + INTO::from(other.0) + } + } + + impl FromAs> for INTO + where + Unsigned: UnsignedSizeCheck, + INTO: FromAs, + { + #[inline] + fn from_as(other: Unsigned) -> INTO { + INTO::from_as(other.0) + } + } + } +} diff --git a/firmware-support/bittide-macros/Cargo.toml b/firmware-support/bittide-macros/Cargo.toml index 5b8e8f7e4..76c0ceac8 100644 --- a/firmware-support/bittide-macros/Cargo.toml +++ b/firmware-support/bittide-macros/Cargo.toml @@ -11,5 +11,5 @@ proc-macro = true [dependencies] proc-macro2 = "*" -syn = "2.0" +syn = {version = "2.0", features = ["full"]} quote = "1.0" diff --git a/firmware-support/bittide-macros/src/lib.rs b/firmware-support/bittide-macros/src/lib.rs index 7b7df1b42..fac6887d6 100644 --- a/firmware-support/bittide-macros/src/lib.rs +++ b/firmware-support/bittide-macros/src/lib.rs @@ -6,7 +6,7 @@ mod clock_config; use clock_config::{ClockConfigParser, ConfigEntry, ParserState}; use quote::quote; -use std::fs; +use std::{fmt::Write, fs}; use syn::{LitInt, LitStr, Token, parse::Parse, parse_macro_input}; /// Macro to conveniently create an indexed type @@ -25,12 +25,9 @@ use syn::{LitInt, LitStr, Token, parse::Parse, parse_macro_input}; pub fn Index(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { let n_lit = parse_macro_input!(toks as LitInt); - fn next_pow2(n: &u128) -> u128 { - n.next_power_of_two().max(8) - } - - fn clog2(n: &u128) -> u128 { - n.ilog2().max(8) as u128 + fn get_correct_size(n: u128) -> u32 { + let clog2 = (u128::BITS - (n - 1).leading_zeros()).next_power_of_two(); + clog2.max(8) } let n_val = match n_lit.base10_parse() { @@ -39,12 +36,12 @@ pub fn Index(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { }; let ty = { - let type_name = format!("u{}", next_pow2(&clog2(&n_val))); + let type_name = format!("u{}", get_correct_size(n_val)); syn::Ident::new(&type_name, n_lit.span()) }; quote! { - bittide_hal::manual_additions::index::IndexTy<#n_lit, #ty> + Index<#n_lit, #ty> } .into() } @@ -70,16 +67,13 @@ pub fn index(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { let val = args.value; let n_lit = args.n_lit; - fn next_pow2(n: &u128) -> u128 { - n.next_power_of_two().max(8) - } - - fn clog2(n: &u128) -> u128 { - n.ilog2().max(8) as u128 + fn get_correct_size(n: u128) -> u32 { + let clog2 = (u128::BITS - (n - 1).leading_zeros()).next_power_of_two(); + clog2.max(8) } let ty = { - let type_name = format!("u{}", next_pow2(&clog2(&args.n_val))); + let type_name = format!("u{}", get_correct_size(args.n_val)); syn::Ident::new(&type_name, n_lit.span()) }; @@ -97,7 +91,9 @@ pub fn index(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { if n < args.n_val { quote! { unsafe { - bittide_hal::manual_additions::index::IndexTy::<#n_lit, #ty>::new_unchecked(#int_lit) + Index::<#n_lit, #ty>::new_unchecked( + #int_lit + ) } } .into() @@ -116,7 +112,7 @@ pub fn index(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { } // non literal case, do slow bounds check _ => quote! { - bittide_hal::manual_additions::index::IndexTy::<#n_lit, #ty>::new(#val).unwrap() + Index::<#n_lit, #ty>::new(#val).unwrap() } .into(), } @@ -151,6 +147,392 @@ impl Parse for IndexValArgs { } } +#[proc_macro] +#[allow(non_snake_case)] +pub fn Signed(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { + let n_lit = parse_macro_input!(toks as LitInt); + + fn get_correct_size(n: u8) -> u8 { + (n.div_ceil(8).next_power_of_two() * 8).max(8) + } + + let n_val = match n_lit.base10_parse() { + Err(err) => return err.into_compile_error().into(), + Ok(v) => v, + }; + + let ty = { + let type_name = format!("i{}", get_correct_size(n_val)); + syn::Ident::new(&type_name, n_lit.span()) + }; + + quote! { + crate::manual_additions::signed::Signed<#n_lit, #ty> + } + .into() +} + +#[proc_macro] +pub fn signed(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(toks as NumericArgs); + + let val = args.value; + let nlit = args.n_lit; + let nval = args.n_val; + + fn get_correct_size(n: u8) -> u8 { + (n.div_ceil(8).next_power_of_two() * 8).max(8) + } + + let ty = { + let type_name = format!("i{}", get_correct_size(nval)); + syn::Ident::new(&type_name, nlit.span()) + }; + + let uty = { + let type_name = format!("u{}", get_correct_size(nval)); + syn::Ident::new(&type_name, nlit.span()) + }; + + match val { + // try to detect a constant literal to skip the bounds check + syn::Expr::Lit(syn::ExprLit { + attrs: _, + lit: syn::Lit::Int(int_lit), + }) => { + let litval: i128 = match int_lit.base10_parse::() { + Err(err) => return err.into_compile_error().into(), + Ok(v) => v as i128, + }; + let lit_is_neg = litval & (1 << (nval - 1)) != 0; + let litval = if lit_is_neg { + litval | (!0 << (nval - 1)) + } else { + litval + }; + let neg_bound = !0 << (nval - 1); + let pos_bound = !(!0 << (nval - 1)); + if (neg_bound..=pos_bound).contains(&litval) { + if lit_is_neg { + quote! { + unsafe { + Signed::<#nlit, #ty>::new_unchecked(const { + const LIT: #uty = #int_lit; + LIT as #ty | (!0 << (#nval - 1)) + }) + } + } + } else { + quote! { + unsafe { + Signed::<#nlit, #ty>::new_unchecked(const { + const LIT: #uty = #int_lit; + LIT as #ty + }) + } + } + } + .into() + } else { + let bounds = neg_bound..=pos_bound; + syn::Error::new( + int_lit.span().join(nlit.span()).unwrap_or(int_lit.span()), + format!( + "Value not in {bounds:?}: value `{litval}`, type Signed<{nlit}, {nval}>" + ), + ) + .into_compile_error() + .into() + } + } + _ => quote! { + Signed::<#nlit, #ty>::new(#val).expect(concat!( + "Failed to construct `Signed<", + stringify!(#nlit), + ", ", + stringify!(#ty), + ">` at line ", + line!(), + ", column ", + column!(), + )) + } + .into(), + } +} + +#[proc_macro] +#[allow(non_snake_case)] +pub fn Unsigned(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { + let n_lit = parse_macro_input!(toks as LitInt); + + fn get_correct_size(n: u8) -> u8 { + (n.div_ceil(8).next_power_of_two() * 8).max(8) + } + + let n_val = match n_lit.base10_parse() { + Err(err) => return err.into_compile_error().into(), + Ok(v) => v, + }; + + let ty = { + let type_name = format!("u{}", get_correct_size(n_val)); + syn::Ident::new(&type_name, n_lit.span()) + }; + + quote! { + Unsigned<#n_lit, #ty> + } + .into() +} + +#[proc_macro] +pub fn unsigned(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(toks as NumericArgs); + + let val = args.value; + let nlit = args.n_lit; + let nval = args.n_val; + + fn get_correct_size(n: u8) -> u8 { + (n.div_ceil(8).next_power_of_two() * 8).max(8) + } + + let ty = { + let type_name = format!("u{}", get_correct_size(nval)); + syn::Ident::new(&type_name, nlit.span()) + }; + + match val { + // try to detect a constant literal to skip the bounds check + syn::Expr::Lit(syn::ExprLit { + attrs: _, + lit: syn::Lit::Int(int_lit), + }) => { + let litval: u128 = match int_lit.base10_parse() { + Err(err) => return err.into_compile_error().into(), + Ok(v) => v, + }; + if nval == 128 || (nval < 128 && (0..(1u128 << nval)).contains(&litval)) { + quote! { + unsafe { + Unsigned::<#nlit, #ty>::new_unchecked( + #int_lit + ) + } + } + .into() + } else { + syn::Error::new( + int_lit.span().join(nlit.span()).unwrap(), + format!("Value out of bounds: value `{litval}`, type Unsigned<{nlit}, {nval}>"), + ) + .into_compile_error() + .into() + } + } + _ => quote! { + Unsigned::<#nlit, #ty>::new(#val).expect(concat!( + "Failed to construct `Unigned<", + stringify!(#nlit), + ", ", + stringify!(#ty), + ">` at line ", + line!(), + ", column ", + column!(), + )) + } + .into(), + } +} + +struct NumericArgs { + value: syn::Expr, + n_lit: syn::LitInt, + n_val: u8, +} + +impl Parse for NumericArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let value = input.parse()?; + input.parse::()?; + let n_ident = input.parse::()?; + if n_ident != "n" { + return Err(syn::Error::new( + n_ident.span(), + "only `n` is allowed here. Example: `n = 20`", + )); + } + input.parse::()?; + let n_lit: syn::LitInt = input.parse()?; + let n_val = n_lit.base10_parse()?; + if n_val > 128 { + Err(syn::Error::new( + n_lit.span(), + format!("Value `{n_val}` is outside of allowed range 0..=128!"), + )) + } else { + Ok(NumericArgs { + value, + n_lit, + n_val, + }) + } + } +} + +#[proc_macro] +#[allow(non_snake_case)] +pub fn BitVector(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { + let n_lit = parse_macro_input!(toks as LitInt); + + let n_val: usize = match n_lit.base10_parse() { + Err(err) => return err.into_compile_error().into(), + Ok(v) => v, + }; + + let size = n_val.div_ceil(8); + + quote! { + BitVector<#n_val, #size> + } + .into() +} + +#[proc_macro] +pub fn bitvector(toks: proc_macro::TokenStream) -> proc_macro::TokenStream { + let BitVectorArgs { + value, + n_lit, + n_val, + } = parse_macro_input!(toks as BitVectorArgs); + + let size = n_val.div_ceil(8); + + match value { + syn::Expr::Lit(syn::ExprLit { + attrs: _, + lit: syn::Lit::Int(input), + }) => { + let input_str = input.to_string(); + if !(input_str.starts_with("0x") || input_str.starts_with("0X")) + || input_str + .chars() + .skip(2) + .any(|c| !(c.is_ascii_hexdigit() || c == ' ' || c == '_')) + { + return syn::Error::new( + input.span(), + concat!( + "Expected a hexadecimal literal! Must start with `0x` or `0X` and contain ", + "only hexdigits, spaces, and underscores", + ) + .to_string(), + ) + .into_compile_error() + .into(); + } + let input_str = input_str + .trim_start_matches('0') + .trim_start_matches(['x', 'X']); + let mut buf: Vec = vec![0; size]; + let mut char_iter = input_str.chars().filter(char::is_ascii_hexdigit).rev(); + let mut lower = true; + let mut idx = 0; + loop { + let Some(c) = char_iter.next() else { + break; + }; + let val = match c { + '0'..='9' => c as u8 - b'0', + 'A'..='F' => c as u8 - b'A' + 10, + 'a'..='f' => c as u8 - b'a' + 10, + _ => unreachable!(), + }; + match (buf.get_mut(idx), lower) { + (None, _) => { + let rem = char_iter.filter(|c| c.is_ascii_hexdigit()).count(); + return syn::Error::new( + input.span(), + format!( + "Literal too long! Expected 0..={size} hexdigits, but {rem} left" + ), + ) + .into_compile_error() + .into(); + } + (Some(byte), true) => *byte |= val, + (Some(byte), false) => *byte |= val << 4, + } + if !lower { + idx += 1; + } + lower = !lower; + } + let check_bit = n_val % 8; + if check_bit == 0 || buf.last().unwrap().leading_zeros() as usize >= (8 - check_bit) { + let mut array_str = String::with_capacity(buf.len() * 6); + array_str.push('['); + for byte in buf { + write!(array_str, "0x{byte:02x}, ").unwrap(); + } + array_str.pop(); + array_str.pop(); + array_str.push(']'); + let toks: proc_macro2::TokenStream = array_str.parse().unwrap(); + quote! { + unsafe { + BitVector::<#n_lit, #size>::new_unchecked( + #toks + ) + } + } + .into() + } else { + syn::Error::new( + input.span(), + format!("Set bits outside allowed range: {:0nb$b}", 0, nb = 1), + ) + .into_compile_error() + .into() + } + } + _ => quote! { + BitVector::<#n_lit, #size>::new(#value).unwrap() + } + .into(), + } +} + +struct BitVectorArgs { + value: syn::Expr, + n_lit: syn::LitInt, + n_val: usize, +} + +impl Parse for BitVectorArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let value = input.parse()?; + input.parse::()?; + let n_ident = input.parse::()?; + if n_ident != "n" { + return Err(syn::Error::new( + n_ident.span(), + "only `n` is allowed here. Example: `n = 20`", + )); + } + input.parse::()?; + let n_lit: syn::LitInt = input.parse()?; + let n_val = n_lit.base10_parse()?; + Ok(BitVectorArgs { + value, + n_lit, + n_val, + }) + } +} + // /// Macro to read a CSV file with a clock configuration into a usable format. #[proc_macro] // #[allow(non_snake_case)] diff --git a/firmware-support/bittide-sys/src/callisto.rs b/firmware-support/bittide-sys/src/callisto.rs index ddfe1b791..3e1b02538 100644 --- a/firmware-support/bittide-sys/src/callisto.rs +++ b/firmware-support/bittide-sys/src/callisto.rs @@ -2,10 +2,11 @@ // // SPDX-License-Identifier: Apache-2.0 -use bittide_hal::shared_devices::clock_control::ClockControl; -use bittide_hal::types::callisto_config::CallistoConfig; -use bittide_hal::types::maybe::Maybe; -use bittide_hal::types::speed_change::SpeedChange; +use bittide_hal::{ + manual_additions::IntoAs, + shared_devices::clock_control::ClockControl, + types::{callisto_config::CallistoConfig, maybe::Maybe, speed_change::SpeedChange}, +}; /// Rust sibling of /// `Bittide.ClockControl.Callisto.Types.Stability`. @@ -83,7 +84,7 @@ impl Callisto { self.accumulated_speed_requests += speed_change_to_sign(speed_request); if let Maybe::Just(wait_time) = self.config.wait_time { - self.update_reframe_state(wait_time as usize, cc.links_stable()[0] != 0, c_des); + self.update_reframe_state(wait_time.into_as(), cc.links_stable()[0] != 0, c_des); } speed_request diff --git a/firmware-support/bittide-sys/src/sample_store.rs b/firmware-support/bittide-sys/src/sample_store.rs index b3b4cbef3..2431f4dbd 100644 --- a/firmware-support/bittide-sys/src/sample_store.rs +++ b/firmware-support/bittide-sys/src/sample_store.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 +use bittide_hal::manual_additions::bitvector::BitVector; use bittide_hal::shared_devices::freeze::Freeze; use bittide_hal::shared_devices::sample_memory::SampleMemory; @@ -19,7 +20,8 @@ pub struct SampleStore { impl SampleStore { pub fn new(memory: SampleMemory, store_samples_every: usize) -> Self { // First memory location is reserved for the number of samples stored. - memory.set_data(0, 0u32.to_le_bytes()); + // memory.set_data(0, BitVector::new(0u32.to_le_bytes()).unwrap()); + memory.set_data(0, unsafe { BitVector::new_unchecked([0; 4]) }); Self { memory, @@ -37,49 +39,69 @@ impl SampleStore { stability: Stability, net_speed_change: i32, ) { - let n_samples_stored: usize = u32::from_ne_bytes(self.memory.data(0).unwrap()) as usize; + let n_samples_stored: usize = + u32::from_ne_bytes(self.memory.data(0).unwrap().into_inner()) as usize; let start_index = n_samples_stored * WORDS_PER_SAMPLE + 1; // Store local clock counter - let local_clock: u64 = freeze.local_clock_counter(); + let local_clock: u64 = freeze.local_clock_counter().into_inner(); let local_clock_msbs = (local_clock >> 32) as u32; let local_clock_lsbs = (local_clock & 0xFFFFFFFF) as u32; - self.memory - .set_data(start_index, local_clock_lsbs.to_le_bytes()); - self.memory - .set_data(start_index + 1, local_clock_msbs.to_le_bytes()); + self.memory.set_data( + start_index, + // BitVector::new(local_clock_lsbs.to_le_bytes()).unwrap(), + unsafe { BitVector::new_unchecked(local_clock_lsbs.to_le_bytes()) }, + ); + self.memory.set_data( + start_index + 1, + // BitVector::new(local_clock_msbs.to_le_bytes()).unwrap(), + unsafe { BitVector::new_unchecked(local_clock_msbs.to_le_bytes()) }, + ); // Store number of sync pulses seen let number_of_sync_pulses_seen = freeze.number_of_sync_pulses_seen(); self.memory - .set_data(start_index + 2, number_of_sync_pulses_seen.to_le_bytes()); + .set_data(start_index + 2, number_of_sync_pulses_seen.into()); // Store cycles since last sync pulse let cycles_since_sync_pulse = freeze.cycles_since_sync_pulse(); self.memory - .set_data(start_index + 3, cycles_since_sync_pulse.to_le_bytes()); + .set_data(start_index + 3, cycles_since_sync_pulse.into()); // Store stability information self.memory.set_data( start_index + 4, - (stability.stable as u32 | ((stability.settled as u32) << 8)).to_le_bytes(), + // BitVector::new( + // (stability.stable as u32 | ((stability.settled as u32) << 8)).to_le_bytes(), + // ) + // .unwrap(), + unsafe { + BitVector::new_unchecked( + (stability.stable as u32 | ((stability.settled as u32) << 8)).to_le_bytes(), + ) + }, ); // Store net speed change - self.memory - .set_data(start_index + 5, (net_speed_change as u32).to_le_bytes()); + self.memory.set_data( + start_index + 5, + // BitVector::new((net_speed_change as u32).to_le_bytes()).unwrap(), + unsafe { BitVector::new_unchecked((net_speed_change as u32).to_le_bytes()) }, + ); // Store the EB counters for (i, eb_counter) in freeze.eb_counters_volatile_iter().enumerate() { - self.memory - .set_data(start_index + 6 + i, (eb_counter as u32).to_le_bytes()); + self.memory.set_data(start_index + 6 + i, eb_counter.into()); } // Bump number of samples stored, but only if we're running "for real" // and the data actually fits in memory. if bump_counter && self.memory.data(start_index + WORDS_PER_SAMPLE).is_some() { - self.memory - .set_data(0, ((n_samples_stored + 1) as u32).to_le_bytes()); + self.memory.set_data( + 0, + // BitVector::new(((n_samples_stored + 1) as u32).to_le_bytes()).unwrap(), + unsafe { BitVector::new_unchecked(((n_samples_stored + 1) as u32).to_le_bytes()) }, + ); } } diff --git a/firmware-support/bittide-sys/src/stability_detector.rs b/firmware-support/bittide-sys/src/stability_detector.rs index 0eed3b3af..04753d0ee 100644 --- a/firmware-support/bittide-sys/src/stability_detector.rs +++ b/firmware-support/bittide-sys/src/stability_detector.rs @@ -3,7 +3,10 @@ // SPDX-License-Identifier: Apache-2.0 use bittide_hal::{ - manual_additions::timer::{Duration, Instant}, + manual_additions::{ + bitvector::BitVector, + timer::{Duration, Instant}, + }, shared_devices::clock_control::ClockControl, }; use itertools::izip; @@ -79,8 +82,8 @@ impl StabilityDetector { .enumerate() { let active = test_bit(link_mask_rev[0], i); - let diff0 = data_count_stored.abs_diff(min_seen); - let diff1 = data_count_stored.abs_diff(max_seen); + let diff0 = data_count_stored.abs_diff(min_seen.into_inner()); + let diff1 = data_count_stored.abs_diff(max_seen.into_inner()); let height_violated = diff0 > self.margin || diff1 > self.margin; let (stable, settled) = if height_violated || !active { @@ -88,7 +91,7 @@ impl StabilityDetector { // link is inactive. The latter prevents inactive links from // being considered stable. *maybe_start = Some(now); - *data_count_stored = data_count; + *data_count_stored = data_count.into_inner(); cc.set_clear_data_counts_seen(true); (false, false) } else { @@ -110,7 +113,7 @@ impl StabilityDetector { // if it happened to stabilize very close to its margins and, just // by "luck", the next sample pushes it over the edge. if !*prev_stable && stable { - *data_count_stored = data_count; + *data_count_stored = data_count.into_inner(); cc.set_clear_data_counts_seen(true); } *prev_stable = stable; @@ -119,8 +122,8 @@ impl StabilityDetector { // XXX: These values are currently hardcoded to 8 bits, which would // break if we ever have more than 8 links. We'll cross that bridge // when we get there. - cc.set_links_settled([settleds as u8]); - cc.set_links_stable([stables as u8]); + cc.set_links_settled(unsafe { BitVector::new_unchecked([settleds as u8 & 0x7f]) }); + cc.set_links_stable(unsafe { BitVector::new_unchecked([stables as u8 & 0x7f]) }); Stability { stable: stables as u8, diff --git a/firmware-support/memmap-generate/Cargo.toml b/firmware-support/memmap-generate/Cargo.toml index 6b257ca78..c6e4f4875 100644 --- a/firmware-support/memmap-generate/Cargo.toml +++ b/firmware-support/memmap-generate/Cargo.toml @@ -10,10 +10,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -quote = "1.0" -proc-macro2 = "1.0" +# bittide-macros = { path = "../bittide-macros" } derivative = "2.2.0" heck = "0.5.0" +proc-macro2 = "1.0" +quote = "1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" smallvec = { version = "1.15", features = ["write"] } diff --git a/firmware-support/memmap-generate/src/backends/rust/mod.rs b/firmware-support/memmap-generate/src/backends/rust/mod.rs index a08512832..e07af3eae 100644 --- a/firmware-support/memmap-generate/src/backends/rust/mod.rs +++ b/firmware-support/memmap-generate/src/backends/rust/mod.rs @@ -77,6 +77,10 @@ pub fn generate_type_desc<'ir>( ) -> (&'ir str, proc_macro2::TokenStream, TypeReferences) { let mut refs = TypeReferences { references: BTreeSet::new(), + use_bitvec: false, + use_index: false, + use_signed: false, + use_unsigned: false, }; let desc = &ctx.type_descs[handle]; let type_name = &ctx.type_names[desc.name]; @@ -427,6 +431,10 @@ pub fn generate_device_desc<'ir>( ) -> (&'ir str, proc_macro2::TokenStream, TypeReferences) { let mut refs = TypeReferences { references: BTreeSet::new(), + use_bitvec: false, + use_index: false, + use_signed: false, + use_unsigned: false, }; let desc = &ctx.device_descs[handle]; let name = &ctx.identifiers[desc.name]; @@ -661,6 +669,10 @@ fn generate_reg_set_method( /// Types referenced during code generation. pub struct TypeReferences { pub references: BTreeSet>, + pub use_bitvec: bool, + pub use_index: bool, + pub use_signed: bool, + pub use_unsigned: bool, } fn generate_type_ref( @@ -675,11 +687,11 @@ fn generate_type_ref( TypeRef::BitVector(handle) => { let size = &ctx.type_refs[lookup_sub(variant, *handle)]; - if let TypeRef::Nat(n) = size { - // TODO maybe special case on n == 1 like the C backend? - let n = n.div_ceil(8) as usize; - let n_lit = Literal::usize_unsuffixed(n); - quote! { [u8; #n_lit] } + if let &TypeRef::Nat(n) = size { + refs.use_bitvec = true; + let n = n as usize; + let len = n.div_ceil(8); + quote! { BitVector<#n, #len> } } else { quote! { compile_error!("BitVector with length not known after monomorphisation") } } @@ -687,10 +699,16 @@ fn generate_type_ref( TypeRef::Unsigned(handle) => { let size = &ctx.type_refs[lookup_sub(variant, *handle)]; - if let TypeRef::Nat(n) = size { - let n = po2_type(*n); - let name = format!("u{n}"); - ident(IdentType::Raw, name).into_token_stream() + if let &TypeRef::Nat(n) = size { + if n > 128 { + let msg = format!("Unsigned length {n} is outside of allowed range 0..=128!"); + quote! { compile_error!(#msg) } + } else { + refs.use_unsigned = true; + let n = (n as u8).div_ceil(8).next_power_of_two() * 8; + let backer = ident(IdentType::Raw, format!("u{n}")).into_token_stream(); + quote! { Unsigned<#n, #backer> } + } } else { quote! { compile_error!("Unsigned with length not known after monomorphisation") } } @@ -698,10 +716,16 @@ fn generate_type_ref( TypeRef::Signed(handle) => { let size = &ctx.type_refs[lookup_sub(variant, *handle)]; - if let TypeRef::Nat(n) = size { - let n = po2_type(*n); - let name = format!("i{n}"); - ident(IdentType::Raw, name).into_token_stream() + if let &TypeRef::Nat(n) = size { + if n > 128 { + let msg = format!("Signed length {n} is outside of allowed range 0..=128!"); + quote! { compile_error!(#msg) } + } else { + refs.use_signed = true; + let n = (n as u8).div_ceil(8).next_power_of_two() * 8; + let backer = ident(IdentType::Raw, format!("i{n}")).into_token_stream(); + quote! { Signed<#n, #backer> } + } } else { quote! { compile_error!("Signed with length not known after monomorphisation") } } @@ -710,9 +734,11 @@ fn generate_type_ref( let size = &ctx.type_refs[lookup_sub(variant, *handle)]; if let TypeRef::Nat(n) = size { - let n = po2_type(n.ilog2() as u64); - let name = format!("u{n}"); - ident(IdentType::Raw, name).into_token_stream() + refs.use_index = true; + let n = *n as u128; + let backer = + ident(IdentType::Raw, format!("u{}", index_size(n))).into_token_stream(); + quote! { Index<#n, #backer> } } else { quote! { compile_error!("Index with length not known after monomorphisation") } } @@ -903,3 +929,9 @@ fn type_to_ident(ctx: &IrCtx, ty: Handle) -> String { } } } + +fn index_size(n: u128) -> u32 { + (u128::BITS - (n - 1).leading_zeros()) + .next_power_of_two() + .max(8) +}