ni_fpga_interface_build/
lib.rs

1//! The NI Fpga Interface Builder is used to generate a Rust module
2//! with the definition of the interface of a given FPGA bitfile.
3//!
4//! This depends on the NI Fpga Interface crate and generates the
5//! definitions that it can use.
6//!
7//! # Example
8//!
9//! ```no_run
10//! use ni_fpga_interface_build::FpgaCInterface;
11//!
12//! FpgaCInterface::from_custom_header("NiFpga_prefix.h").build();
13//! ```
14//!
15//! This will generate a file in the output directory with the name `NiFpga_Main.rs`.
16//!
17//! This will contain a module with the definitions of registers and DMA FIFOs.
18//!
19//! # Example Output
20//!
21//! ```rust,ignore
22//! pub const SIGNATURE: &str = "A0613989B20F45FC6E79EB71383493E8";
23//!
24//! pub mod registers {
25//!     use ni_fpga_interface::registers::{ArrayRegister, Register};
26//!     pub const SglSumArray: ArrayRegister<f32, 4> = ArrayRegister::new(0x1801C);
27//!     pub const U8ControlArray: ArrayRegister<u8, 4> = ArrayRegister::new(0x18014);
28//!     pub const U8SumArray: ArrayRegister<u8, 4> = ArrayRegister::new(0x18010);
29//!     pub const SglControl: Register<f32> = Register::new(0x1802C);
30//!     pub const U8Sum: Register<u8> = Register::new(0x18006);
31//!     pub const U8Control: Register<u8> = Register::new(0x18002);
32//!     pub const SglSum: Register<f32> = Register::new(0x18028);
33//!     pub const SglResult: Register<f32> = Register::new(0x18024);
34//!     pub const SglResultArray: ArrayRegister<f32, 4> = ArrayRegister::new(0x18018);
35//!     pub const IRQs: Register<u32> = Register::new(0x18060);
36//!     pub const U8Result: Register<u8> = Register::new(0x1800A);
37//!     pub const U8ResultArray: ArrayRegister<u8, 4> = ArrayRegister::new(0x1800C);
38//!     pub const SglControlArray: ArrayRegister<f32, 4> = ArrayRegister::new(0x18020);
39//! }
40//!
41//! pub mod fifos {
42//!     use ni_fpga_interface::fifos::{ReadFifo, WriteFifo};
43//!     pub const NumbersFromFPGA: ReadFifo<u16> = ReadFifo::new(0x1);
44//!     pub const NumbersToFPGA: WriteFifo<u32> = WriteFifo::new(0x0);
45//! }
46//! ```
47//!
48//! To then use this in your system you can import it into a module.
49//!
50//! ```rust,ignore
51//! mod fpga_defs {
52//!    include!(concat!(env!("OUT_DIR"), "/NiFpga_Main.rs"));
53//!}
54//! ```
55
56mod address_definitions;
57mod address_definitions_visitor;
58mod bindings_parser;
59mod custom_type_register_visitor;
60mod registers_generator;
61mod string_constant_visitor;
62
63use std::{
64    env,
65    path::{Path, PathBuf},
66};
67
68/// Defines the generated C interface for the FPGA project.
69pub struct FpgaCInterface {
70    common_c: PathBuf,
71    custom_h: PathBuf,
72    custom_c: Option<PathBuf>,
73    interface_name: String,
74    sysroot: Option<String>,
75}
76
77impl FpgaCInterface {
78    /// Constructs a new interface from the given custom header.
79    ///
80    /// This is the header file which includes the project specific prefix.
81    /// e.g. NiFpga_prefix.h not NiFpga.h.
82    ///
83    /// This finds the other files assuming they are in the same folder.
84    pub fn from_custom_header(fpga_header: impl AsRef<Path>) -> Self {
85        let fpga_header = fpga_header.as_ref();
86        let fpga_header = fpga_header.to_owned();
87        let interface_folder = fpga_header.parent().unwrap();
88        let interface_name = fpga_header
89            .file_stem()
90            .unwrap()
91            .to_str()
92            .unwrap()
93            .strip_prefix("NiFpga_")
94            .unwrap()
95            .to_owned();
96
97        let common_c = interface_folder.join("NiFpga.c");
98        let custom_c = interface_folder.join(format!("NiFpga_{}.c", interface_name));
99
100        let custom_c = if custom_c.exists() {
101            Some(custom_c)
102        } else {
103            None
104        };
105
106        Self {
107            common_c,
108            custom_h: fpga_header,
109            custom_c,
110            interface_name,
111            sysroot: None,
112        }
113    }
114
115    /// Sets the sysroot for the C compiler.
116    /// This is useful for cross compiling.
117    /// ```no_run
118    /// use ni_fpga_interface_build::FpgaCInterface;
119    /// FpgaCInterface::from_custom_header("NiFpga_prefix.h")
120    ///    .sysroot("C:\\build\\2023\\x64\\sysroots\\core2-64-nilrt-linux")
121    ///   .build();
122    /// ```
123    pub fn sysroot(&mut self, sysroot: impl Into<String>) -> &mut Self {
124        self.sysroot = Some(sysroot.into());
125        self
126    }
127
128    /// Build the C interface and generate rust bindings for it.
129    pub fn build(&self) {
130        self.build_lib();
131        self.build_rust_interface();
132    }
133
134    fn build_lib(&self) {
135        let mut build = cc::Build::new();
136
137        if let Some(path) = &self.sysroot {
138            build.flag(&format!("--sysroot={path}"));
139        }
140
141        build.file(&self.common_c);
142
143        if let Some(custom_c) = &self.custom_c {
144            build.file(custom_c);
145        }
146
147        build.compile("ni_fpga");
148    }
149
150    fn build_rust_interface(&self) {
151        // Write the bindings to the $OUT_DIR/bindings.rs file.
152        let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
153        let mod_path = out_path.join(format!("NiFpga_{}.rs", self.interface_name));
154        println!("cargo:rerun-if-changed={}", self.custom_h.display());
155
156        let interface_description = bindings_parser::InterfaceDescription::parse_bindings(
157            &self.interface_name,
158            &PathBuf::from(&self.custom_h),
159        );
160
161        std::fs::write(mod_path, interface_description.generate_rust_output()).unwrap();
162    }
163}
164
165#[cfg(test)]
166mod test {
167    use std::path::PathBuf;
168
169    use crate::FpgaCInterface;
170
171    #[test]
172    fn test_constructs_from_custom_header_relative_path() {
173        let fpga_header = "./NiFpga_fpga.h";
174        let fpga_interface = FpgaCInterface::from_custom_header(fpga_header);
175        assert_eq!(fpga_interface.common_c, PathBuf::from("./NiFpga.c"));
176        //this is none since it wont exist in test environment.
177        assert_eq!(fpga_interface.custom_c, None);
178        assert_eq!(fpga_interface.custom_h, PathBuf::from("./NiFpga_fpga.h"));
179        assert_eq!(fpga_interface.interface_name, "fpga");
180    }
181
182    #[test]
183    fn test_constructs_from_custom_header_absolute_path() {
184        let fpga_header = "C:\\fpga\\NiFpga_fpga.h";
185        let fpga_interface = FpgaCInterface::from_custom_header(fpga_header);
186        assert_eq!(fpga_interface.common_c, PathBuf::from("C:\\fpga\\NiFpga.c"));
187        //this is none since it wont exist in test environment.
188        assert_eq!(fpga_interface.custom_c, None);
189        assert_eq!(
190            fpga_interface.custom_h,
191            PathBuf::from("C:\\fpga\\NiFpga_fpga.h")
192        );
193        assert_eq!(fpga_interface.interface_name, "fpga");
194    }
195}