Skip to content

Commit

Permalink
Implement compile-time port width verification
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanuppal committed Nov 20, 2024
1 parent cfe7916 commit caf6055
Show file tree
Hide file tree
Showing 9 changed files with 639 additions and 102 deletions.
3 changes: 1 addition & 2 deletions interp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ mod tests;

pub mod flatten;

// ethan: griffin ok'd this
pub use baa::{BitVecOps, BitVecValue};
pub use baa::{BitVecOps, BitVecValue, WidthInt, Word};
27 changes: 16 additions & 11 deletions tools/calyx-ffi-macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]

use std::{env, path::PathBuf};

use parse::{CalyxFFIMacroArgs, CalyxPortDeclaration};
Expand All @@ -9,6 +11,9 @@ mod calyx;
mod parse;
mod util;

// this is super bad, might go out of sync with interp::WidthInt
type WidthInt = u32;

#[proc_macro_attribute]
pub fn calyx_ffi(attrs: TokenStream, item: TokenStream) -> TokenStream {
let source_manifest_dir = PathBuf::from(
Expand Down Expand Up @@ -62,46 +67,46 @@ pub fn calyx_ffi(attrs: TokenStream, item: TokenStream) -> TokenStream {

port_names.push(port_name.clone());

let port_width = port.borrow().width;
let port_width = port.borrow().width as WidthInt;
let width_getter = format_ident!("{}_width", port_name);
width_getters.push(quote! {
pub const fn #width_getter() -> u64 {
#port_width
pub const fn #width_getter() -> calyx_ffi::value::WidthInt {
#port_width as calyx_ffi::value::WidthInt
}
});

default_field_inits.push(quote! {
#port_name: calyx_ffi::value_from_u64::<#port_width>(0)
#port_name: calyx_ffi::value::Value::from(0)
});

// idk why input output ports are being flipped??
match port.borrow().direction.reverse() {
calyx_ir::Direction::Input => {
let setter = format_ident!("set_{}", port_name);
fields.push(quote! {
pub #port_name: calyx_ffi::Value<#port_width>
pub #port_name: calyx_ffi::value::Value<#port_width>
});
setters.push(quote! {
pub fn #setter(&mut self, value: u64) {
self.#port_name = calyx_ffi::value_from_u64::<#port_width>(value);
self.#port_name = calyx_ffi::value::Value::from(value);
}
});
input_names.push(port_name);
}
calyx_ir::Direction::Output => {
fields.push(quote! {
#port_name: calyx_ffi::Value<#port_width>
#port_name: calyx_ffi::value::Value<#port_width>

});

let bitvec_getter = format_ident!("{}_bits", port_name);

getters.push(quote! {
pub fn #port_name(&self) -> u64 {
interp::BitVecOps::to_u64(&self.#port_name).expect("port value wider than 64 bits")
(&self.#port_name).try_into().expect("port value wider than 64 bits")
}

pub const fn #bitvec_getter(&self) -> &calyx_ffi::Value<#port_width> {
pub const fn #bitvec_getter(&self) -> &calyx_ffi::value::Value<#port_width> {
&self.#port_name
}
});
Expand Down Expand Up @@ -186,7 +191,7 @@ pub fn calyx_ffi(attrs: TokenStream, item: TokenStream) -> TokenStream {
let name_bits = format_ident!("{}_bits", &name);

getters.push(quote! {
fn #name_bits(&self) -> &calyx_ffi::Value<#width> {
fn #name_bits(&self) -> &calyx_ffi::value::Value<#width> {
&self.#name
}

Expand All @@ -202,7 +207,7 @@ pub fn calyx_ffi(attrs: TokenStream, item: TokenStream) -> TokenStream {
let setter = format_ident!("set_{}", name);

setters.push(quote! {
fn #name_bits(&mut self) -> &mut calyx_ffi::Value<#width> {
fn #name_bits(&mut self) -> &mut calyx_ffi::value::Value<#width> {
&mut self.#name
}

Expand Down
8 changes: 4 additions & 4 deletions tools/calyx-ffi/src/backend/cider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,22 @@ macro_rules! cider_ffi_backend {
// println!("cider_ffi_backend tick");
let cider = unsafe { $dut.user_data.assume_init_mut() };
$(
cider.write_port(stringify!($input), &$dut.$input);
cider.write_port(stringify!($input), &$dut.$input.inner);
)*
cider.step();
$(
$dut.$output = cider.read_port(stringify!($output));
$dut.$output.inner = cider.read_port(stringify!($output));
)*
};
(@go $dut:ident; $($input:ident),*; $($output:ident),*) => {
// println!("cider_ffi_backend go");
let cider = unsafe { $dut.user_data.assume_init_mut() };
$(
cider.write_port(stringify!($input), &$dut.$input);
cider.write_port(stringify!($input), &$dut.$input.inner);
)*
cider.go();
$(
$dut.$output = cider.read_port(stringify!($output));
$dut.$output.inner = cider.read_port(stringify!($output));
)*
};
}
73 changes: 73 additions & 0 deletions tools/calyx-ffi/src/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use calyx_ir::Context;
use std::{
any, cell::RefCell, collections::HashMap, env, path::PathBuf, rc::Rc,
};

/// A non-combinational calyx component.
pub trait CalyxFFIComponent: any::Any {
/// The path to the component source file. Must be a constant expression.
fn path(&self) -> &'static str;

/// The in-source name of this component. Must be a constant expression.
fn name(&self) -> &'static str;

/// Internal initialization routine. Do not call!
fn init(&mut self, context: &Context);

/// Resets this component.
fn reset(&mut self);

/// Whether this component's backend supports ticking.
fn can_tick(&self) -> bool;

/// Advances this component by one clock cycle. May not always be available, so check [`has_tick`]([CalyxFFIComponent::has_tick]).
fn tick(&mut self);

/// Calls this component, blocking until it is done executing.
fn go(&mut self);
}

pub type CalyxFFIComponentRef = Rc<RefCell<dyn CalyxFFIComponent>>;

fn box_calyx_ffi_component<T: CalyxFFIComponent>(
comp: T,
) -> CalyxFFIComponentRef {
Rc::new(RefCell::new(comp))
}

#[derive(Default)]
pub struct CalyxFFI {
contexts: HashMap<&'static str, Context>,
}

impl CalyxFFI {
pub fn new() -> Self {
Self::default()
}

/// Constructs a new calyx component of the given type.
///
/// The `path` implementation for `CalyxFFIComponent` must be a constant
/// expression and should derived via the `calyx_ffi` procedural macro.
pub fn new_comp<T: CalyxFFIComponent + Default>(
&mut self,
) -> CalyxFFIComponentRef {
let mut comp = T::default();
let path = comp.path();
let context = self.contexts.entry(path).or_insert_with_key(|path| {
// there has to be a better way to find lib
let home_dir = env::var("HOME").expect("user home not set");
let mut lib_path = PathBuf::from(home_dir);
lib_path.push(".calyx");
let ws = calyx_frontend::Workspace::construct(
&Some(path.into()),
&lib_path,
)
.expect("couldn't parse calyx");
calyx_ir::from_ast::ast_to_ir(ws)
.expect("couldn't construct calyx ir")
});
comp.init(context);
box_calyx_ffi_component(comp)
}
}
82 changes: 2 additions & 80 deletions tools/calyx-ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,4 @@
use calyx_ir::Context;
use std::{
any, cell::RefCell, collections::HashMap, env, path::PathBuf, rc::Rc,
};

pub mod backend;
pub mod interface;
pub mod prelude;

/// A non-combinational calyx component.
pub trait CalyxFFIComponent: any::Any {
/// The path to the component source file. Must be a constant expression.
fn path(&self) -> &'static str;

/// The in-source name of this component. Must be a constant expression.
fn name(&self) -> &'static str;

/// Internal initialization routine. Do not call!
fn init(&mut self, context: &Context);

/// Resets this component.
fn reset(&mut self);

/// Whether this component's backend supports ticking.
fn can_tick(&self) -> bool;

/// Advances this component by one clock cycle. May not always be available, so check [`has_tick`]([CalyxFFIComponent::has_tick]).
fn tick(&mut self);

/// Calls this component, blocking until it is done executing.
fn go(&mut self);
}

pub type CalyxFFIComponentRef = Rc<RefCell<dyn CalyxFFIComponent>>;

fn box_calyx_ffi_component<T: CalyxFFIComponent>(
comp: T,
) -> CalyxFFIComponentRef {
Rc::new(RefCell::new(comp))
}

#[derive(Default)]
pub struct CalyxFFI {
contexts: HashMap<&'static str, Context>,
}

impl CalyxFFI {
pub fn new() -> Self {
Self::default()
}

/// Constructs a new calyx component of the given type.
///
/// The `path` implementation for `CalyxFFIComponent` must be a constant
/// expression and should derived via the `calyx_ffi` procedural macro.
pub fn new_comp<T: CalyxFFIComponent + Default>(
&mut self,
) -> CalyxFFIComponentRef {
let mut comp = T::default();
let path = comp.path();
let context = self.contexts.entry(path).or_insert_with_key(|path| {
// there has to be a better way to find lib
let home_dir = env::var("HOME").expect("user home not set");
let mut lib_path = PathBuf::from(home_dir);
lib_path.push(".calyx");
let ws = calyx_frontend::Workspace::construct(
&Some(path.into()),
&lib_path,
)
.expect("couldn't parse calyx");
calyx_ir::from_ast::ast_to_ir(ws)
.expect("couldn't construct calyx ir")
});
comp.init(context);
box_calyx_ffi_component(comp)
}
}

pub type Value<const N: u64> = interp::BitVecValue;

pub fn value_from_u64<const N: u64>(value: u64) -> Value<N> {
Value::from_u64(value, N as u32)
}
pub mod value;
7 changes: 4 additions & 3 deletions tools/calyx-ffi/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use super::{
value_from_u64, CalyxFFI, CalyxFFIComponent, CalyxFFIComponentRef, Value,
interface::{CalyxFFI, CalyxFFIComponent, CalyxFFIComponentRef},
value::Value,
};
pub use calyx_ffi_macro::{calyx_ffi, calyx_ffi_test, calyx_ffi_tests};
pub use calyx_ir;
Expand All @@ -20,12 +21,12 @@ macro_rules! declare_interface {
calyx_ffi::prelude::paste::paste! {
pub trait $name: CalyxFFIComponent {
$(
fn [<$input _bits>](&mut self) -> &mut calyx_ffi::Value<$input_width>;
fn [<$input _bits>](&mut self) -> &mut calyx_ffi::value::Value<$input_width>;

fn [<set_ $input>](&mut self, value: u64);
)*
$(
fn [<$output _bits>](&self) -> &calyx_ffi::Value<$output_width>;
fn [<$output _bits>](&self) -> &calyx_ffi::value::Value<$output_width>;

fn $output(&self) -> u64;
)*
Expand Down
58 changes: 58 additions & 0 deletions tools/calyx-ffi/src/value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::{error, fmt};

pub use interp::WidthInt;

#[derive(Debug)]
pub enum ValueConversionError {
WidthTooLarge(interp::WidthInt),
}

impl fmt::Display for ValueConversionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueConversionError::WidthTooLarge(width) => {
write!(
f,
"Failed to convert bitvector of width `{}` into `u64`",
width
)
}
}
}
}

impl error::Error for ValueConversionError {}

#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
pub struct Value<const N: interp::WidthInt> {
pub inner: interp::BitVecValue,
}

impl<const N: interp::WidthInt> From<u64> for Value<N> {
fn from(value: u64) -> Self {
Self {
inner: interp::BitVecValue::from_u64(value, N),
}
}
}

impl<const N: interp::WidthInt> TryInto<u64> for &Value<N> {
type Error = ValueConversionError;

fn try_into(self) -> Result<u64, Self::Error> {
use interp::BitVecOps;
self.inner
.to_u64()
.ok_or(Self::Error::WidthTooLarge(self.inner.width()))
}
}

impl<const N: interp::WidthInt> interp::BitVecOps for Value<N> {
fn width(&self) -> interp::WidthInt {
N
}

fn words(&self) -> &[interp::Word] {
self.inner.words()
}
}
Loading

0 comments on commit caf6055

Please sign in to comment.