Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Version 2.0 #32

Draft
wants to merge 59 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
738218d
Switch FPGA API's to be dynamically loaded
ThadHouse Sep 2, 2023
13438c9
Finish up changes
ThadHouse Sep 2, 2023
b722aaf
Fix first round of review nits
ThadHouse Sep 2, 2023
3551844
Only have FFI code at the low level
ThadHouse Sep 2, 2023
0844fd3
Fix loading
ThadHouse Sep 2, 2023
c7c022c
Add HMB as an example of an optional api
ThadHouse Sep 2, 2023
ca4785b
Don't make general write functions mut
ThadHouse Sep 2, 2023
3f67a0b
Allow opening a non owning session
ThadHouse Sep 2, 2023
05f91f5
Add all enums
ThadHouse Sep 2, 2023
c06fc35
Some reformatting
ThadHouse Sep 2, 2023
33cdbd7
Use enums at high level
ThadHouse Sep 2, 2023
207b3a5
Add all FPGA APIs
ThadHouse Sep 3, 2023
bf0a476
Flesh out HMB API
ThadHouse Sep 3, 2023
841b9d8
Add an API to create from an existing session
ThadHouse Sep 3, 2023
3ea872b
Really fix up lifetimes this time
ThadHouse Sep 3, 2023
86376cd
Add typed register API
ThadHouse Sep 3, 2023
75f40e4
Use some lifetime hack to make alternate objects easier
ThadHouse Sep 3, 2023
3a18483
Use traits to access high level values
ThadHouse Sep 3, 2023
0c5d90b
Export high level types, so they can actually be typed out
ThadHouse Sep 3, 2023
d2895a2
Add accessors for ffi low level directly
ThadHouse Sep 3, 2023
725045c
Allow building without unstable features
ThadHouse Sep 3, 2023
ece6325
Massively improve lifetime holding for registers
ThadHouse Sep 3, 2023
96154d3
Remove missed file
ThadHouse Sep 4, 2023
0679c4d
Hide unsafe API a bit better, don't allow using register API for it
ThadHouse Sep 4, 2023
0f8d8a0
Make enums fixed types too
ThadHouse Sep 4, 2023
3447ae0
Use from for dlopen error coersion
ThadHouse Sep 6, 2023
0f4b378
Switch FPGA API's to be dynamically loaded
ThadHouse Sep 6, 2023
852453a
Merge branch 'master' into dlopenapi
ThadHouse Sep 7, 2023
92f85d4
Fixup running
ThadHouse Sep 7, 2023
e89e926
Fix integration tests
ThadHouse Sep 7, 2023
21cf98c
Merge master
ThadHouse Sep 8, 2023
3ecb57c
Make not require unsafe
ThadHouse Sep 8, 2023
85b3620
Fix session
ThadHouse Sep 8, 2023
1de06ef
Remove generated code
ThadHouse Sep 8, 2023
e528442
Remove unnecessary
ThadHouse Sep 8, 2023
02fc71d
Lots more cluster work
ThadHouse Sep 8, 2023
050b491
Delete fixed registers
ThadHouse Sep 8, 2023
f30d9d0
1 more miss
ThadHouse Sep 8, 2023
c9fc506
Add packed number
ThadHouse Sep 8, 2023
3d917b9
Add back in specializations
ThadHouse Sep 8, 2023
7856fa0
Finally found a way to specialize reads and writes
ThadHouse Sep 8, 2023
6911389
Use a builder API
ThadHouse Sep 9, 2023
16d0ea9
Fix enum macros
ThadHouse Sep 9, 2023
5fadba4
Fix lints
ThadHouse Sep 9, 2023
95ae402
Better specialization
ThadHouse Sep 9, 2023
ce8c0d2
Test and abstract out much stuff
ThadHouse Sep 9, 2023
84864ce
Split Register Read and Write
ThadHouse Sep 9, 2023
b42ce0a
Split read and write, make write unsafe
ThadHouse Sep 9, 2023
ff937b2
Remove unsafe
ThadHouse Sep 10, 2023
e993fc7
Work on generating a full high level API
ThadHouse Sep 10, 2023
ca8e10f
Lots more generation
ThadHouse Sep 10, 2023
eb44cbd
Everything needs to explicitly implement datatype
ThadHouse Sep 10, 2023
fa19b27
Add register transmute
ThadHouse Sep 10, 2023
684e231
Fix
ThadHouse Sep 10, 2023
5ec06b3
Add overflowing option for FXP
ThadHouse Sep 10, 2023
3892a05
Add raw HMB readers
ThadHouse Sep 12, 2023
82b2aba
Add interrupt manager
ThadHouse Sep 13, 2023
a80a72c
Export Interrupt Manager
ThadHouse Sep 13, 2023
cffa1d2
Export IRQ
ThadHouse Sep 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -46,3 +46,33 @@ jobs:
with:
command: clippy
args: -- -D warnings -A incomplete_features

build_stable:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Setup QEMU/binfmts
run:
sudo apt-get update &&
sudo apt-get install -y binfmt-support qemu qemu-user-static qemu-system-arm &&
sudo update-binfmts --enable
- name: Install latest stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt, clippy
default: true
- name: cross build
uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: --verbose --target=arm-unknown-linux-gnueabi --package example-arc
- name: cross test
uses: actions-rs/cargo@v1
with:
use-cross: true
command: test
args: --verbose --target=arm-unknown-linux-gnueabi --package ni-fpga
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"example",
"example-arc",
"integration",
"ni-fpga",
"ni-fpga-macros",
12 changes: 12 additions & 0 deletions example-arc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "example-arc"
version = "0.1.0"
authors = ["Thad House <thadhouse1>"]
edition = "2018"
publish = false

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
ni-fpga = { path = "../ni-fpga" }
ni-fpga-macros = { path = "../ni-fpga-macros" }
38 changes: 38 additions & 0 deletions example-arc/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::{thread, time::Duration};

use ni_fpga::RegisterRead;

use crate::registers::FpgaBitfile;

mod registers;

fn main() -> Result<(), ni_fpga::Error> {
let session = FpgaBitfile::session_builder("rio://172.22.11.2/RIO0")?.build_arc()?;
let mut regs = FpgaBitfile::take(&session).unwrap();

let frequency = regs.DutyCycle0_Frequency.take().unwrap();
let output = regs.DutyCycle0_HighTicks.take().unwrap();

let frequency1 = regs.DutyCycle1_Frequency.take().unwrap();

loop {
let o = output
.read(&session)?
.to_fxp()
.map(|f| f.to_int())
.unwrap_or(0);
let f = frequency
.read(&session)?
.to_fxp()
.map(|f| f.to_int())
.unwrap_or(0);
let f1 = frequency1
.read(&session)?
.to_fxp()
.map(|f| f.to_int())
.unwrap_or(-42);

println!("F: {f} O: {o} f1: {f1}");
thread::sleep(Duration::from_secs(1));
}
}
2,794 changes: 2,794 additions & 0 deletions example-arc/src/registers.rs

Large diffs are not rendered by default.

104,005 changes: 104,005 additions & 0 deletions example-arc/src/roboRIO_FPGA_2023_23.0.0.lvbitx

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions example/src/main.rs
Original file line number Diff line number Diff line change
@@ -28,15 +28,15 @@ enum SPIDebugState {
}

fn main() -> Result<(), ni_fpga::Error> {
let session = Session::open(
let _session = Session::open(
"/boot/user.lvbitx",
"264D0BA312FF00B741D4742415E1D470",
"RIO0",
)?;

println!("Input voltage: {:?}", session.read::<u16>(99174)?);
println!("{:#?}", session.read::<PWMConfig>(98536)?);
println!("{:#?}", session.read::<[AnalogTriggerOutput; 8]>(98424)?);
println!("{:#?}", session.read::<SPIDebugState>(99314)?);
// println!("Input voltage: {:?}", session.read::<u16>(99174)?);
// println!("{:#?}", session.read::<PWMConfig>(98536)?);
// println!("{:#?}", session.read::<[AnalogTriggerOutput; 8]>(98424)?);
// println!("{:#?}", session.read::<SPIDebugState>(99314)?);
Ok(())
}
2 changes: 1 addition & 1 deletion integration/Cargo.toml
Original file line number Diff line number Diff line change
@@ -11,4 +11,4 @@ publish = false
colored = "2.0.0"
ni-fpga = { path = "../ni-fpga" }
ni-fpga-macros = { path = "../ni-fpga-macros" }
tempfile = "3.1.0"
bitvec = "1.0.1"
152 changes: 91 additions & 61 deletions integration/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
use std::io::Write;
use bitvec::vec::BitVec;
use ni_fpga::{Error, RegisterRead, SessionAccess};

use colored::*;
use ni_fpga::fxp::{SignedFXP, UnsignedFXP};
use ni_fpga::Session;
use ni_fpga_macros::Cluster;
use tempfile::NamedTempFile;
use ni_fpga::Datatype;
use registers::FpgaBitfile;

#[derive(Cluster, Debug, PartialEq)]
struct TestCluster {
b: bool,
u: u16,
}
mod registers;

fn test_case<T: PartialEq + std::fmt::Debug>(test_case_name: &str, actual: T, expected: T) {
eprint!("{}...", test_case_name);
@@ -26,89 +22,123 @@ fn test_case<T: PartialEq + std::fmt::Debug>(test_case_name: &str, actual: T, ex
}
}

fn full_test_case<T: PartialEq + std::fmt::Debug + Datatype + Copy>(
session: &impl SessionAccess,
test_case_name: &str,
reg: impl RegisterRead<T>,
expected: T,
) -> Result<(), ni_fpga::Error> {
test_case(test_case_name, reg.read(session)?, expected);
round_trip_test(&expected)?;

Ok(())
}

fn round_trip_test<T: Datatype + PartialEq + std::fmt::Debug>(data: &T) -> Result<(), Error> {
let mut bv = BitVec::with_capacity(T::SIZE_IN_BITS);
unsafe { bv.set_len(T::SIZE_IN_BITS) }

let fpga_bits = bv.as_mut_bitslice();
T::pack(fpga_bits, data)?;
assert_eq!(T::unpack(fpga_bits)?, *data);
Ok(())
}

#[allow(overflowing_literals)]
fn main() -> Result<(), ni_fpga::Error> {
let mut tmp_bitfile = NamedTempFile::new().unwrap();
write!(tmp_bitfile, include_str!("integration.lvbitx")).unwrap();
let session = FpgaBitfile::session_builder("rio://172.22.11.2/RIO0")?.build()?;
let registers = FpgaBitfile::take(&session)?;

let session = Session::open(
tmp_bitfile.path().to_str().unwrap(),
"D08F17F77A45A5692FA2342C6B86E0EE",
"RIO0",
)?;

test_case("read plain U8", session.read::<u8>(98306)?, 0b00000001);
test_case(
full_test_case(&session, "read plain U8", registers.U8.unwrap(), 0b00000001)?;
full_test_case(
&session,
"read plain U16",
session.read::<u16>(98310)?,
registers.U16.unwrap(),
0b0000001100000001,
);
test_case(
)?;
full_test_case(
&session,
"read plain U32",
session.read::<u32>(98312)?,
registers.U32.unwrap(),
0b00001111000001110000001100000001,
);
test_case(
)?;
full_test_case(
&session,
"read plain U64",
session.read::<u64>(98316)?,
registers.U64.unwrap(),
0b1111111101111111001111110001111100001111000001110000001100000001,
);
)?;

test_case("read plain I8", session.read::<i8>(98322)?, 0b10000000);
test_case(
full_test_case(&session, "read plain I8", registers.I8.unwrap(), 0b10000000)?;
full_test_case(
&session,
"read plain I16",
session.read::<i16>(98326)?,
registers.I16.unwrap(),
0b1100000010000000,
);
test_case(
)?;
full_test_case(
&session,
"read plain I32",
session.read::<i32>(98328)?,
registers.I32.unwrap(),
0b11110000111000001100000010000000,
);
test_case(
)?;
full_test_case(
&session,
"read plain I64",
session.read::<i64>(98332)?,
registers.I64.unwrap(),
0b1111111111111110111111001111100011110000111000001100000010000000,
);
)?;

#[allow(clippy::approx_constant)]
test_case("read SGL", session.read::<f32>(98336)?, 3.14);
full_test_case(&session, "read SGL", registers.SGL.unwrap(), 3.14)?;

test_case(
full_test_case(
&session,
"read unsigned FXP",
session.read::<UnsignedFXP<4, 3>>(98342)?,
registers.UnsignedFXP.unwrap(),
UnsignedFXP::from_float(4.5)?,
);
test_case(
)?;
full_test_case(
&session,
"read unsigned FXP",
session.read::<SignedFXP<4, 3>>(98346)?,
registers.SignedFXP.unwrap(),
SignedFXP::from_float(-1.5)?,
);
)?;

test_case("read true bool", session.read::<bool>(98350)?, true);
test_case("read false bool", session.read::<bool>(98354)?, false);
test_case(
full_test_case(
&session,
"read true bool",
registers.TrueBool.unwrap(),
true,
)?;
full_test_case(
&session,
"read false bool",
registers.FalseBool.unwrap(),
false,
)?;
full_test_case(
&session,
"read bool array",
session.read::<[bool; 8]>(98358)?,
registers.BoolArray.unwrap(),
[true, false, true, false, true, false, true, false],
);
)?;

test_case(
full_test_case(
&session,
"read cluster",
session.read::<TestCluster>(98360)?,
TestCluster { b: false, u: 1337 },
);
// TODO: Investigate cluster array memory layout in order to fix this test.
// The expected array may be incorrect here, I don't exactly remember what I used for the
// fixture bitfile before my LabView FPGA trial expired.
test_case(
registers.TestCluster.unwrap(),
registers::types::TestCluster { b: false, u: 1337 },
)?;
full_test_case(
&session,
"read cluster array",
session.read::<[TestCluster; 2]>(98360)?,
registers.TestClusterArray.unwrap(),
[
TestCluster { b: true, u: 255 },
TestCluster { b: false, u: 1337 },
registers::types::TestClusterArray { b: true, u: 1234 },
registers::types::TestClusterArray { b: false, u: 5678 },
],
);
)?;

Ok(())
}
108 changes: 108 additions & 0 deletions integration/src/registers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#![allow(warnings)]

pub mod types {
use super::types;
use ni_fpga_macros::{Cluster, Enum};
#[derive(Cluster, Debug, PartialEq, Clone, Copy)]
pub struct TestCluster {
pub b: bool,
pub u: u16,
}
#[derive(Cluster, Debug, PartialEq, Clone, Copy)]
pub struct TestClusterArray {
pub b: bool,
pub u: u16,
}
}
pub struct FpgaBitfile {
pub U8: Option<ni_fpga::Register<u8, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub U16: Option<ni_fpga::Register<u16, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub U32: Option<ni_fpga::Register<u32, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub U64: Option<ni_fpga::Register<u64, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub I8: Option<ni_fpga::Register<i8, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub I16: Option<ni_fpga::Register<i16, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub I32: Option<ni_fpga::Register<i32, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub I64: Option<ni_fpga::Register<i64, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub SGL: Option<ni_fpga::Register<f32, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub UnsignedFXP: Option<
ni_fpga::Register<ni_fpga::fxp::FXP<4, 3, false>, ni_fpga::ReadOnly, ni_fpga::StoredOffset>,
>,
pub SignedFXP: Option<
ni_fpga::Register<ni_fpga::fxp::FXP<4, 3, true>, ni_fpga::ReadOnly, ni_fpga::StoredOffset>,
>,
pub TrueBool: Option<ni_fpga::Register<bool, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub FalseBool: Option<ni_fpga::Register<bool, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub BoolArray: Option<ni_fpga::Register<[bool; 8], ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub TestCluster:
Option<ni_fpga::Register<types::TestCluster, ni_fpga::ReadOnly, ni_fpga::StoredOffset>>,
pub TestClusterArray: Option<
ni_fpga::Register<[types::TestClusterArray; 2], ni_fpga::ReadOnly, ni_fpga::StoredOffset>,
>,
pub ViControl: Option<ni_fpga::Register<u32, ni_fpga::ReadWrite, ni_fpga::StoredOffset>>,
pub DiagramReset: Option<ni_fpga::Register<u32, ni_fpga::ReadWrite, ni_fpga::StoredOffset>>,
pub InterruptEnable: Option<ni_fpga::Register<u32, ni_fpga::ReadWrite, ni_fpga::StoredOffset>>,
pub InterruptMask: Option<ni_fpga::Register<u32, ni_fpga::ReadWrite, ni_fpga::StoredOffset>>,
pub InterruptStatus: Option<ni_fpga::Register<u32, ni_fpga::ReadWrite, ni_fpga::StoredOffset>>,
}
impl FpgaBitfile {
pub fn take(session: &impl ni_fpga::SessionAccess) -> Result<Self, ni_fpga::Error> {
static REGISTERS: std::sync::Mutex<Option<()>> = std::sync::Mutex::new(Some(()));
let mut lock = REGISTERS.lock().unwrap();
let contents = lock.take();
drop(lock);
if contents.is_none() {
return Err(ni_fpga::Error::ResourceAlreadyTaken);
}
Ok(Self {
U8: Some(unsafe { ni_fpga::Register::new(session.find_offset("U8")?) }),
U16: Some(unsafe { ni_fpga::Register::new(session.find_offset("U16")?) }),
U32: Some(unsafe { ni_fpga::Register::new(session.find_offset("U32")?) }),
U64: Some(unsafe { ni_fpga::Register::new(session.find_offset("U64")?) }),
I8: Some(unsafe { ni_fpga::Register::new(session.find_offset("I8")?) }),
I16: Some(unsafe { ni_fpga::Register::new(session.find_offset("I16")?) }),
I32: Some(unsafe { ni_fpga::Register::new(session.find_offset("I32")?) }),
I64: Some(unsafe { ni_fpga::Register::new(session.find_offset("I64")?) }),
SGL: Some(unsafe { ni_fpga::Register::new(session.find_offset("SGL")?) }),
UnsignedFXP: Some(unsafe {
ni_fpga::Register::new(session.find_offset("UnsignedFXP")?)
}),
SignedFXP: Some(unsafe { ni_fpga::Register::new(session.find_offset("SignedFXP")?) }),
TrueBool: Some(unsafe { ni_fpga::Register::new(session.find_offset("TrueBool")?) }),
FalseBool: Some(unsafe { ni_fpga::Register::new(session.find_offset("FalseBool")?) }),
BoolArray: Some(unsafe { ni_fpga::Register::new(session.find_offset("BoolArray")?) }),
TestCluster: Some(unsafe {
ni_fpga::Register::new(session.find_offset("TestCluster")?)
}),
TestClusterArray: Some(unsafe {
ni_fpga::Register::new(session.find_offset("TestClusterArray")?)
}),
ViControl: Some(unsafe { ni_fpga::Register::new(session.find_offset("ViControl")?) }),
DiagramReset: Some(unsafe {
ni_fpga::Register::new(session.find_offset("DiagramReset")?)
}),
InterruptEnable: Some(unsafe {
ni_fpga::Register::new(session.find_offset("InterruptEnable")?)
}),
InterruptMask: Some(unsafe {
ni_fpga::Register::new(session.find_offset("InterruptMask")?)
}),
InterruptStatus: Some(unsafe {
ni_fpga::Register::new(session.find_offset("InterruptStatus")?)
}),
})
}
pub const fn contents() -> &'static str {
include_str!("integration.lvbitx")
}
pub const fn signature() -> &'static str {
"D08F17F77A45A5692FA2342C6B86E0EE"
}
pub fn session_builder(
resource: impl AsRef<str>,
) -> Result<ni_fpga::SessionBuilder, ni_fpga::Error> {
ni_fpga::SessionBuilder::new()
.bitfile_contents(Self::contents())?
.signature(Self::signature())?
.resource(resource)
}
}
67 changes: 58 additions & 9 deletions ni-fpga-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -16,18 +16,18 @@ pub fn derive_cluster(item: TokenStream) -> TokenStream {
let struct_name = &input.ident;

let output = quote! {
impl ni_fpga::Datatype for #struct_name {
const SIZE_IN_BITS: usize = 0 #( + <#field_types as ni_fpga::Datatype>::SIZE_IN_BITS )*;
impl ni_fpga::DatatypePacker for #struct_name {
const SIZE_IN_BITS: usize = 0 #( + <#field_types as ni_fpga::DatatypePacker>::SIZE_IN_BITS )*;

fn pack(fpga_bits: &mut ni_fpga::FpgaBits, data: &Self) -> Result<(), ni_fpga::Error> {
let mut remaining_bits = fpga_bits;
#(
{
let (field_bits, other_remaining_bits) = remaining_bits.split_at_mut(
<#field_types_for_pack as ni_fpga::Datatype>::SIZE_IN_BITS
<#field_types_for_pack as ni_fpga::DatatypePacker>::SIZE_IN_BITS
);
remaining_bits = other_remaining_bits;
ni_fpga::Datatype::pack(field_bits, &data.#field_names_for_pack)?;
ni_fpga::DatatypePacker::pack(field_bits, &data.#field_names_for_pack)?;
}
)*
Ok(())
@@ -41,16 +41,33 @@ pub fn derive_cluster(item: TokenStream) -> TokenStream {
#(
#field_names_for_unpack: {
let (field_bits, other_remaining_bits) = remaining_bits.split_at(
<#field_types_for_unpack as ni_fpga::Datatype>::SIZE_IN_BITS
<#field_types_for_unpack as ni_fpga::DatatypePacker>::SIZE_IN_BITS
);
remaining_bits = other_remaining_bits;
ni_fpga::Datatype::unpack(field_bits)?
ni_fpga::DatatypePacker::unpack(field_bits)?
}
),*
},
)
}
}
impl ni_fpga::DerivedDatatype for #struct_name {}
impl ni_fpga::StockAccessDatatype for #struct_name {}

impl ni_fpga::Datatype for #struct_name
{
unsafe fn read(session: &impl ni_fpga::SessionAccess, offset: ni_fpga::Offset) -> Result<Self, ni_fpga::Error> {
<Self as ni_fpga::StockAccessDatatype>::read(session, offset)
}

unsafe fn write(
session: &impl ni_fpga::SessionAccess,
offset: ni_fpga::Offset,
value: impl std::borrow::Borrow<Self>,
) -> Result<(), ni_fpga::Error> {
<Self as ni_fpga::StockAccessDatatype>::write(session, offset, value)
}
}
};

output.into()
@@ -72,6 +89,7 @@ pub fn derive_enum(item: TokenStream) -> TokenStream {
64 => quote! {u64},
_ => unreachable!(),
});

let discriminants: Vec<_> = (0..input.variants.len())
.map(|discriminant| {
syn::LitInt::new(
@@ -88,27 +106,58 @@ pub fn derive_enum(item: TokenStream) -> TokenStream {
let enum_name = &input.ident;

let output = quote! {
impl ni_fpga::Datatype for #enum_name {
impl ni_fpga::DatatypePacker for #enum_name {
const SIZE_IN_BITS: usize = #backing_size;

fn pack(fpga_bits: &mut ni_fpga::FpgaBits, data: &Self) -> Result<(), ni_fpga::Error> {
match data {
#(
Self::#variants_for_pack => <#backing_type as ni_fpga::Datatype>::pack(fpga_bits, &#discriminants_for_pack)?
Self::#variants_for_pack => <#backing_type as ni_fpga::DatatypePacker>::pack(fpga_bits, &#discriminants_for_pack)?
),*
};
Ok(())
}

fn unpack(fpga_bits: &ni_fpga::FpgaBits) -> Result<Self, ni_fpga::Error> {
match <#backing_type as ni_fpga::Datatype>::unpack(fpga_bits)? {
match <#backing_type as ni_fpga::DatatypePacker>::unpack(fpga_bits)? {
#(
#discriminants_for_unpack => Ok(Self::#variants_for_unpack)
),*,
unknown => Err(ni_fpga::Error::InvalidEnumDiscriminant(unknown as u64)),
}
}
}

impl #enum_name {
pub fn from_backing(value: #backing_type) -> Result<Self, ni_fpga::Error> {
match value {
#(
#discriminants_for_unpack => Ok(Self::#variants_for_unpack)
),*,
unknown => Err(ni_fpga::Error::InvalidEnumDiscriminant(unknown as u64)),
}
}

fn into_backing(&self) -> #backing_type {
match self {
#(
#enum_name::#variants_for_pack => #discriminants_for_pack
),*
}
}
}

impl ni_fpga::Datatype for #enum_name {
unsafe fn read(session: &impl ni_fpga::SessionAccess, offset: ni_fpga::Offset) -> Result<Self, ni_fpga::Error> {
Self::from_backing(ni_fpga::Datatype::read(session, offset)?)
}

unsafe fn write(session: &impl ni_fpga::SessionAccess, offset: ni_fpga::Offset, value: impl std::borrow::Borrow<Self>) -> Result<(), ni_fpga::Error> {
<#backing_type as ni_fpga::Datatype>::write(session, offset, value.borrow().into_backing())
}
}

impl ni_fpga::DerivedDatatype for #enum_name {}
};

output.into()
5 changes: 1 addition & 4 deletions ni-fpga-sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -707,9 +707,6 @@ pub struct NiFpgaApi {

impl NiFpgaApi {
pub fn load() -> Result<NiFpgaApiContainer, Error> {
match unsafe { Container::load(platform_file_name("NiFpga")) } {
Ok(api) => Ok(api),
Err(err) => Err(err),
}
unsafe { Container::load(platform_file_name("NiFpga")) }
}
}
2 changes: 1 addition & 1 deletion ni-fpga/Cargo.toml
Original file line number Diff line number Diff line change
@@ -10,6 +10,6 @@ readme = "README.md"
repository = "https://github.com/first-rust-competition/ni-fpga-rs"

[dependencies]
bitvec = "0.17.4"
bitvec = "1.0.1"
ni-fpga-sys = { version = "1.0.1", path = "../ni-fpga-sys" }
thiserror = "1.0.19"
776 changes: 755 additions & 21 deletions ni-fpga/src/datatype.rs

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions ni-fpga/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::{ffi::NulError, num::TryFromIntError, sync::PoisonError};

use thiserror::Error;

use crate::status::Status;

#[derive(Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum Error {
#[allow(clippy::upper_case_acronyms)]
#[error("an FPGA operation failed: {0}")]
@@ -15,4 +18,42 @@ pub enum Error {
FixedPointRawOutOfBounds(u64, u8, u8, bool),
#[error("{0} cannot be precisely represented as FXP<{1}, {2}, {3}>`")]
FixedPointPrecision(f64, u8, u8, bool),
#[error("Cannot close an unowned fpga session")]
ClosingUnownedSession,
#[error("An invalid datatype was passed.")]
InvalidDatatype,
#[error("Null C String {0}")]
NullCString(NulError),
#[error("No bitfile specified")]
NoBitfileSpecified,
#[error("No signature specified")]
NoSignatureSpecified,
#[error("No resource specified")]
NoResourceSpecified,
#[error("Resource has already been taken")]
ResourceAlreadyTaken,
#[error("Register {0} not found")]
RegisterNotFound(String),
#[error("A mutex is poisoned {0}. This is unrecoverable")]
MutexPoisoned(String),
#[error("Timeout is too big to pass to the FPGA")]
TimeoutTooLong,
}

impl From<TryFromIntError> for Error {
fn from(_: TryFromIntError) -> Self {
Self::TimeoutTooLong
}
}

impl<T> From<PoisonError<T>> for Error {
fn from(value: PoisonError<T>) -> Self {
Self::MutexPoisoned(value.to_string())
}
}

impl From<NulError> for Error {
fn from(value: NulError) -> Self {
Self::NullCString(value)
}
}
510 changes: 487 additions & 23 deletions ni-fpga/src/fxp.rs

Large diffs are not rendered by default.

128 changes: 128 additions & 0 deletions ni-fpga/src/hmb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use std::{
ffi::{c_void, CString},
mem::size_of,
ops::Deref,
ptr,
};

use crate::{
nifpga::{NiFpga, StatusHelper},
session::SessionAccess,
Error, Session, Status,
};

struct VirtualAddressHandle(*mut c_void);

unsafe impl Send for VirtualAddressHandle {}
unsafe impl Sync for VirtualAddressHandle {}

pub struct HmbDefinition {
pub name: &'static str,
pub stride: u32,
pub elements: u32,
}

pub struct Hmb<Fpga>
where
Fpga: Deref<Target = NiFpga>,
{
session: Session<Fpga>,
memory_name: CString,
memory_size: usize,
virtual_address: VirtualAddressHandle,
}

impl<Fpga> Hmb<Fpga>
where
Fpga: Deref<Target = NiFpga>,
{
pub unsafe fn new(session: Session<Fpga>, memory_name: &CString) -> Result<Hmb<Fpga>, Error> {
let fpga = session.fpga();
match &fpga.api.hmb {
Some(hmb) => {
let mut memory_size: usize = 0;
let mut virtual_address: *mut c_void = ptr::null_mut();
match unsafe {
hmb.NiFpgaDll_OpenHmb(
fpga.session,
memory_name.as_ptr(),
&mut memory_size,
&mut virtual_address,
)
.to_result()
} {
Ok(_) => Ok({
Self {
session,
memory_name: memory_name.clone(),
memory_size,
virtual_address: VirtualAddressHandle(virtual_address),
}
}),
Err(err) => Err(err),
}
}
None => Err(Error::FPGA(Status::ResourceNotInitialized)),
}
}
}

pub trait HmbAccess {
fn read<T>(&self, offset: usize) -> T;
fn write<T>(&mut self, offset: usize, value: T);
unsafe fn get_ptr(&self) -> *const c_void;
unsafe fn get_ptr_mut(&mut self) -> *mut c_void;
}

impl<Fpga> HmbAccess for Hmb<Fpga>
where
Fpga: Deref<Target = NiFpga>,
{
fn read<T>(&self, offset: usize) -> T {
unsafe {
assert!(size_of::<T>() + offset <= self.memory_size);
let base: *const u8 = self.virtual_address.0 as *const u8;
let address = base.add(offset);
let typed_address = address as *const T;
ptr::read_volatile(typed_address)
}
}

fn write<T>(&mut self, offset: usize, value: T) {
unsafe {
assert!(size_of::<T>() + offset <= self.memory_size);
let base: *mut u8 = self.virtual_address.0 as *mut u8;
let address = base.add(offset);
let typed_address = address as *mut T;
ptr::write_volatile(typed_address, value);
}
}

unsafe fn get_ptr(&self) -> *const c_void {
self.virtual_address.0
}

unsafe fn get_ptr_mut(&mut self) -> *mut c_void {
self.virtual_address.0
}
}

impl<Fpga> Drop for Hmb<Fpga>
where
Fpga: Deref<Target = NiFpga>,
{
fn drop(&mut self) {
// Unwrap is safe here, as the only way this can get constructed is
// if its possible to unwrap it at construction
// TODO figure out what to do here with the return value
unsafe {
self.session
.fpga()
.api
.hmb
.as_ref()
.unwrap()
.NiFpgaDll_CloseHmb(self.session.fpga().session, self.memory_name.as_ptr());
}
}
}
118 changes: 118 additions & 0 deletions ni-fpga/src/interrupt_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use std::{ops::Deref, sync::Mutex, time::Duration};

pub use ni_fpga_sys::Irq;

use crate::{nifpga::IrqContext, Error, NiFpga, Session, SessionAccess, Status};

pub struct InterruptContext<Fpga>
where
Fpga: Deref<Target = NiFpga>,
{
manager: &'static Mutex<InterruptManager>,
context: IrqContext,
session: Session<Fpga>,
}

pub trait InterruptWaiter {
fn wait_for_interrupt(
&self,
mask: Irq,
ignore_previous: bool,
timeout: Duration,
) -> Result<Irq, Error>;
}

impl<Fpga> InterruptContext<Fpga>
where
Fpga: Deref<Target = NiFpga>,
{
pub fn new(session: Session<Fpga>) -> Result<Self, Error> {
static MANAGER: Mutex<InterruptManager> = Mutex::new(InterruptManager {
current_mask: Irq::empty(),
});

let context = unsafe { session.fpga() }.reserve_irq_context()?;
Ok(Self {
session,
context,
manager: &MANAGER,
})
}
}


impl<Fpga> InterruptWaiter for InterruptContext<Fpga>
where
Fpga: Deref<Target = NiFpga>,
{
fn wait_for_interrupt(
&self,
mask: Irq,
ignore_previous: bool,
timeout: Duration,
) -> Result<Irq, Error> {
let _reservation = MaskReservation::reserve(self.manager, mask)?;

if ignore_previous {
unsafe { self.session.fpga().acknowledge_irqs(mask)? };
}

unsafe {
let result = self
.session
.fpga()
.wait_on_irqs(self.context, mask, timeout)?;
self.session.fpga().acknowledge_irqs(mask)?;
Ok(result)
}
}
}

impl<Fpga> Drop for InterruptContext<Fpga>
where
Fpga: Deref<Target = NiFpga>,
{
fn drop(&mut self) {
// TODO Handle result
let _ = unsafe { self.session.fpga().unreserve_irq_context(self.context) };
}
}

struct MaskReservation {
manager: &'static Mutex<InterruptManager>,
mask: Irq,
}

impl MaskReservation {
pub fn reserve(manager: &'static Mutex<InterruptManager>, mask: Irq) -> Result<Self, Error> {
let mut lock = manager.lock()?;
lock.reserve(mask)?;
Ok(Self { manager, mask })
}
}

impl Drop for MaskReservation {
fn drop(&mut self) {
// TODO make this unwrap a bit safer
let mut lock = self.manager.lock().unwrap();
lock.release(self.mask);
}
}

struct InterruptManager {
pub current_mask: Irq,
}

impl InterruptManager {
pub fn reserve(&mut self, mask: Irq) -> Result<(), Error> {
if self.current_mask.intersects(mask) {
return Err(Error::FPGA(Status::InvalidParameter));
}
self.current_mask |= mask;
Ok(())
}

pub fn release(&mut self, mask: Irq) {
self.current_mask &= !mask;
}
}
34 changes: 31 additions & 3 deletions ni-fpga/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
#![feature(generic_const_exprs)]
#![feature(maybe_uninit_uninit_array)]
#![allow(clippy::missing_safety_doc)]

extern crate ni_fpga_sys as ffi;

mod datatype;
mod errors;
pub mod fxp;
mod hmb;
mod interrupt_manager;
mod nifpga;
mod register;
mod session;
pub mod session_lifetimes;
mod status;

pub use interrupt_manager::InterruptContext;
pub use interrupt_manager::InterruptWaiter;

pub use ni_fpga_sys::Irq;
pub use ni_fpga_sys::IrqContext;

// Keep these for backwards compatibility, but don't use them internally
pub use datatype::{Datatype, FpgaBits};
pub use datatype::{Datatype, DatatypePacker, FpgaBits};
pub use errors::Error;
pub type Offset = ffi::Offset;
pub use datatype::DerivedDatatype;
pub use datatype::StockAccessDatatype;
pub use hmb::Hmb;
pub use hmb::HmbAccess;
pub use nifpga::NiFpga;
pub use register::ConstOffset;
pub use register::Register;
pub use register::RegisterRead;
pub use register::RegisterWrite;
pub use register::StoredOffset;
pub use session::Session;
pub use session::SessionAccess;
pub use session::SessionBuilder;
pub use status::Status;

pub use register::ReadOnly;
pub use register::ReadWrite;
pub use register::WriteOnly;

pub use hmb::HmbDefinition;
397 changes: 397 additions & 0 deletions ni-fpga/src/nifpga.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,397 @@
use std::{convert::TryInto, ffi::CString, ptr::null, time::Duration};

use ni_fpga_sys::{
Bool, CloseAttribute, Irq, NiFpgaApi, NiFpgaApiContainer, Offset, OpenAttribute, Session,
};

#[derive(Clone, Copy)]
pub struct IrqContext(ni_fpga_sys::IrqContext);

use crate::{Error, Status};

pub(crate) trait StatusHelper {
fn to_result(self) -> Result<(), Error>;
}

impl StatusHelper for ffi::Status {
#[inline]
fn to_result(self) -> Result<(), Error> {
match self {
0 => Ok(()),
_ => Err(Error::FPGA(self.into())),
}
}
}

impl From<ffi::Error> for Error {
fn from(value: ffi::Error) -> Self {
match value {
// Map the explicit opening errors to what the C API
// returns for the same errors.
ffi::Error::OpeningLibraryError(_) => Error::FPGA(Status::ResourceNotFound),
ffi::Error::SymbolGettingError(_) => Error::FPGA(Status::VersionMismatch),
// Map unknowns (Which are impossible to hit)
// just as a generic error. All 3 other enum states
// for this are impossible with the FPGA library.
e => panic!("{}", e),
}
}
}

pub struct NiFpga {
pub(crate) session: Session,
pub(crate) api: NiFpgaApiContainer,
close_attribute: Option<CloseAttribute>,
}

macro_rules! type_wrapper {
($type:ident, $read_fun_name:ident, $read_ffi_name:ident, $write_fun_name:ident, $write_ffi_name:ident,
$readarr_fun_name:ident, $readarr_ffi_name:ident, $writearr_fun_name:ident, $writearr_ffi_name:ident) => {
#[inline]
pub fn $read_fun_name(&self, indicator: Offset) -> Result<$type, Error> {
let mut value: $type = Default::default();
match unsafe {
self.api
.base
.$read_ffi_name(self.session, indicator, &mut value as *mut $type)
.to_result()
} {
Ok(_) => Ok(value),
Err(err) => Err(err),
}
}

#[inline]
pub fn $write_fun_name(&self, indicator: Offset, value: $type) -> Result<(), Error> {
unsafe {
self.api
.base
.$write_ffi_name(self.session, indicator, value)
.to_result()
}
}

#[inline]
pub fn $readarr_fun_name(
&self,
indicator: Offset,
value: &mut [$type],
) -> Result<(), Error> {
unsafe {
self.api
.base
.$readarr_ffi_name(self.session, indicator, value.as_mut_ptr(), value.len())
.to_result()
}
}

#[inline]
pub fn $writearr_fun_name(&self, indicator: Offset, value: &[$type]) -> Result<(), Error> {
unsafe {
self.api
.base
.$writearr_ffi_name(self.session, indicator, value.as_ptr(), value.len())
.to_result()
}
}
};
}

impl NiFpga {
#[inline]
pub fn ffi(&self) -> &NiFpgaApiContainer {
&self.api
}

#[inline]
pub fn session(&self) -> Session {
self.session
}

#[inline]
pub fn read_bool(&self, indicator: Offset) -> Result<bool, Error> {
let mut value: u8 = 0;
match unsafe {
self.api
.base
.NiFpgaDll_ReadBool(self.session, indicator, &mut value as *mut Bool)
.to_result()
} {
Ok(_) => Ok(value != 0),
Err(err) => Err(err),
}
}
#[inline]
pub fn write_bool(&self, indicator: Offset, value: bool) -> Result<(), Error> {
let value = if value { 1 } else { 0 };
unsafe {
self.api
.base
.NiFpgaDll_WriteBool(self.session, indicator, value)
.to_result()
}
}
#[inline]
pub fn read_bool_array_fast(&self, indicator: Offset, value: &mut [u8]) -> Result<(), Error> {
unsafe {
self.api
.base
.NiFpgaDll_ReadArrayBool(self.session, indicator, value.as_mut_ptr(), value.len())
.to_result()
}
}
#[inline]
pub fn write_bool_array_fast(&self, indicator: Offset, value: &[u8]) -> Result<(), Error> {
unsafe {
self.api
.base
.NiFpgaDll_WriteArrayBool(self.session, indicator, value.as_ptr(), value.len())
.to_result()
}
}

type_wrapper!(
u8,
read_u8,
NiFpgaDll_ReadU8,
write_u8,
NiFpgaDll_WriteU8,
read_u8_array,
NiFpgaDll_ReadArrayU8,
write_u8_array,
NiFpgaDll_WriteArrayU8
);
type_wrapper!(
i8,
read_i8,
NiFpgaDll_ReadI8,
write_i8,
NiFpgaDll_WriteI8,
read_i8_array,
NiFpgaDll_ReadArrayI8,
write_i8_array,
NiFpgaDll_WriteArrayI8
);
type_wrapper!(
u16,
read_u16,
NiFpgaDll_ReadU16,
write_u16,
NiFpgaDll_WriteU16,
read_u16_array,
NiFpgaDll_ReadArrayU16,
write_u16_array,
NiFpgaDll_WriteArrayU16
);
type_wrapper!(
i16,
read_i16,
NiFpgaDll_ReadI16,
write_i16,
NiFpgaDll_WriteI16,
read_i16_array,
NiFpgaDll_ReadArrayI16,
write_i16_array,
NiFpgaDll_WriteArrayI16
);
type_wrapper!(
u32,
read_u32,
NiFpgaDll_ReadU32,
write_u32,
NiFpgaDll_WriteU32,
read_u32_array,
NiFpgaDll_ReadArrayU32,
write_u32_array,
NiFpgaDll_WriteArrayU32
);
type_wrapper!(
i32,
read_i32,
NiFpgaDll_ReadI32,
write_i32,
NiFpgaDll_WriteI32,
read_i32_array,
NiFpgaDll_ReadArrayI32,
write_i32_array,
NiFpgaDll_WriteArrayI32
);
type_wrapper!(
u64,
read_u64,
NiFpgaDll_ReadU64,
write_u64,
NiFpgaDll_WriteU64,
read_u64_array,
NiFpgaDll_ReadArrayU64,
write_u64_array,
NiFpgaDll_WriteArrayU64
);
type_wrapper!(
i64,
read_i64,
NiFpgaDll_ReadI64,
write_i64,
NiFpgaDll_WriteI64,
read_i64_array,
NiFpgaDll_ReadArrayI64,
write_i64_array,
NiFpgaDll_WriteArrayI64
);

type_wrapper!(
f32,
read_f32,
NiFpgaDll_ReadSgl,
write_f32,
NiFpgaDll_WriteSgl,
read_f32_array,
NiFpgaDll_ReadArraySgl,
write_f32_array,
NiFpgaDll_WriteArraySgl
);

type_wrapper!(
f64,
read_f64,
NiFpgaDll_ReadDbl,
write_f64,
NiFpgaDll_WriteDbl,
read_f64_array,
NiFpgaDll_ReadArrayDbl,
write_f64_array,
NiFpgaDll_WriteArrayDbl
);

pub fn from_session(session: Session) -> Result<Self, Error> {
let api = NiFpgaApi::load()?;

Ok(Self {
session,
api,
close_attribute: None,
})
}

pub fn find_offset(&self, name: CString) -> Result<Offset, Error> {
let api = self
.api
.api21
.as_ref()
.ok_or(Error::FPGA(Status::FeatureNotSupported))?;
let mut fifo_number = 0;
unsafe {
api.NiFpgaDll_FindRegister(self.session, name.as_ptr(), &mut fifo_number)
.to_result()
.map(|_| fifo_number)
}
}

pub fn reserve_irq_context(&self) -> Result<IrqContext, Error> {
let mut context: ni_fpga_sys::IrqContext = null();
unsafe {
self.api
.base
.NiFpgaDll_ReserveIrqContext(self.session, &mut context)
.to_result()
.map(|_| IrqContext(context))
}
}

pub fn acknowledge_irqs(&self, mask: Irq) -> Result<(), Error> {
unsafe {
self.api
.base
.NiFpgaDll_AcknowledgeIrqs(self.session, mask.bits())
.to_result()
}
}

pub fn wait_on_irqs(
&self,
context: IrqContext,
mask: Irq,
timeout: Duration,
) -> Result<Irq, Error> {
unsafe {
let mut asserted_irqs: u32 = 0;
let mut timed_out: Bool = 0;
let timeout: u32 = timeout.as_millis().try_into()?;
self.api
.base
.NiFpgaDll_WaitOnIrqs(
self.session,
context.0,
mask.bits(),
timeout,
&mut asserted_irqs,
&mut timed_out,
)
.to_result()?;
Ok(Irq::from_bits_retain(asserted_irqs))
}
}

pub fn unreserve_irq_context(&self, context: IrqContext) -> Result<(), Error> {
unsafe {
self.api
.base
.NiFpgaDll_UnreserveIrqContext(self.session, context.0)
.to_result()
}
}

pub fn open(
bitfile: CString,
signature: CString,
resource: CString,
open_attribute: OpenAttribute,
close_attribute: CloseAttribute,
) -> Result<Self, Error> {
let api = NiFpgaApi::load()?;

let mut session: Session = 0;
match unsafe {
api.base
.NiFpgaDll_Open(
bitfile.as_ptr(),
signature.as_ptr(),
resource.as_ptr(),
open_attribute.bits(),
&mut session,
)
.to_result()
} {
Ok(_) => Ok(Self {
session,
api,
close_attribute: Some(close_attribute),
}),
Err(err) => Err(err),
}
}

pub fn close(self, attribute: CloseAttribute) -> Result<(), Error> {
match self.close_attribute {
Some(_) => unsafe {
self.api
.base
.NiFpgaDll_Close(self.session, attribute.bits())
.to_result()
},
None => Err(Error::ClosingUnownedSession),
}
}
}

impl Drop for NiFpga {
fn drop(&mut self) {
// TODO figure out what to do here with attribute
// and the return value
if let Some(attr) = self.close_attribute {
unsafe {
self.api.base.NiFpgaDll_Close(self.session, attr.bits());
}
}
}
}
230 changes: 230 additions & 0 deletions ni-fpga/src/register.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use std::{borrow::Borrow, marker::PhantomData};

use crate::{Datatype, Error, Offset, SessionAccess};

pub struct ReadOnly;
pub struct WriteOnly;
pub struct ReadWrite;

pub trait RegisterPermission {}

impl RegisterPermission for ReadOnly {}
impl RegisterPermission for ReadWrite {}
impl RegisterPermission for WriteOnly {}

#[derive(Clone, Copy)]
pub struct ConstOffset<const N: Offset>;

impl<const N: Offset> From<ConstOffset<N>> for Offset {
#[inline]
fn from(_value: ConstOffset<N>) -> Self {
N
}
}

#[derive(Clone, Copy)]
pub struct StoredOffset(Offset);

impl From<StoredOffset> for Offset {
#[inline]
fn from(value: StoredOffset) -> Self {
value.0
}
}

pub trait RegisterOffset: Copy + Clone {}
impl RegisterOffset for StoredOffset {}
impl<const N: Offset> RegisterOffset for ConstOffset<N> {}

pub struct Register<T, P, O>
where
T: Datatype,
P: RegisterPermission,
O: RegisterOffset,
{
_offset_type: O,
_type: PhantomData<T>,
_perms: PhantomData<P>,
}

impl<T, P, O> Register<T, P, O>
where
T: Datatype,
P: RegisterPermission,
O: RegisterOffset,
{
pub unsafe fn duplicate(&self) -> Self {
Self {
_offset_type: self._offset_type,
_type: PhantomData,
_perms: PhantomData,
}
}
}

impl<T, P> Register<T, P, StoredOffset>
where
T: Datatype,
P: RegisterPermission,
{
#[inline]
pub const unsafe fn new(offset: Offset) -> Self {
Self {
_offset_type: StoredOffset(offset),
_type: PhantomData,
_perms: PhantomData,
}
}

pub const unsafe fn transmute_type<NT>(self) -> Register<NT, P, StoredOffset>
where
NT: Datatype,
{
Register {
_offset_type: self._offset_type,
_type: PhantomData,
_perms: PhantomData,
}
}

pub const unsafe fn transmute_permissions<NP>(self) -> Register<T, NP, StoredOffset>
where
NP: RegisterPermission,
{
Register {
_offset_type: self._offset_type,
_type: PhantomData,
_perms: PhantomData,
}
}
}

impl<T, P, const N: Offset> Register<T, P, ConstOffset<N>>
where
T: Datatype,
P: RegisterPermission,
{
#[inline]
pub const unsafe fn new_const() -> Self {
Self {
_offset_type: ConstOffset,
_type: PhantomData,
_perms: PhantomData,
}
}

pub const unsafe fn transmute_type<NT>(self) -> Register<NT, P, ConstOffset<N>>
where
NT: Datatype,
{
Register {
_offset_type: self._offset_type,
_type: PhantomData,
_perms: PhantomData,
}
}

pub const unsafe fn transmute_permissions<NP>(self) -> Register<T, NP, ConstOffset<N>>
where
NP: RegisterPermission,
{
Register {
_offset_type: self._offset_type,
_type: PhantomData,
_perms: PhantomData,
}
}
}

impl<T, P, const N: Offset> From<Register<T, P, ConstOffset<N>>> for Register<T, P, StoredOffset>
where
T: Datatype,
P: RegisterPermission,
{
#[inline]
fn from(_: Register<T, P, ConstOffset<N>>) -> Self {
Self {
_offset_type: StoredOffset(N),
_type: PhantomData,
_perms: PhantomData,
}
}
}

pub trait RegisterRead<T>
where
T: Datatype,
{
#[doc(hidden)]
fn offset_read(&self) -> Offset;

#[inline(never)]
fn read(&self, session: &impl SessionAccess) -> Result<T, Error> {
unsafe { T::read(session, self.offset_read()) }
}
}

pub trait RegisterWrite<T>
where
T: Datatype,
{
#[doc(hidden)]
fn offset_write(&self) -> Offset;

#[inline]
fn write(&mut self, session: &impl SessionAccess, value: impl Borrow<T>) -> Result<(), Error> {
unsafe { T::write(session, self.offset_write(), value) }
}
}

impl<T, U> RegisterRead<T> for Register<T, ReadOnly, U>
where
T: Datatype,
U: RegisterOffset,
Offset: From<U>,
U: Copy,
{
#[inline]
fn offset_read(&self) -> Offset {
self._offset_type.into()
}
}

impl<T, U> RegisterWrite<T> for Register<T, WriteOnly, U>
where
T: Datatype,
U: RegisterOffset,
Offset: From<U>,
U: Copy,
{
#[inline]
fn offset_write(&self) -> Offset {
self._offset_type.into()
}
}

impl<T, U> RegisterRead<T> for Register<T, ReadWrite, U>
where
T: Datatype,
U: RegisterOffset,
Offset: From<U>,
U: Copy,
{
#[inline]
fn offset_read(&self) -> Offset {
self._offset_type.into()
}
}

impl<T, U> RegisterWrite<T> for Register<T, ReadWrite, U>
where
T: Datatype,
U: RegisterOffset,
Offset: From<U>,
U: Copy,
{
#[inline]
fn offset_write(&self) -> Offset {
self._offset_type.into()
}
}
381 changes: 303 additions & 78 deletions ni-fpga/src/session.rs

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions ni-fpga/src/session_lifetimes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::{marker::PhantomData, ops::Deref, sync::Arc};

use crate::nifpga::NiFpga;

pub trait StorageClone<'a> {
type Target;

fn storage_clone(&'a self) -> Self::Target;
}

#[derive(Clone)]
pub struct ArcStorage {
pub(crate) api: Arc<NiFpga>,
}

impl Deref for ArcStorage {
type Target = NiFpga;

#[inline]
fn deref(&self) -> &Self::Target {
&self.api
}
}

impl StorageClone<'_> for ArcStorage {
type Target = ArcStorage;

#[inline]
fn storage_clone(&self) -> ArcStorage {
self.clone()
}
}

pub struct InPlaceStorage<'a> {
pub(crate) api: NiFpga,
pub(crate) _marker: PhantomData<&'a NiFpga>,
}

impl<'a> Deref for InPlaceStorage<'a> {
type Target = NiFpga;

#[inline]
fn deref(&self) -> &Self::Target {
&self.api
}
}

impl<'a> StorageClone<'a> for InPlaceStorage<'a> {
type Target = RefStorage<'a>;

#[inline]
fn storage_clone(&'a self) -> RefStorage<'a> {
RefStorage { api: &self.api }
}
}

impl<'a> From<&'a InPlaceStorage<'_>> for RefStorage<'a> {
#[inline]
fn from(value: &'a InPlaceStorage) -> Self {
Self { api: &value.api }
}
}

pub struct RefStorage<'a> {
pub(crate) api: &'a NiFpga,
}

impl<'a> Deref for RefStorage<'a> {
type Target = NiFpga;

#[inline]
fn deref(&self) -> &Self::Target {
self.api
}
}

impl<'a> StorageClone<'a> for RefStorage<'a> {
type Target = RefStorage<'a>;

#[inline]
fn storage_clone(&self) -> RefStorage<'a> {
RefStorage { api: self.api }
}
}
2 changes: 2 additions & 0 deletions ni-fpga/src/status.rs
Original file line number Diff line number Diff line change
@@ -126,12 +126,14 @@ impl fmt::Debug for Status {
}

impl From<ffi::Status> for Status {
#[inline]
fn from(status: ffi::Status) -> Self {
Status(status)
}
}

impl From<Status> for ffi::Status {
#[inline]
fn from(status: Status) -> Self {
status.0
}