diff --git a/.gitmodules b/.gitmodules index fde861f7..c03206a3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "himbaechel/uarch/xilinx/meta"] path = himbaechel/uarch/xilinx/meta url = https://github.com/gatecat/nextpnr-xilinx-meta +[submodule "3rdparty/corrosion"] + path = 3rdparty/corrosion + url = https://github.com/corrosion-rs/corrosion diff --git a/3rdparty/corrosion b/3rdparty/corrosion new file mode 160000 index 00000000..be764802 --- /dev/null +++ b/3rdparty/corrosion @@ -0,0 +1 @@ +Subproject commit be76480232216a64f65e3b1d9794d68cbac6c690 diff --git a/CMakeLists.txt b/CMakeLists.txt index adef332f..2a2886e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,13 @@ option(EXTERNAL_CHIPDB "Create build with pre-built chipdb binaries" OFF) option(WERROR "pass -Werror to compiler (used for CI)" OFF) option(PROFILER "Link against libprofiler" OFF) option(USE_IPO "Compile nextpnr with IPO" ON) +option(USE_RUST "Enable Rust bindings" OFF) + +if (USE_RUST) + add_subdirectory(3rdparty/corrosion) + corrosion_import_crate(MANIFEST_PATH rust/Cargo.toml PROFILE "release" IMPORTED_CRATES RUST_CRATES) + aux_source_directory(rust RUST_FILES) +endif() if (USE_IPO) if (ipo_supported) @@ -249,7 +256,10 @@ aux_source_directory(3rdparty/json11 EXT_JSON11_FILES) aux_source_directory(3rdparty/oourafft EXT_OOURAFFT_FILES) aux_source_directory(frontend/ FRONTEND_FILES) +aux_source_directory(rust/ RUST_FILES) + set(COMMON_FILES ${KERNEL_SRC_FILES} ${PLACE_SRC_FILES} ${ROUTE_SRC_FILES} ${EXT_JSON11_FILES} ${EXT_OOURAFFT_FILES} ${JSON_PARSER_FILES} ${FRONTEND_FILES}) + if( NOT CMAKE_BUILD_TYPE ) set(CMAKE_BUILD_TYPE Release) endif() @@ -337,7 +347,7 @@ foreach (family ${ARCH}) endforeach() # Include family-specific source files to all family targets and set defines appropriately - target_include_directories(${target} PRIVATE ${family}/ ${CMAKE_CURRENT_BINARY_DIR}/generated/) + target_include_directories(${target} PRIVATE ${family}/ ${CMAKE_CURRENT_BINARY_DIR}/generated/ rust/) target_compile_definitions(${target} PRIVATE NEXTPNR_NAMESPACE=nextpnr_${family} ARCH_${ufamily} ARCHNAME=${family}) target_link_libraries(${target} LINK_PUBLIC ${Boost_LIBRARIES} ${link_param}) if (NOT MSVC) @@ -360,6 +370,13 @@ foreach (family ${ARCH}) endif() endif() endif() + if (USE_RUST) + target_sources(${target} PRIVATE ${RUST_FILES}) + target_compile_definitions(${target} PRIVATE USE_RUST) + foreach(crate ${RUST_CRATES}) + target_link_libraries(${target} PRIVATE ${crate}) + endforeach(crate) + endif() endforeach (target) endforeach (family) diff --git a/ice40/arch.cc b/ice40/arch.cc index 030849a5..1f8577fd 100644 --- a/ice40/arch.cc +++ b/ice40/arch.cc @@ -30,6 +30,7 @@ #include "placer_static.h" #include "router1.h" #include "router2.h" +#include "rust.h" #include "timing_opt.h" #include "util.h" NEXTPNR_NAMESPACE_BEGIN @@ -642,6 +643,12 @@ std::vector Arch::getGroupGroups(GroupId group) const bool Arch::place() { +#ifdef USE_RUST + log_info("Calling Rust\n"); + example_printnets(getCtx()); + log_info("Returned from Rust\n"); +#endif + std::string placer = str_or_default(settings, id_placer, defaultPlacer); if (placer == "heap") { PlacerHeapCfg cfg(getCtx()); diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 00000000..edf09dfe --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + "nextpnr", + "example_printnets", +] diff --git a/rust/example_printnets/Cargo.toml b/rust/example_printnets/Cargo.toml new file mode 100644 index 00000000..bc516be7 --- /dev/null +++ b/rust/example_printnets/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example_printnets" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "example_printnets" +path = "src/lib.rs" +crate-type = ["staticlib"] + +[dependencies] +nextpnr = { path = "../nextpnr" } diff --git a/rust/example_printnets/src/lib.rs b/rust/example_printnets/src/lib.rs new file mode 100644 index 00000000..7a21af50 --- /dev/null +++ b/rust/example_printnets/src/lib.rs @@ -0,0 +1,12 @@ +use nextpnr::{Context, Nets}; + +#[no_mangle] +pub extern "C" fn rust_example_printnets(ctx: &mut Context) { + let nets = Nets::new(ctx); + let nets_vec = nets.to_vec(); + + println!("Nets in design:"); + for (&name, _net) in nets_vec { + println!(" {}", ctx.name_of(name).to_str().unwrap()); + } +} diff --git a/rust/nextpnr/Cargo.toml b/rust/nextpnr/Cargo.toml new file mode 100644 index 00000000..aa0fb488 --- /dev/null +++ b/rust/nextpnr/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "nextpnr" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "nextpnr" +path = "src/lib.rs" +crate-type = ["rlib"] + +[dependencies] +libc = "0.2" diff --git a/rust/nextpnr/src/lib.rs b/rust/nextpnr/src/lib.rs new file mode 100644 index 00000000..f0231f7b --- /dev/null +++ b/rust/nextpnr/src/lib.rs @@ -0,0 +1,581 @@ +use core::slice; +use std::{collections::HashMap, ffi::CStr, marker::PhantomData, sync::Mutex}; + +use libc::c_char; + +#[derive(Clone, Copy)] +#[repr(C)] +pub enum PlaceStrength { + None = 0, + Weak = 1, + Strong = 2, + Placer = 3, + Fixed = 4, + Locked = 5, + User = 6, +} + +#[repr(C)] +pub struct CellInfo { + private: [u8; 0], +} + +impl CellInfo { + pub fn location(&self) -> Loc { + unsafe { npnr_cellinfo_get_location(self) } + } +} + +#[repr(C)] +pub struct NetInfo { + private: [u8; 0], +} + +impl NetInfo { + pub fn driver(&mut self) -> *mut PortRef { + unsafe { npnr_netinfo_driver(self) } + } + + pub fn is_global(&self) -> bool { + unsafe { npnr_netinfo_is_global(self) } + } + + pub fn index(&self) -> NetIndex { + unsafe { npnr_netinfo_udata(self) } + } +} + +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct NetIndex(i32); + +impl NetIndex { + pub fn into_inner(self) -> i32 { + self.0 + } +} + +#[repr(C)] +pub struct PortRef { + private: [u8; 0], +} + +impl PortRef { + pub fn cell(&self) -> Option<&CellInfo> { + // SAFETY: handing out &s is safe when we have &self. + unsafe { npnr_portref_cell(self).as_ref() } + } + + pub fn cell_mut(&mut self) -> Option<&mut CellInfo> { + // SAFETY: handing out &mut is safe when we have &mut self + // as getting multiple &mut CellInfo would require multiple &mut PortRef. + unsafe { npnr_portref_cell(self).as_mut() } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct IdString(libc::c_int); + +/// A type representing a bel name. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct BelId(u64); + +impl BelId { + /// Return a sentinel value that represents an invalid bel. + pub fn null() -> Self { + // SAFETY: BelId() has no safety requirements. + unsafe { npnr_belid_null() } + } + + /// Check if this bel is invalid. + pub fn is_null(self) -> bool { + self == Self::null() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct PipId(u64); + +impl PipId { + pub fn null() -> Self { + unsafe { npnr_pipid_null() } + } +} + +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct WireId(u64); + +impl WireId { + /// Return a sentinel value that represents an invalid wire. + pub fn null() -> Self { + // SAFETY: WireId() has no safety requirements. + unsafe { npnr_wireid_null() } + } + + /// Check if this wire is invalid. + pub fn is_null(self) -> bool { + self == Self::null() + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Loc { + pub x: libc::c_int, + pub y: libc::c_int, + pub z: libc::c_int, +} + +impl From<(i32, i32)> for Loc { + fn from(pos: (i32, i32)) -> Self { + Self { + x: pos.0, + y: pos.1, + z: 0, + } + } +} + +#[repr(C)] +pub struct Context { + _private: [u8; 0], +} + +impl Context { + /// Get grid X dimension. All bels and pips must have X coordinates in the range `0 .. getGridDimX()-1` (inclusive). + pub fn grid_dim_x(&self) -> i32 { + unsafe { npnr_context_get_grid_dim_x(self) } + } + + /// Get grid Y dimension. All bels and pips must have Y coordinates in the range `0 .. getGridDimY()-1` (inclusive). + pub fn grid_dim_y(&self) -> i32 { + unsafe { npnr_context_get_grid_dim_y(self) } + } + + /// Bind a given bel to a given cell with the given strength. + pub fn bind_bel(&mut self, bel: BelId, cell: *mut CellInfo, strength: PlaceStrength) { + unsafe { npnr_context_bind_bel(self, bel, cell, strength) } + } + + /// Unbind a bel. + pub fn unbind_bel(&mut self, bel: BelId) { + unsafe { npnr_context_unbind_bel(self, bel) } + } + + /// Returns true if the bel is available. A bel can be unavailable because it is bound, or because it is exclusive to some other resource that is bound. + pub fn check_bel_avail(&self, bel: BelId) -> bool { + unsafe { npnr_context_check_bel_avail(self, bel) } + } + + /// Bind a wire to a net. This method must be used when binding a wire that is driven by a bel pin. Use bindPip() when binding a wire that is driven by a pip. + pub fn bind_wire(&mut self, wire: WireId, net: *mut NetInfo, strength: PlaceStrength) { + unsafe { npnr_context_bind_wire(self, wire, net, strength) } + } + + /// Unbind a wire. For wires that are driven by a pip, this will also unbind the driving pip. + pub fn unbind_wire(&mut self, wire: WireId) { + unsafe { npnr_context_unbind_wire(self, wire) } + } + + /// Bid a pip to a net. This also bind the destination wire of that pip. + pub fn bind_pip(&mut self, pip: PipId, net: *mut NetInfo, strength: PlaceStrength) { + unsafe { npnr_context_bind_pip(self, pip, net, strength) } + } + + /// Unbind a pip and the wire driven by that pip. + pub fn unbind_pip(&mut self, pip: PipId) { + unsafe { npnr_context_unbind_pip(self, pip) } + } + + /// Get the source wire for a pip. + pub fn pip_src_wire(&self, pip: PipId) -> WireId { + unsafe { npnr_context_get_pip_src_wire(self, pip) } + } + + /// Get the destination wire for a pip. + pub fn pip_dst_wire(&self, pip: PipId) -> WireId { + unsafe { npnr_context_get_pip_dst_wire(self, pip) } + } + + // TODO: Should this be a Duration? Does that even make sense? + pub fn estimate_delay(&self, src: WireId, dst: WireId) -> f32 { + unsafe { npnr_context_estimate_delay(self, src, dst) } + } + + pub fn pip_delay(&self, pip: PipId) -> f32 { + unsafe { npnr_context_get_pip_delay(self, pip) } + } + + pub fn wire_delay(&self, wire: WireId) -> f32 { + unsafe { npnr_context_get_wire_delay(self, wire) } + } + + pub fn delay_epsilon(&self) -> f32 { + unsafe { npnr_context_delay_epsilon(self) } + } + + pub fn source_wire(&self, net: *const NetInfo) -> WireId { + unsafe { npnr_context_get_netinfo_source_wire(self, net) } + } + + pub fn sink_wires(&self, net: *const NetInfo, sink: *const PortRef) -> Vec { + let mut v = Vec::new(); + let mut n = 0; + loop { + let wire = unsafe { npnr_context_get_netinfo_sink_wire(self, net, sink, n) }; + if wire.is_null() { + break; + } + n += 1; + v.push(wire); + } + v + } + + pub fn wires_leaking(&self) -> &[WireId] { + let mut wires = std::ptr::null_mut(); + let len = unsafe { npnr_context_get_wires_leak(self, &mut wires as *mut *mut WireId) }; + unsafe { std::slice::from_raw_parts(wires, len as usize) } + } + + pub fn pips_leaking(&self) -> &[PipId] { + let mut pips = std::ptr::null_mut(); + let len = unsafe { npnr_context_get_pips_leak(self, &mut pips as *mut *mut PipId) }; + unsafe { std::slice::from_raw_parts(pips, len as usize) } + } + + pub fn get_downhill_pips(&self, wire: WireId) -> DownhillPipsIter { + let iter = unsafe { npnr_context_get_pips_downhill(self, wire) }; + DownhillPipsIter { + iter, + phantom_data: Default::default(), + } + } + + pub fn get_uphill_pips(&self, wire: WireId) -> UphillPipsIter { + let iter = unsafe { npnr_context_get_pips_uphill(self, wire) }; + UphillPipsIter { + iter, + phantom_data: Default::default(), + } + } + + pub fn pip_location(&self, pip: PipId) -> Loc { + unsafe { npnr_context_get_pip_location(self, pip) } + } + + pub fn pip_direction(&self, pip: PipId) -> Loc { + unsafe { npnr_context_get_pip_direction(self, pip) } + } + + pub fn pip_avail_for_net(&self, pip: PipId, net: *mut NetInfo) -> bool { + unsafe { npnr_context_check_pip_avail_for_net(self, pip, net) } + } + + pub fn check(&self) { + unsafe { npnr_context_check(self) } + } + + pub fn debug(&self) -> bool { + unsafe { npnr_context_debug(self) } + } + + pub fn id(&self, s: &str) -> IdString { + let s = std::ffi::CString::new(s).unwrap(); + unsafe { npnr_context_id(self, s.as_ptr()) } + } + + pub fn name_of(&self, s: IdString) -> &CStr { + unsafe { CStr::from_ptr(npnr_context_name_of(self, s)) } + } + + pub fn name_of_pip(&self, pip: PipId) -> &CStr { + unsafe { CStr::from_ptr(npnr_context_name_of_pip(self, pip)) } + } + + pub fn name_of_wire(&self, wire: WireId) -> &CStr { + static MUTEX: Mutex<()> = Mutex::new(()); + let _lock = MUTEX.lock().unwrap(); + unsafe { CStr::from_ptr(npnr_context_name_of_wire(self, wire)) } + } + + pub fn verbose(&self) -> bool { + unsafe { npnr_context_verbose(self) } + } +} + +extern "C" { + pub fn npnr_log_info(format: *const c_char); + pub fn npnr_log_error(format: *const c_char); + + fn npnr_belid_null() -> BelId; + fn npnr_wireid_null() -> WireId; + fn npnr_pipid_null() -> PipId; + + fn npnr_context_get_grid_dim_x(ctx: *const Context) -> libc::c_int; + fn npnr_context_get_grid_dim_y(ctx: *const Context) -> libc::c_int; + fn npnr_context_bind_bel( + ctx: *mut Context, + bel: BelId, + cell: *mut CellInfo, + strength: PlaceStrength, + ); + fn npnr_context_unbind_bel(ctx: *mut Context, bel: BelId); + fn npnr_context_check_bel_avail(ctx: *const Context, bel: BelId) -> bool; + fn npnr_context_bind_wire( + ctx: *mut Context, + wire: WireId, + net: *mut NetInfo, + strength: PlaceStrength, + ); + fn npnr_context_unbind_wire(ctx: *mut Context, wire: WireId); + fn npnr_context_bind_pip( + ctx: *mut Context, + pip: PipId, + net: *mut NetInfo, + strength: PlaceStrength, + ); + fn npnr_context_unbind_pip(ctx: *mut Context, pip: PipId); + fn npnr_context_get_pip_src_wire(ctx: *const Context, pip: PipId) -> WireId; + fn npnr_context_get_pip_dst_wire(ctx: *const Context, pip: PipId) -> WireId; + fn npnr_context_estimate_delay(ctx: *const Context, src: WireId, dst: WireId) -> f32; + fn npnr_context_delay_epsilon(ctx: *const Context) -> f32; + fn npnr_context_get_pip_delay(ctx: *const Context, pip: PipId) -> f32; + fn npnr_context_get_wire_delay(ctx: *const Context, wire: WireId) -> f32; + fn npnr_context_get_wires_leak(ctx: *const Context, wires: *mut *mut WireId) -> u64; + fn npnr_context_get_pips_leak(ctx: *const Context, pips: *mut *mut PipId) -> u64; + fn npnr_context_get_pip_location(ctx: *const Context, pip: PipId) -> Loc; + fn npnr_context_get_pip_direction(ctx: *const Context, pip: PipId) -> Loc; + fn npnr_context_check_pip_avail_for_net( + ctx: *const Context, + pip: PipId, + net: *const NetInfo, + ) -> bool; + + fn npnr_context_check(ctx: *const Context); + fn npnr_context_debug(ctx: *const Context) -> bool; + fn npnr_context_id(ctx: *const Context, s: *const c_char) -> IdString; + fn npnr_context_name_of(ctx: *const Context, s: IdString) -> *const libc::c_char; + fn npnr_context_name_of_pip(ctx: *const Context, pip: PipId) -> *const libc::c_char; + fn npnr_context_name_of_wire(ctx: *const Context, wire: WireId) -> *const libc::c_char; + fn npnr_context_verbose(ctx: *const Context) -> bool; + + fn npnr_context_get_netinfo_source_wire(ctx: *const Context, net: *const NetInfo) -> WireId; + fn npnr_context_get_netinfo_sink_wire( + ctx: *const Context, + net: *const NetInfo, + sink: *const PortRef, + n: u32, + ) -> WireId; + + fn npnr_context_nets_leak( + ctx: *const Context, + names: *mut *mut libc::c_int, + nets: *mut *mut *mut NetInfo, + ) -> u32; + fn npnr_context_get_pips_downhill(ctx: *const Context, wire: WireId) -> *mut RawDownhillIter; + fn npnr_delete_downhill_iter(iter: *mut RawDownhillIter); + fn npnr_context_get_pips_uphill(ctx: *const Context, wire: WireId) -> *mut RawUphillIter; + fn npnr_delete_uphill_iter(iter: *mut RawUphillIter); + + fn npnr_netinfo_driver(net: *mut NetInfo) -> *mut PortRef; + fn npnr_netinfo_users_leak(net: *mut NetInfo, users: *mut *mut *mut PortRef) -> u32; + fn npnr_netinfo_is_global(net: *const NetInfo) -> bool; + fn npnr_netinfo_udata(net: *const NetInfo) -> NetIndex; + fn npnr_netinfo_udata_set(net: *mut NetInfo, value: NetIndex); + + fn npnr_portref_cell(port: *const PortRef) -> *mut CellInfo; + fn npnr_cellinfo_get_location(info: *const CellInfo) -> Loc; + + fn npnr_inc_downhill_iter(iter: *mut RawDownhillIter); + fn npnr_deref_downhill_iter(iter: *mut RawDownhillIter) -> PipId; + fn npnr_is_downhill_iter_done(iter: *mut RawDownhillIter) -> bool; + fn npnr_inc_uphill_iter(iter: *mut RawUphillIter); + fn npnr_deref_uphill_iter(iter: *mut RawUphillIter) -> PipId; + fn npnr_is_uphill_iter_done(iter: *mut RawUphillIter) -> bool; +} + +/// Store for the nets of a context. +pub struct Nets<'a> { + nets: HashMap, + users: HashMap, + index_to_net: Vec, + net_to_index: HashMap<*mut NetInfo, i32>, + _data: PhantomData<&'a Context>, +} + +unsafe impl Send for Nets<'_> {} +unsafe impl Sync for Nets<'_> {} + +impl<'a> Nets<'a> { + /// Create a new store for the nets of a context. + /// + /// Note that this leaks memory created by nextpnr; the intention is this is called once. + pub fn new(ctx: &'a Context) -> Nets<'a> { + let mut names: *mut libc::c_int = std::ptr::null_mut(); + let mut nets_ptr: *mut *mut NetInfo = std::ptr::null_mut(); + let size = unsafe { + npnr_context_nets_leak( + ctx, + &mut names as *mut *mut libc::c_int, + &mut nets_ptr as *mut *mut *mut NetInfo, + ) + }; + let mut nets = HashMap::new(); + let mut users = HashMap::new(); + let mut index_to_net = Vec::new(); + let mut net_to_index = HashMap::new(); + for i in 0..size { + let name = unsafe { IdString(*names.add(i as usize)) }; + let net = unsafe { *nets_ptr.add(i as usize) }; + let mut users_ptr = std::ptr::null_mut(); + // SAFETY: net is not null because it's a &mut, and users is only written to. + // Leaking memory is the most convenient FFI I could think of. + let len = + unsafe { npnr_netinfo_users_leak(net, &mut users_ptr as *mut *mut *mut PortRef) }; + let users_slice = + unsafe { slice::from_raw_parts(users_ptr as *mut &mut PortRef, len as usize) }; + nets.insert(name, net); + users.insert(name, users_slice); + let index = index_to_net.len() as i32; + index_to_net.push(name); + net_to_index.insert(net, index); + unsafe { + npnr_netinfo_udata_set(net, NetIndex(index)); + } + } + // Note: the contents of `names` and `nets_ptr` are now lost. + Self { + nets, + users, + index_to_net, + net_to_index, + _data: PhantomData, + } + } + + /// Find net users given a net's name. + pub fn users_by_name(&self, net: IdString) -> Option<&&[&mut PortRef]> { + self.users.get(&net) + } + + /// Return the number of nets in the store. + pub fn len(&self) -> usize { + self.nets.len() + } + + pub fn name_from_index(&self, index: NetIndex) -> IdString { + self.index_to_net[index.0 as usize] + } + + pub fn net_from_index(&self, index: NetIndex) -> *mut NetInfo { + *self.nets.get(&self.name_from_index(index)).unwrap() + } + + pub fn to_vec(&self) -> Vec<(&IdString, &*mut NetInfo)> { + let mut v = Vec::new(); + v.extend(self.nets.iter()); + v.sort_by_key(|(name, _net)| name.0); + v + } +} + +pub struct NetSinkWireIter<'a> { + ctx: &'a Context, + net: *const NetInfo, + sink: *const PortRef, + n: u32, +} + +impl<'a> Iterator for NetSinkWireIter<'a> { + type Item = WireId; + + fn next(&mut self) -> Option { + let item = + unsafe { npnr_context_get_netinfo_sink_wire(self.ctx, self.net, self.sink, self.n) }; + if item.is_null() { + return None; + } + self.n += 1; + Some(item) + } +} + +#[repr(C)] +struct RawDownhillIter { + content: [u8; 0], +} + +pub struct DownhillPipsIter<'a> { + iter: *mut RawDownhillIter, + phantom_data: std::marker::PhantomData<&'a PipId>, +} + +impl<'a> Iterator for DownhillPipsIter<'a> { + type Item = PipId; + + fn next(&mut self) -> Option { + if unsafe { npnr_is_downhill_iter_done(self.iter) } { + None + } else { + let pip = unsafe { npnr_deref_downhill_iter(self.iter) }; + unsafe { npnr_inc_downhill_iter(self.iter) }; + Some(pip) + } + } +} + +impl<'a> Drop for DownhillPipsIter<'a> { + fn drop(&mut self) { + unsafe { npnr_delete_downhill_iter(self.iter) }; + } +} + +#[repr(C)] +struct RawUphillIter { + content: [u8; 0], +} + +pub struct UphillPipsIter<'a> { + iter: *mut RawUphillIter, + phantom_data: std::marker::PhantomData<&'a PipId>, +} + +impl<'a> Iterator for UphillPipsIter<'a> { + type Item = PipId; + + fn next(&mut self) -> Option { + if unsafe { npnr_is_uphill_iter_done(self.iter) } { + None + } else { + let pip = unsafe { npnr_deref_uphill_iter(self.iter) }; + unsafe { npnr_inc_uphill_iter(self.iter) }; + Some(pip) + } + } +} + +impl<'a> Drop for UphillPipsIter<'a> { + fn drop(&mut self) { + unsafe { npnr_delete_uphill_iter(self.iter) }; + } +} + +macro_rules! log_info { + ($($t:tt)*) => { + let s = std::ffi::CString::new(format!($($t)*)).unwrap(); + unsafe { crate::npnr::npnr_log_info(s.as_ptr()); } + }; +} + +macro_rules! log_error { + ($($t:tt)*) => { + let s = std::ffi::CString::new(format!($($t)*)).unwrap(); + unsafe { crate::npnr::npnr_log_error(s.as_ptr()); } + }; +} + diff --git a/rust/rust.cc b/rust/rust.cc new file mode 100644 index 00000000..33970f27 --- /dev/null +++ b/rust/rust.cc @@ -0,0 +1,323 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2022 Lofty + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "log.h" +#include "nextpnr.h" + +namespace { + USING_NEXTPNR_NAMESPACE; + + template + uint64_t wrap(T thing) { + static_assert(sizeof(T) <= 8, "T is too big for FFI"); + uint64_t b = 0; + memcpy(&b, &thing, sizeof(T)); + return b; + } + + BelId unwrap_bel(uint64_t bel) { + static_assert(sizeof(BelId) <= 8, "T is too big for FFI"); + auto b = BelId(); + memcpy(&b, &bel, sizeof(BelId)); + return b; + } + + PipId unwrap_pip(uint64_t pip) { + static_assert(sizeof(PipId) <= 8, "T is too big for FFI"); + auto p = PipId(); + memcpy(&p, &pip, sizeof(PipId)); + return p; + } + + WireId unwrap_wire(uint64_t wire) { + static_assert(sizeof(WireId) <= 8, "T is too big for FFI"); + auto w = WireId(); + memcpy(&w, &wire, sizeof(WireId)); + return w; + } +} + +using DownhillIter = decltype(Context(ArchArgs()).getPipsDownhill(WireId()).begin()); + +struct DownhillIterWrapper { + DownhillIter current; + DownhillIter end; + + DownhillIterWrapper(DownhillIter begin, DownhillIter end) : current(begin), end(end) {} +}; +using UphillIter = decltype(Context(ArchArgs()).getPipsUphill(WireId()).begin()); + +struct UphillIterWrapper { + UphillIter current; + UphillIter end; + + UphillIterWrapper(UphillIter begin, UphillIter end) : current(begin), end(end) {} +}; + +extern "C" { + USING_NEXTPNR_NAMESPACE; + + /* + DONE: + ctx->bindPip + ctx->bindWire + ctx->check + ctx->debug + ctx->estimateDelay + ctx->getDelayEpsilon + ctx->getPipDstWire + ctx->getPipSrcWire + ctx->getGridDimX + ctx->getGridDimY + ctx->id + ctx->nameOf + ctx->unbindWire + ctx->verbose + + UNNECESSARY: + ctx->getDelayNS - all FFI calls go through it anyway. + + TODO: + ctx->checkPipAvail + ctx->checkPipAvailForNet + ctx->checkWireAvail + ctx->getBelPinType + ctx->getBoundPipNet + ctx->getBoundWireNet + ctx->getNetinfoSinkWire + ctx->getNetinfoSinkWires + ctx->getNetinfoSourceWire + ctx->getPipDelay + ctx->getPipLocation + ctx->getPipsDownhill + ctx->getPipsUphill + ctx->getRouteBoundingBox + ctx->getWireBelPins + ctx->getWireDelay + ctx->getWires + ctx->getWireType + ctx->nameOfPip + ctx->nameOfWire + ctx->nets + ctx->nets.at + ctx->nets.size + ctx->rng64 + ctx->setting + ctx->setting + ctx->setting + ctx->sorted_shuffle + */ + + void npnr_log_info(const char *const format) { log_info("%s", format); } + void npnr_log_error(const char *const format) { log_error("%s", format); } + + uint64_t npnr_belid_null() { return wrap(BelId()); } + uint64_t npnr_wireid_null() { return wrap(WireId()); } + uint64_t npnr_pipid_null() { return wrap(PipId()); } + + int npnr_context_get_grid_dim_x(const Context *const ctx) { return ctx->getGridDimX(); } + int npnr_context_get_grid_dim_y(const Context *const ctx) { return ctx->getGridDimY(); } + void npnr_context_bind_bel(Context *ctx, uint64_t bel, CellInfo* cell, PlaceStrength strength) { return ctx->bindBel(unwrap_bel(bel), cell, strength); } + void npnr_context_unbind_bel(Context *ctx, uint64_t bel) { return ctx->unbindBel(unwrap_bel(bel)); } + bool npnr_context_check_bel_avail(Context *const ctx, uint64_t bel) { return ctx->checkBelAvail(unwrap_bel(bel)); } + void npnr_context_bind_wire(Context *ctx, uint64_t wire, NetInfo* net, PlaceStrength strength) { ctx->bindWire(unwrap_wire(wire), net, strength); } + void npnr_context_unbind_wire(Context *ctx, uint64_t wire) { ctx->unbindWire(unwrap_wire(wire)); } + void npnr_context_bind_pip(Context *ctx, uint64_t pip, NetInfo* net, PlaceStrength strength) { ctx->bindPip(unwrap_pip(pip), net, strength); } + void npnr_context_unbind_pip(Context *ctx, uint64_t pip) { ctx->unbindPip(unwrap_pip(pip)); } + uint64_t npnr_context_get_pip_src_wire(const Context *const ctx, uint64_t pip) { return wrap(ctx->getPipSrcWire(unwrap_pip(pip))); } + uint64_t npnr_context_get_pip_dst_wire(const Context *const ctx, uint64_t pip) { return wrap(ctx->getPipDstWire(unwrap_pip(pip))); } + float npnr_context_estimate_delay(const Context *const ctx, uint64_t src, uint64_t dst) { return ctx->getDelayNS(ctx->estimateDelay(unwrap_wire(src), unwrap_wire(dst))); } + float npnr_context_get_pip_delay(const Context *const ctx, uint64_t pip) { return ctx->getDelayNS(ctx->getPipDelay(unwrap_pip(pip)).maxDelay()); } + float npnr_context_get_wire_delay(const Context *const ctx, uint64_t wire) { return ctx->getDelayNS(ctx->getWireDelay(unwrap_wire(wire)).maxDelay()); } + float npnr_context_delay_epsilon(const Context *const ctx) { return ctx->getDelayNS(ctx->getDelayEpsilon()); } + Loc npnr_context_get_pip_location(const Context *const ctx, uint64_t pip) { return ctx->getPipLocation(unwrap_pip(pip)); } + bool npnr_context_check_pip_avail_for_net(const Context *const ctx, uint64_t pip, NetInfo *net) { return ctx->checkPipAvailForNet(unwrap_pip(pip), net); } + + // This method's in C++ temporarily, while I figure out some better way of getting a pip iterator. + Loc npnr_context_get_pip_direction(const Context *const ctx, uint64_t _pip) { + auto pip = unwrap_pip(_pip); + auto src_loc = Loc{}; + auto dst_loc = Loc{}; + + auto uh_pips = 0; + for (auto uh : ctx->getPipsUphill(ctx->getPipSrcWire(pip))) { + auto loc = ctx->getPipLocation(uh); + src_loc.x += loc.x; + src_loc.y += loc.y; + uh_pips++; + } + if (uh_pips > 1) { + src_loc.x /= uh_pips; + src_loc.y /= uh_pips; + } + + auto dh_pips = 0; + for (auto dh : ctx->getPipsDownhill(ctx->getPipDstWire(pip))) { + auto loc = ctx->getPipLocation(dh); + dst_loc.x += loc.x; + dst_loc.y += loc.y; + dh_pips++; + } + if (dh_pips > 1) { + dst_loc.x /= dh_pips; + dst_loc.y /= dh_pips; + } + + dst_loc.x -= src_loc.x; + dst_loc.y -= src_loc.y; + return dst_loc; + } + + uint64_t npnr_context_get_pips_leak(const Context *const ctx, PipId **pips) { + auto pip_vec = std::vector{}; + for (auto pip : ctx->getPips()) { + pip_vec.push_back(pip); + } + pip_vec.shrink_to_fit(); + auto size = pip_vec.size(); + *pips = pip_vec.data(); + auto dummy = std::vector{}; + // Yes, by memcpying over `pip_vec` we leak memory. + std::memcpy(&pip_vec, &dummy, sizeof(dummy)); + return size; + } + + uint64_t npnr_context_get_wires_leak(const Context *const ctx, WireId **wires) { + auto wire_vec = std::vector{}; + for (auto wire : ctx->getWires()) { + wire_vec.push_back(wire); + } + wire_vec.shrink_to_fit(); + auto size = wire_vec.size(); + *wires = wire_vec.data(); + auto dummy = std::vector{}; + // Yes, by memcpying over `wire_vec` we leak memory. + std::memcpy(&wire_vec, &dummy, sizeof(dummy)); + return size; + } + + void npnr_context_check(const Context *const ctx) { ctx->check(); } + bool npnr_context_debug(const Context *const ctx) { return ctx->debug; } + int npnr_context_id(const Context *const ctx, const char *const str) { return ctx->id(str).hash(); } + const char *npnr_context_name_of(const Context *const ctx, IdString str) { return ctx->nameOf(str); } + const char *npnr_context_name_of_pip(const Context *const ctx, uint64_t pip) { return ctx->nameOfPip(unwrap_pip(pip)); } + const char *npnr_context_name_of_wire(const Context *const ctx, uint64_t wire) { return ctx->nameOfWire(unwrap_wire(wire)); } + bool npnr_context_verbose(const Context *const ctx) { return ctx->verbose; } + + uint64_t npnr_context_get_netinfo_source_wire(const Context *const ctx, const NetInfo *const net) { return wrap(ctx->getNetinfoSourceWire(net)); } + uint64_t npnr_context_get_netinfo_sink_wire(const Context *const ctx, const NetInfo *const net, const PortRef *const sink, uint32_t n) { return wrap(ctx->getNetinfoSinkWire(net, *sink, n)); } + + uint32_t npnr_context_nets_leak(const Context *const ctx, int **names, NetInfo ***nets) { + auto name_vec = std::vector{}; + auto nets_vec = std::vector{}; + for (auto& item : ctx->nets) { + name_vec.push_back(item.first.hash()); + nets_vec.push_back(item.second.get()); + } + name_vec.shrink_to_fit(); + nets_vec.shrink_to_fit(); + auto size = name_vec.size(); + *names = name_vec.data(); + *nets = nets_vec.data(); + // Yes, by memcpying over `name_vec` and `nets_vec` we leak memory. + auto dummy1 = std::vector{}; + auto dummy2 = std::vector{}; + std::memcpy(&name_vec, &dummy1, sizeof(dummy1)); + std::memcpy(&nets_vec, &dummy2, sizeof(dummy2)); + return size; + } + + DownhillIterWrapper *npnr_context_get_pips_downhill(Context *ctx, uint64_t wire_id) { + auto wire = unwrap_wire(wire_id); + auto range = ctx->getPipsDownhill(wire); + return new DownhillIterWrapper(range.begin(), range.end()); + } + void npnr_delete_downhill_iter(DownhillIterWrapper *iter) { + delete iter; + } + UphillIterWrapper *npnr_context_get_pips_uphill(Context *ctx, uint64_t wire_id) { + auto wire = unwrap_wire(wire_id); + auto range = ctx->getPipsUphill(wire); + return new UphillIterWrapper(range.begin(), range.end()); + } + void npnr_delete_uphill_iter(UphillIterWrapper *iter) { + delete iter; + } + + PortRef* npnr_netinfo_driver(NetInfo *const net) { + if (net == nullptr) { + return nullptr; + } + return &net->driver; + } + + uint32_t npnr_netinfo_users_leak(NetInfo *const net, PortRef ***users) { + auto x = std::vector{}; + for (auto& item : net->users) { + x.push_back(&item); + } + x.shrink_to_fit(); + *users = x.data(); + auto size = x.size(); + // Yes, by memcpying over `x` we leak memory. + auto dummy = std::vector{}; + std::memcpy(&x, &dummy, sizeof(dummy)); + return size; + } + +#ifdef ARCH_ECP5 + bool npnr_netinfo_is_global(NetInfo *const net) { return net->is_global; } +#else + bool npnr_netinfo_is_global(NetInfo *const net) { return false; } +#endif + + int32_t npnr_netinfo_udata(NetInfo *const net) { return net->udata; } + void npnr_netinfo_udata_set(NetInfo *const net, int32_t value) { net->udata = value; } + + CellInfo* npnr_portref_cell(const PortRef *const port) { return port->cell; } + Loc npnr_cellinfo_get_location(const CellInfo *const info) { return info->getLocation(); } + + void npnr_inc_downhill_iter(DownhillIterWrapper *iter) { + ++iter->current; + } + uint64_t npnr_deref_downhill_iter(DownhillIterWrapper *iter) { + return wrap(*iter->current); + } + bool npnr_is_downhill_iter_done(DownhillIterWrapper *iter) { + return !(iter->current != iter->end); + } + void npnr_inc_uphill_iter(UphillIterWrapper *iter) { + ++iter->current; + } + uint64_t npnr_deref_uphill_iter(UphillIterWrapper *iter) { + return wrap(*iter->current); + } + bool npnr_is_uphill_iter_done(UphillIterWrapper *iter) { + return !(iter->current != iter->end); + } + + void rust_example_printnets(Context *ctx); +} + +NEXTPNR_NAMESPACE_BEGIN + +void example_printnets(Context *ctx) { + rust_example_printnets(ctx); +} + +NEXTPNR_NAMESPACE_END diff --git a/rust/rust.h b/rust/rust.h new file mode 100644 index 00000000..4830503f --- /dev/null +++ b/rust/rust.h @@ -0,0 +1,34 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2022 Lofty + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef RUST_H +#define RUST_H + +#ifdef USE_RUST + +#include "nextpnr.h" +NEXTPNR_NAMESPACE_BEGIN + +void example_printnets(Context* ctx); + +NEXTPNR_NAMESPACE_END + +#endif + +#endif // RUST_H