diff --git a/interp/src/debugger/debugger_core.rs b/interp/src/debugger/debugger_core.rs index b9e4ffe81..863d42b4a 100644 --- a/interp/src/debugger/debugger_core.rs +++ b/interp/src/debugger/debugger_core.rs @@ -77,7 +77,7 @@ pub struct Debugger + Clone> { } /// A type alias for the debugger using an Rc of the context. Use this in cases -/// where the use of lifetimes would be a hinderance. +/// where the use of lifetimes would be a hindrance. pub type OwnedDebugger = Debugger>; impl OwnedDebugger { @@ -94,7 +94,7 @@ impl OwnedDebugger { )?; let debugger: Debugger> = - Self::new(Rc::new(ctx), &None, &None)?; + Self::new(Rc::new(ctx), &None, &None, false)?; Ok((debugger, map)) } @@ -106,11 +106,13 @@ impl + Clone> Debugger { program_context: C, data_file: &Option, wave_file: &Option, + check_data_races: bool, ) -> InterpreterResult { let mut interpreter = Simulator::build_simulator( program_context.clone(), data_file, wave_file, + check_data_races, )?; interpreter.converge()?; diff --git a/interp/src/errors.rs b/interp/src/errors.rs index c0088d8a2..4bb14f109 100644 --- a/interp/src/errors.rs +++ b/interp/src/errors.rs @@ -3,11 +3,12 @@ use crate::flatten::{ base::{AssignmentWinner, ComponentIdx, GlobalCellIdx, GlobalPortIdx}, prelude::AssignedValue, }, - structures::environment::Environment, + structures::environment::{clock::ClockError, Environment}, }; use baa::{BitVecOps, BitVecValue}; use calyx_ir::Id; use calyx_utils::{Error as CalyxError, MultiError as CalyxMultiError}; +use owo_colors::OwoColorize; use rustyline::error::ReadlineError; use thiserror::Error; @@ -193,10 +194,16 @@ pub enum InterpreterError { )] UndefinedReadAddr(GlobalCellIdx), + #[error("Attempted to undefine a defined port \"{0:?}\"")] + UndefiningDefinedPort(GlobalPortIdx), + /// A wrapper for serialization errors #[error(transparent)] SerializationError(#[from] crate::serialization::SerializationError), + #[error(transparent)] + ClockError(#[from] ClockError), + /// A nonspecific error, used for arbitrary messages #[error("{0}")] GenericError(String), @@ -295,6 +302,14 @@ impl InterpreterError { InterpreterError::UndefinedWrite(c) => InterpreterError::GenericError(format!("Attempted to write an undefined value to register or memory named \"{}\"", env.get_full_name(c))), InterpreterError::UndefinedWriteAddr(c) => InterpreterError::GenericError(format!("Attempted to write to an undefined memory address in memory named \"{}\"", env.get_full_name(c))), InterpreterError::UndefinedReadAddr(c) => InterpreterError::GenericError(format!("Attempted to read from an undefined memory address from memory named \"{}\"", env.get_full_name(c))), + InterpreterError::ClockError(clk) => { + match clk { + ClockError::ReadWrite(c) => InterpreterError::GenericError(format!("Concurrent read & write to the same register/memory {}", env.get_full_name(c).underline())), + ClockError::WriteWrite(c) => InterpreterError::GenericError(format!("Concurrent writes to the same register/memory {}", env.get_full_name(c).underline())), + _ => InterpreterError::ClockError(clk), + } + } + InterpreterError::UndefiningDefinedPort(p) => InterpreterError::GenericError(format!("Attempted to undefine a defined port \"{}\"", env.get_full_name(p))), e => e, } } diff --git a/interp/src/flatten/flat_ir/base.rs b/interp/src/flatten/flat_ir/base.rs index 9f612d275..6326adfe8 100644 --- a/interp/src/flatten/flat_ir/base.rs +++ b/interp/src/flatten/flat_ir/base.rs @@ -5,8 +5,10 @@ use std::{ use super::{cell_prototype::CellPrototype, prelude::Identifier}; use crate::{ - flatten::structures::index_trait::{ - impl_index, impl_index_nonzero, IndexRange, IndexRef, + flatten::structures::{ + environment::clock::ClockPair, + index_trait::{impl_index, impl_index_nonzero, IndexRange, IndexRef}, + thread::ThreadIdx, }, serialization::PrintCode, }; @@ -424,6 +426,8 @@ impl From for AssignmentWinner { pub struct AssignedValue { val: BitVecValue, winner: AssignmentWinner, + thread: Option, + clocks: Option, } impl std::fmt::Debug for AssignedValue { @@ -431,6 +435,7 @@ impl std::fmt::Debug for AssignedValue { f.debug_struct("AssignedValue") .field("val", &self.val.to_bit_str()) .field("winner", &self.winner) + .field("thread", &self.thread) .finish() } } @@ -448,9 +453,26 @@ impl AssignedValue { Self { val, winner: winner.into(), + thread: None, + clocks: None, } } + pub fn with_thread(mut self, thread: ThreadIdx) -> Self { + self.thread = Some(thread); + self + } + + pub fn with_thread_optional(mut self, thread: Option) -> Self { + self.thread = thread; + self + } + + pub fn with_clocks(mut self, clock_pair: ClockPair) -> Self { + self.clocks = Some(clock_pair); + self + } + /// Returns true if the two AssignedValues do not have the same winner pub fn has_conflict_with(&self, other: &Self) -> bool { self.winner != other.winner @@ -472,6 +494,8 @@ impl AssignedValue { Self { val: BitVecValue::tru(), winner: AssignmentWinner::Implicit, + thread: None, + clocks: None, } } @@ -482,6 +506,8 @@ impl AssignedValue { Self { val, winner: AssignmentWinner::Cell, + thread: None, + clocks: None, } } @@ -492,6 +518,8 @@ impl AssignedValue { Self { val, winner: AssignmentWinner::Implicit, + thread: None, + clocks: None, } } @@ -507,6 +535,14 @@ impl AssignedValue { pub fn cell_b_low() -> Self { Self::cell_value(BitVecValue::fals()) } + + pub fn thread(&self) -> Option { + self.thread + } + + pub fn clocks(&self) -> Option<&ClockPair> { + self.clocks.as_ref() + } } #[derive(Debug, Clone, Default)] @@ -539,6 +575,20 @@ impl PortValue { self.0.as_ref() } + pub fn with_thread(mut self, thread: ThreadIdx) -> Self { + if let Some(val) = self.0.as_mut() { + val.thread = Some(thread); + } + self + } + + pub fn with_thread_optional(mut self, thread: Option) -> Self { + if let Some(val) = self.0.as_mut() { + val.thread = thread; + } + self + } + /// If the value is defined, returns the value cast to a boolean. Otherwise /// returns `None`. It will panic if the given value is not one bit wide. pub fn as_bool(&self) -> Option { @@ -551,12 +601,20 @@ impl PortValue { self.0.as_ref().map(|x| x.val().to_u64().unwrap()) } + pub fn is_zero(&self) -> Option { + self.0.as_ref().map(|x| x.val.is_zero()) + } + /// Returns a reference to the underlying value if it is defined. Otherwise /// returns `None`. pub fn val(&self) -> Option<&BitVecValue> { self.0.as_ref().map(|x| &x.val) } + pub fn clocks(&self) -> Option { + self.0.as_ref().and_then(|x| x.clocks) + } + /// Returns a reference to the underlying [`AssignmentWinner`] if it is /// defined. Otherwise returns `None`. pub fn winner(&self) -> Option<&AssignmentWinner> { diff --git a/interp/src/flatten/flat_ir/control/translator.rs b/interp/src/flatten/flat_ir/control/translator.rs index 8e8a1a70e..bd8272072 100644 --- a/interp/src/flatten/flat_ir/control/translator.rs +++ b/interp/src/flatten/flat_ir/control/translator.rs @@ -117,7 +117,43 @@ fn translate_guard( interp_ctx: &mut InterpretationContext, map: &PortMapper, ) -> GuardIdx { - flatten_tree(guard, None, &mut interp_ctx.guards, map) + let idx = flatten_tree(guard, None, &mut interp_ctx.guards, map); + + // not worth trying to force this search traversal into the flatten trait so + // I'm just gonna opt for this. It's a onetime cost, so I'm not particularly + // worried about it + let mut search_stack = vec![idx]; + let mut read_ports = vec![]; + + while let Some(idx) = search_stack.pop() { + match &interp_ctx.guards[idx] { + Guard::True => {} + Guard::Or(guard_idx, guard_idx1) => { + search_stack.push(*guard_idx); + search_stack.push(*guard_idx1); + } + Guard::And(guard_idx, guard_idx1) => { + search_stack.push(*guard_idx); + search_stack.push(*guard_idx1); + } + Guard::Not(guard_idx) => { + search_stack.push(*guard_idx); + } + Guard::Comp(_port_comp, port_ref, port_ref1) => { + read_ports.push(*port_ref); + read_ports.push(*port_ref1); + } + Guard::Port(port_ref) => { + read_ports.push(*port_ref); + } + } + } + + if !read_ports.is_empty() { + interp_ctx.guard_read_map.insert_value(idx, read_ports); + } + + idx } fn translate_component( @@ -287,8 +323,17 @@ fn insert_port( local_offset.into() } ContainmentType::Local => { - let idx_definition = - secondary_ctx.push_local_port(id, port.borrow().width as usize); + let borrow = port.borrow(); + let is_control = borrow.has_attribute(calyx_ir::NumAttr::Go) + || borrow.has_attribute(calyx_ir::NumAttr::Done) + || borrow.has_attribute(calyx_ir::BoolAttr::Control) + || (borrow.direction == calyx_ir::Direction::Inout); + + let idx_definition = secondary_ctx.push_local_port( + id, + port.borrow().width as usize, + is_control, + ); let local_offset = aux.port_offset_map.insert(idx_definition); local_offset.into() } diff --git a/interp/src/flatten/primitives/builder.rs b/interp/src/flatten/primitives/builder.rs index db10e6657..6c35e2ef3 100644 --- a/interp/src/flatten/primitives/builder.rs +++ b/interp/src/flatten/primitives/builder.rs @@ -1,6 +1,9 @@ use ahash::HashSet; -use super::{combinational::*, stateful::*, Primitive}; +use super::{ + combinational::*, prim_trait::RaceDetectionPrimitive, stateful::*, + Primitive, +}; use crate::{ flatten::{ flat_ir::{ @@ -11,7 +14,10 @@ use crate::{ }, prelude::{CellInfo, GlobalPortIdx}, }, - structures::context::Context, + structures::{ + context::Context, + environment::{clock::ClockMap, CellLedger}, + }, }, serialization::DataDump, }; @@ -27,8 +33,10 @@ pub fn build_primitive( ctx: &Context, dump: &Option, memories_initialized: &mut HashSet, -) -> Box { - match &prim.prototype { + // if the clock map is not provided then data race checking is disabled + mut clocks: Option<&mut ClockMap>, +) -> CellLedger { + let b: Box = match &prim.prototype { CellPrototype::Constant { value: val, width, @@ -41,74 +49,84 @@ pub fn build_primitive( CellPrototype::Component(_) => unreachable!( "Build primitive erroneously called on a calyx component" ), - CellPrototype::SingleWidth { op, width } => match op { - SingleWidthType::Reg => { - Box::new(StdReg::new(base_port, cell_idx, *width)) - } - SingleWidthType::Not => Box::new(StdNot::new(base_port)), - SingleWidthType::And => Box::new(StdAnd::new(base_port)), - SingleWidthType::Or => Box::new(StdOr::new(base_port)), - SingleWidthType::Xor => Box::new(StdXor::new(base_port)), - SingleWidthType::Add => Box::new(StdAdd::new(base_port)), - SingleWidthType::Sub => Box::new(StdSub::new(base_port)), - SingleWidthType::Gt => Box::new(StdGt::new(base_port)), - SingleWidthType::Lt => Box::new(StdLt::new(base_port)), - SingleWidthType::Eq => Box::new(StdEq::new(base_port)), - SingleWidthType::Neq => Box::new(StdNeq::new(base_port)), - SingleWidthType::Ge => Box::new(StdGe::new(base_port)), - SingleWidthType::Le => Box::new(StdLe::new(base_port)), - SingleWidthType::Lsh => Box::new(StdLsh::new(base_port)), - SingleWidthType::Rsh => Box::new(StdRsh::new(base_port)), - SingleWidthType::Mux => Box::new(StdMux::new(base_port)), - SingleWidthType::Wire => Box::new(StdWire::new(base_port)), - SingleWidthType::SignedAdd => Box::new(StdAdd::new(base_port)), - SingleWidthType::SignedSub => Box::new(StdSub::new(base_port)), - SingleWidthType::SignedGt => Box::new(StdSgt::new(base_port)), - SingleWidthType::SignedLt => Box::new(StdSlt::new(base_port)), - SingleWidthType::SignedEq => Box::new(StdSeq::new(base_port)), - SingleWidthType::SignedNeq => Box::new(StdSneq::new(base_port)), - SingleWidthType::SignedGe => Box::new(StdSge::new(base_port)), - SingleWidthType::SignedLe => Box::new(StdSle::new(base_port)), - SingleWidthType::SignedLsh => Box::new(StdSlsh::new(base_port)), - SingleWidthType::SignedRsh => Box::new(StdSrsh::new(base_port)), - SingleWidthType::MultPipe => { - Box::new(StdMultPipe::<2>::new(base_port, *width)) - } - SingleWidthType::SignedMultPipe => { - // todo: Check if this is actually okay - Box::new(StdMultPipe::<2>::new(base_port, *width)) - } - SingleWidthType::DivPipe => { - Box::new(StdDivPipe::<2, false>::new(base_port, *width)) - } - SingleWidthType::SignedDivPipe => { - Box::new(StdDivPipe::<2, true>::new(base_port, *width)) - } - SingleWidthType::Sqrt => { - Box::new(Sqrt::::new(base_port, *width, None)) - } - SingleWidthType::UnsynMult => { - Box::new(StdUnsynMult::new(base_port)) + CellPrototype::SingleWidth { op, width } => { + match op { + SingleWidthType::Reg => { + let b = + StdReg::new(base_port, cell_idx, *width, &mut clocks); + if clocks.is_some() { + let b: Box = Box::new(b); + return b.into(); + } else { + let b: Box = Box::new(b); + return b.into(); + } + } + SingleWidthType::Not => Box::new(StdNot::new(base_port)), + SingleWidthType::And => Box::new(StdAnd::new(base_port)), + SingleWidthType::Or => Box::new(StdOr::new(base_port)), + SingleWidthType::Xor => Box::new(StdXor::new(base_port)), + SingleWidthType::Add => Box::new(StdAdd::new(base_port)), + SingleWidthType::Sub => Box::new(StdSub::new(base_port)), + SingleWidthType::Gt => Box::new(StdGt::new(base_port)), + SingleWidthType::Lt => Box::new(StdLt::new(base_port)), + SingleWidthType::Eq => Box::new(StdEq::new(base_port)), + SingleWidthType::Neq => Box::new(StdNeq::new(base_port)), + SingleWidthType::Ge => Box::new(StdGe::new(base_port)), + SingleWidthType::Le => Box::new(StdLe::new(base_port)), + SingleWidthType::Lsh => Box::new(StdLsh::new(base_port)), + SingleWidthType::Rsh => Box::new(StdRsh::new(base_port)), + SingleWidthType::Mux => Box::new(StdMux::new(base_port)), + SingleWidthType::Wire => Box::new(StdWire::new(base_port)), + SingleWidthType::SignedAdd => Box::new(StdAdd::new(base_port)), + SingleWidthType::SignedSub => Box::new(StdSub::new(base_port)), + SingleWidthType::SignedGt => Box::new(StdSgt::new(base_port)), + SingleWidthType::SignedLt => Box::new(StdSlt::new(base_port)), + SingleWidthType::SignedEq => Box::new(StdSeq::new(base_port)), + SingleWidthType::SignedNeq => Box::new(StdSneq::new(base_port)), + SingleWidthType::SignedGe => Box::new(StdSge::new(base_port)), + SingleWidthType::SignedLe => Box::new(StdSle::new(base_port)), + SingleWidthType::SignedLsh => Box::new(StdSlsh::new(base_port)), + SingleWidthType::SignedRsh => Box::new(StdSrsh::new(base_port)), + SingleWidthType::MultPipe => { + Box::new(StdMultPipe::<2>::new(base_port, *width)) + } + SingleWidthType::SignedMultPipe => { + // todo: Check if this is actually okay + Box::new(StdMultPipe::<2>::new(base_port, *width)) + } + SingleWidthType::DivPipe => { + Box::new(StdDivPipe::<2, false>::new(base_port, *width)) + } + SingleWidthType::SignedDivPipe => { + Box::new(StdDivPipe::<2, true>::new(base_port, *width)) + } + SingleWidthType::Sqrt => { + Box::new(Sqrt::::new(base_port, *width, None)) + } + SingleWidthType::UnsynMult => { + Box::new(StdUnsynMult::new(base_port)) + } + SingleWidthType::UnsynDiv => { + Box::new(StdUnsynDiv::new(base_port, *width)) + } + SingleWidthType::UnsynMod => { + Box::new(StdUnsynMod::new(base_port, *width)) + } + SingleWidthType::UnsynSMult => { + Box::new(StdUnsynSmult::new(base_port)) + } + SingleWidthType::UnsynSDiv => { + Box::new(StdUnsynSdiv::new(base_port, *width)) + } + SingleWidthType::UnsynSMod => { + Box::new(StdUnsynSmod::new(base_port, *width)) + } + SingleWidthType::Undef => { + Box::new(StdUndef::new(base_port, *width)) + } } - SingleWidthType::UnsynDiv => { - Box::new(StdUnsynDiv::new(base_port, *width)) - } - SingleWidthType::UnsynMod => { - Box::new(StdUnsynMod::new(base_port, *width)) - } - SingleWidthType::UnsynSMult => { - Box::new(StdUnsynSmult::new(base_port)) - } - SingleWidthType::UnsynSDiv => { - Box::new(StdUnsynSdiv::new(base_port, *width)) - } - SingleWidthType::UnsynSMod => { - Box::new(StdUnsynSmod::new(base_port, *width)) - } - SingleWidthType::Undef => { - Box::new(StdUndef::new(base_port, *width)) - } - }, + } CellPrototype::FixedPoint { op, width, @@ -171,29 +189,74 @@ pub fn build_primitive( }); match mem_type { - MemType::Seq => Box::new(if let Some(data) = data { - memories_initialized - .insert(ctx.resolve_id(prim.name).clone()); - SeqMem::new_with_init( - base_port, cell_idx, *width, false, dims, data, - ) - } else { - SeqMemD1::new(base_port, cell_idx, *width, false, dims) - }), - MemType::Std => Box::new(if let Some(data) = data { - memories_initialized - .insert(ctx.resolve_id(prim.name).clone()); - CombMem::new_with_init( - base_port, cell_idx, *width, false, dims, data, - ) - } else { - CombMem::new(base_port, cell_idx, *width, false, dims) - }), + MemType::Seq => { + let b = if let Some(data) = data { + memories_initialized + .insert(ctx.resolve_id(prim.name).clone()); + SeqMem::new_with_init( + base_port, + cell_idx, + *width, + false, + dims, + data, + &mut clocks, + ) + } else { + SeqMemD1::new( + base_port, + cell_idx, + *width, + false, + dims, + &mut clocks, + ) + }; + if clocks.is_some() { + let b: Box = Box::new(b); + return b.into(); + } else { + let b: Box = Box::new(b); + return b.into(); + } + } + MemType::Std => { + let b = if let Some(data) = data { + memories_initialized + .insert(ctx.resolve_id(prim.name).clone()); + CombMem::new_with_init( + base_port, + cell_idx, + *width, + false, + dims, + data, + &mut clocks, + ) + } else { + CombMem::new( + base_port, + cell_idx, + *width, + false, + dims, + &mut clocks, + ) + }; + if clocks.is_some() { + let b: Box = Box::new(b); + return b.into(); + } else { + let b: Box = Box::new(b); + return b.into(); + } + } } } CellPrototype::Unknown(s, _) => { todo!("Primitives {s} not yet implemented") } - } + }; + b.into() } diff --git a/interp/src/flatten/primitives/combinational.rs b/interp/src/flatten/primitives/combinational.rs index a581de244..9c174f9f6 100644 --- a/interp/src/flatten/primitives/combinational.rs +++ b/interp/src/flatten/primitives/combinational.rs @@ -25,6 +25,7 @@ impl StdConst { impl Primitive for StdConst { fn exec_comb(&self, port_map: &mut PortMap) -> UpdateResult { Ok(if port_map[self.out].is_undef() { + // A constant cannot meaningfully be said to belong to a given thread port_map[self.out] = PortValue::new_cell(self.value.clone()); UpdateStatus::Changed } else { diff --git a/interp/src/flatten/primitives/prim_trait.rs b/interp/src/flatten/primitives/prim_trait.rs index 6519b7229..80f4852fc 100644 --- a/interp/src/flatten/primitives/prim_trait.rs +++ b/interp/src/flatten/primitives/prim_trait.rs @@ -1,8 +1,13 @@ use crate::{ errors::InterpreterResult, - flatten::{flat_ir::base::GlobalPortIdx, structures::environment::PortMap}, - serialization::PrintCode, - serialization::Serializable, + flatten::{ + flat_ir::base::GlobalPortIdx, + structures::{ + environment::{clock::ClockMap, PortMap}, + thread::ThreadMap, + }, + }, + serialization::{PrintCode, Serializable}, }; use baa::BitVecValue; @@ -131,6 +136,30 @@ pub trait Primitive { } } +pub trait RaceDetectionPrimitive: Primitive { + fn exec_comb_checked( + &self, + port_map: &mut PortMap, + _clock_map: &mut ClockMap, + _thread_map: &ThreadMap, + ) -> UpdateResult { + self.exec_comb(port_map) + } + + fn exec_cycle_checked( + &mut self, + port_map: &mut PortMap, + _clock_map: &mut ClockMap, + _thread_map: &ThreadMap, + ) -> UpdateResult { + self.exec_cycle(port_map) + } + + /// Get a reference to the underlying primitive. Unfortunately cannot add an + /// optional default implementation due to size rules + fn as_primitive(&self) -> &dyn Primitive; +} + /// An empty primitive implementation used for testing. It does not do anything /// and has no ports of any kind pub struct DummyPrimitive; diff --git a/interp/src/flatten/primitives/stateful/memories.rs b/interp/src/flatten/primitives/stateful/memories.rs index b4753a9ec..de5bd12a6 100644 --- a/interp/src/flatten/primitives/stateful/memories.rs +++ b/interp/src/flatten/primitives/stateful/memories.rs @@ -9,10 +9,18 @@ use crate::{ }, primitives::{ declare_ports, make_getters, ports, - prim_trait::{UpdateResult, UpdateStatus}, + prim_trait::{RaceDetectionPrimitive, UpdateResult, UpdateStatus}, + utils::infer_thread_id, Primitive, }, - structures::{environment::PortMap, index_trait::IndexRef}, + structures::{ + environment::{ + clock::{new_clock, ClockMap, ValueWithClock}, + PortMap, + }, + index_trait::IndexRef, + thread::{ThreadIdx, ThreadMap}, + }, }, serialization::{Entry, PrintCode, Serializable, Shape}, }; @@ -21,7 +29,7 @@ use baa::{BitVecOps, BitVecValue, WidthInt}; pub struct StdReg { base_port: GlobalPortIdx, - internal_state: BitVecValue, + internal_state: ValueWithClock, global_idx: GlobalCellIdx, done_is_high: bool, } @@ -33,8 +41,10 @@ impl StdReg { base_port: GlobalPortIdx, global_idx: GlobalCellIdx, width: u32, + clocks: &mut Option<&mut ClockMap>, ) -> Self { - let internal_state = BitVecValue::zero(width); + let internal_state = + ValueWithClock::zero(width, new_clock(clocks), new_clock(clocks)); Self { base_port, global_idx, @@ -55,14 +65,14 @@ impl Primitive for StdReg { ]; let done_port = if port_map[reset].as_bool().unwrap_or_default() { - self.internal_state = - BitVecValue::zero(self.internal_state.width()); + self.internal_state.value = + BitVecValue::zero(self.internal_state.value.width()); port_map.insert_val( done, AssignedValue::cell_value(BitVecValue::fals()), )? } else if port_map[write_en].as_bool().unwrap_or_default() { - self.internal_state = port_map[input] + self.internal_state.value = port_map[input] .as_option() .ok_or(InterpreterError::UndefinedWrite(self.global_idx))? .val() @@ -73,9 +83,6 @@ impl Primitive for StdReg { port_map.insert_val( done, AssignedValue::cell_value(BitVecValue::tru()), - )? | port_map.insert_val( - out_idx, - AssignedValue::cell_value(self.internal_state.clone()), )? } else { self.done_is_high = false; @@ -88,7 +95,8 @@ impl Primitive for StdReg { Ok(done_port | port_map.insert_val( out_idx, - AssignedValue::cell_value(self.internal_state.clone()), + AssignedValue::cell_value(self.internal_state.value.clone()) + .with_clocks(self.internal_state.clocks), )?) } @@ -96,9 +104,11 @@ impl Primitive for StdReg { ports![&self.base_port; done: Self::DONE, out_idx: Self::OUT]; + let out_signal = port_map.insert_val( out_idx, - AssignedValue::cell_value(self.internal_state.clone()), + AssignedValue::cell_value(self.internal_state.value.clone()) + .with_clocks(self.internal_state.clocks), )?; let done_signal = port_map.insert_val( done, @@ -114,7 +124,7 @@ impl Primitive for StdReg { fn serialize(&self, code: Option) -> Serializable { Serializable::Val(Entry::from_val_code( - &self.internal_state, + &self.internal_state.value, &code.unwrap_or_default(), )) } @@ -124,7 +134,45 @@ impl Primitive for StdReg { } fn dump_memory_state(&self) -> Option> { - Some(self.internal_state.clone().to_bytes_le()) + Some(self.internal_state.value.clone().to_bytes_le()) + } +} + +impl RaceDetectionPrimitive for StdReg { + fn as_primitive(&self) -> &dyn Primitive { + self + } + + fn exec_cycle_checked( + &mut self, + port_map: &mut PortMap, + clock_map: &mut ClockMap, + thread_map: &ThreadMap, + ) -> UpdateResult { + ports![&self.base_port; + input: Self::IN, + write_en: Self::WRITE_EN, + reset: Self::RESET + ]; + + // If we are writing to the reg, check that the write is not concurrent + // with another write or a read. We can't easily check if the reg is + // being read. + if port_map[write_en].as_bool().unwrap_or_default() { + let thread = infer_thread_id( + [&port_map[input], &port_map[write_en], &port_map[reset]] + .into_iter(), + ) + .expect("Could not infer thread id for reg"); + + let current_clock_idx = thread_map.unwrap_clock_id(thread); + self.internal_state + .clocks + .check_write(current_clock_idx, clock_map) + .map_err(|e| e.add_cell_info(self.global_idx))?; + } + + self.exec_cycle(port_map) } } @@ -225,11 +273,44 @@ impl MemDx { pub fn get_dimensions(&self) -> Shape { self.shape.clone() } + + pub fn iter_addr_ports( + &self, + base_port: GlobalPortIdx, + ) -> Box> { + let (addr0, addr1, addr2, addr3) = if SEQ { + ports![&base_port; + addr0: Self::SEQ_ADDR0, + addr1: Self::SEQ_ADDR1, + addr2: Self::SEQ_ADDR2, + addr3: Self::SEQ_ADDR3 + ]; + (addr0, addr1, addr2, addr3) + } else { + ports![&base_port; + addr0: Self::COMB_ADDR0, + addr1: Self::COMB_ADDR1, + addr2: Self::COMB_ADDR2, + addr3: Self::COMB_ADDR3 + ]; + + (addr0, addr1, addr2, addr3) + }; + + match self.shape { + Shape::D1(_) => Box::new(std::iter::once(addr0)), + Shape::D2(_, _) => Box::new([addr0, addr1].into_iter()), + Shape::D3(_, _, _) => Box::new([addr0, addr1, addr2].into_iter()), + Shape::D4(_, _, _, _) => { + Box::new([addr0, addr1, addr2, addr3].into_iter()) + } + } + } } pub struct CombMem { base_port: GlobalPortIdx, - internal_state: Vec, + internal_state: Vec, // TODO griffin: This bool is unused in the actual struct and should either // be removed or _allow_invalid_access: bool, @@ -262,12 +343,20 @@ impl CombMem { width: u32, allow_invalid: bool, size: T, + clocks: &mut Option<&mut ClockMap>, ) -> Self where T: Into, { let shape = size.into(); - let internal_state = vec![BitVecValue::zero(width); shape.size()]; + let mut internal_state = Vec::with_capacity(shape.size()); + for _ in 0..shape.size() { + internal_state.push(ValueWithClock::zero( + width, + new_clock(clocks), + new_clock(clocks), + )); + } Self { base_port: base, @@ -287,6 +376,7 @@ impl CombMem { allow_invalid: bool, size: T, data: &[u8], + clocks: &mut Option<&mut ClockMap>, ) -> Self where T: Into, @@ -297,6 +387,9 @@ impl CombMem { let internal_state = data .chunks_exact(byte_count as usize) .map(|x| BitVecValue::from_bytes_le(x, width)) + .map(|x| { + ValueWithClock::new(x, new_clock(clocks), new_clock(clocks)) + }) .collect_vec(); assert_eq!(internal_state.len(), size.size()); @@ -319,9 +412,18 @@ impl CombMem { pub fn dump_data(&self) -> Vec { self.internal_state .iter() - .flat_map(|x| x.to_bytes_le()) + .flat_map(|x| x.value.to_bytes_le()) .collect() } + + fn infer_thread(&self, port_map: &mut PortMap) -> Option { + let ports = self + .addresser + .iter_addr_ports(self.base_port) + .chain([self.write_en(), self.write_data()]) + .map(|x| &port_map[x]); + infer_thread_id(ports) + } } impl Primitive for CombMem { @@ -331,11 +433,14 @@ impl Primitive for CombMem { let read = if addr.is_some() && addr.unwrap() < self.internal_state.len() { + let addr = addr.unwrap(); + port_map.insert_val( read_data, AssignedValue::cell_value( - self.internal_state[addr.unwrap()].clone(), - ), + self.internal_state[addr].value.clone(), + ) + .with_clocks(self.internal_state[addr].clocks), )? } // either the address is undefined or it is outside the range of valid addresses @@ -371,7 +476,7 @@ impl Primitive for CombMem { let write_data = port_map[self.write_data()] .as_option() .ok_or(InterpreterError::UndefinedWrite(self.global_idx))?; - self.internal_state[addr] = write_data.val().clone(); + self.internal_state[addr].value = write_data.val().clone(); self.done_is_high = true; port_map.insert_val(done, AssignedValue::cell_b_high())? } else { @@ -382,7 +487,10 @@ impl Primitive for CombMem { if let Some(addr) = addr { Ok(port_map.insert_val( read_data, - AssignedValue::cell_value(self.internal_state[addr].clone()), + AssignedValue::cell_value( + self.internal_state[addr].value.clone(), + ) + .with_clocks(self.internal_state[addr].clocks), )? | done) } else { port_map.write_undef(read_data)?; @@ -396,7 +504,7 @@ impl Primitive for CombMem { Serializable::Array( self.internal_state .iter() - .map(|x| Entry::from_val_code(x, &code)) + .map(|x| Entry::from_val_code(&x.value, &code)) .collect(), self.addresser.get_dimensions(), ) @@ -411,14 +519,72 @@ impl Primitive for CombMem { } } +impl RaceDetectionPrimitive for CombMem { + fn as_primitive(&self) -> &dyn Primitive { + self + } + + // fn exec_comb_checked( + // &self, + // port_map: &mut PortMap, + // clock_map: &mut ClockMap, + // thread_map: &ThreadMap, + // ) -> UpdateResult { + // let thread = self.infer_thread(port_map); + + // if let Some(addr) = + // self.addresser.calculate_addr(port_map, self.base_port) + // { + // if addr < self.internal_state.len() { + // let thread = + // thread.expect("Could not infer thread id for comb mem"); + // let reading_clock = thread_map.unwrap_clock_id(thread); + + // let val = &self.internal_state[addr]; + // val.check_read(reading_clock, clock_map)?; + // } + // } + + // self.exec_comb(port_map) + // } + + fn exec_cycle_checked( + &mut self, + port_map: &mut PortMap, + clock_map: &mut ClockMap, + thread_map: &ThreadMap, + ) -> UpdateResult { + let thread = self.infer_thread(port_map); + if let Some(addr) = + self.addresser.calculate_addr(port_map, self.base_port) + { + if addr < self.internal_state.len() { + let thread = + thread.expect("Could not infer thread id for seq mem"); + let thread_clock = thread_map.unwrap_clock_id(thread); + + let val = &self.internal_state[addr]; + + if port_map[self.write_en()].as_bool().unwrap_or_default() { + val.clocks + .check_write(thread_clock, clock_map) + .map_err(|e| e.add_cell_info(self.global_idx))?; + } + } + } + + self.exec_cycle(port_map) + } +} + pub struct SeqMem { base_port: GlobalPortIdx, - internal_state: Vec, + internal_state: Vec, global_idx: GlobalCellIdx, // TODO griffin: This bool is unused in the actual struct and should either // be removed or _allow_invalid_access: bool, - _width: u32, + width: u32, addresser: MemDx, done_is_high: bool, read_out: PortValue, @@ -431,15 +597,23 @@ impl SeqMem { width: u32, allow_invalid: bool, size: T, + clocks: &mut Option<&mut ClockMap>, ) -> Self { let shape = size.into(); - let internal_state = vec![BitVecValue::zero(width); shape.size()]; + let mut internal_state = Vec::with_capacity(shape.size()); + for _ in 0..shape.size() { + internal_state.push(ValueWithClock::zero( + width, + new_clock(clocks), + new_clock(clocks), + )); + } Self { base_port: base, internal_state, _allow_invalid_access: allow_invalid, - _width: width, + width, addresser: MemDx::new(shape), done_is_high: false, read_out: PortValue::new_undef(), @@ -454,6 +628,7 @@ impl SeqMem { allow_invalid: bool, size: T, data: &[u8], + clocks: &mut Option<&mut ClockMap>, ) -> Self where T: Into, @@ -464,6 +639,9 @@ impl SeqMem { let internal_state = data .chunks_exact(byte_count as usize) .map(|x| BitVecValue::from_bytes_le(x, width)) + .map(|x| { + ValueWithClock::new(x, new_clock(clocks), new_clock(clocks)) + }) .collect_vec(); assert_eq!(internal_state.len(), size.size()); @@ -476,7 +654,7 @@ impl SeqMem { base_port, internal_state, _allow_invalid_access: allow_invalid, - _width: width, + width, addresser: MemDx::new(size), done_is_high: false, read_out: PortValue::new_undef(), @@ -518,9 +696,22 @@ impl SeqMem { pub fn dump_data(&self) -> Vec { self.internal_state .iter() - .flat_map(|x| x.to_bytes_le()) + .flat_map(|x| x.value.to_bytes_le()) .collect() } + + fn infer_thread(&self, port_map: &mut PortMap) -> Option { + let ports = self + .addresser + .iter_addr_ports(self.base_port) + .chain([ + self.content_enable(), + self.write_data(), + self.write_enable(), + ]) + .map(|x| &port_map[x]); + infer_thread_id(ports) + } } impl Primitive for SeqMem { @@ -559,7 +750,7 @@ impl Primitive for SeqMem { if reset { self.done_is_high = false; - self.read_out = PortValue::new_cell(BitVecValue::zero(self._width)); + self.read_out = PortValue::new_cell(BitVecValue::zero(self.width)); } else if content_en && write_en { self.done_is_high = true; self.read_out = PortValue::new_undef(); @@ -568,13 +759,14 @@ impl Primitive for SeqMem { let write_data = port_map[self.write_data()] .as_option() .ok_or(InterpreterError::UndefinedWrite(self.global_idx))?; - self.internal_state[addr_actual] = write_data.val().clone(); + self.internal_state[addr_actual].value = write_data.val().clone(); } else if content_en { self.done_is_high = true; let addr_actual = addr .ok_or(InterpreterError::UndefinedReadAddr(self.global_idx))?; - self.read_out = - PortValue::new_cell(self.internal_state[addr_actual].clone()); + self.read_out = PortValue::new_cell( + self.internal_state[addr_actual].value.clone(), + ); } else { self.done_is_high = false; } @@ -606,7 +798,7 @@ impl Primitive for SeqMem { Serializable::Array( self.internal_state .iter() - .map(|x| Entry::from_val_code(x, &code)) + .map(|x| Entry::from_val_code(&x.value, &code)) .collect(), self.addresser.get_dimensions(), ) @@ -620,6 +812,72 @@ impl Primitive for SeqMem { Some(self.dump_data()) } } + +impl RaceDetectionPrimitive for SeqMem { + fn as_primitive(&self) -> &dyn Primitive { + self + } + + fn exec_comb_checked( + &self, + port_map: &mut PortMap, + _clock_map: &mut ClockMap, + _thread_map: &ThreadMap, + ) -> UpdateResult { + self.exec_comb(port_map) + } + + fn exec_cycle_checked( + &mut self, + port_map: &mut PortMap, + clock_map: &mut ClockMap, + thread_map: &ThreadMap, + ) -> UpdateResult { + let thread = self.infer_thread(port_map); + if let Some(addr) = + self.addresser.calculate_addr(port_map, self.base_port) + { + if addr < self.internal_state.len() { + let thread_clock = + thread.map(|thread| thread_map.unwrap_clock_id(thread)); + + let val = &self.internal_state[addr]; + + if port_map[self.write_enable()].as_bool().unwrap_or_default() + && port_map[self.content_enable()] + .as_bool() + .unwrap_or_default() + { + val.clocks + .check_write( + thread_clock.expect( + "unable to determine thread for seq mem", + ), + clock_map, + ) + .map_err(|e| e.add_cell_info(self.global_idx))?; + } else if port_map[self.content_enable()] + .as_bool() + .unwrap_or_default() + { + val.clocks + .check_read( + ( + thread.expect( + "unable to determine thread for seq mem", + ), + thread_clock.unwrap(), + ), + clock_map, + ) + .map_err(|e| e.add_cell_info(self.global_idx))?; + } + } + } + self.exec_cycle(port_map) + } +} + // type aliases, this is kinda stupid and should probably be changed. or maybe // it's fine, I really don't know. pub type CombMemD1 = CombMem; diff --git a/interp/src/flatten/primitives/utils.rs b/interp/src/flatten/primitives/utils.rs index 41a576937..6b91e17a3 100644 --- a/interp/src/flatten/primitives/utils.rs +++ b/interp/src/flatten/primitives/utils.rs @@ -86,3 +86,28 @@ macro_rules! get_params { } pub(crate) use get_params; + +use crate::flatten::{flat_ir::base::PortValue, structures::thread::ThreadIdx}; + +pub fn infer_thread_id<'a, I: Iterator>( + iter: I, +) -> Option { + let mut result = None; + for val in iter { + // We have seen a thread id before + if let Some(res) = result { + if let Some(thread) = val.as_option().and_then(|x| x.thread()) { + // If there are multiple thread ids, we return None + if res != thread { + return None; + } + } + } + // Have not seen a thread id yet, can just take the possible empty id + // from value + else { + result = val.as_option().and_then(|x| x.thread()); + } + } + result +} diff --git a/interp/src/flatten/structures/context.rs b/interp/src/flatten/structures/context.rs index 2a4aad88a..5ed19d5e4 100644 --- a/interp/src/flatten/structures/context.rs +++ b/interp/src/flatten/structures/context.rs @@ -25,6 +25,7 @@ use super::{ index_trait::{IndexRange, IndexRef}, indexed_map::{AuxiliaryMap, IndexedMap}, printer::Printer, + sparse_map::AuxiliarySparseMap, }; /// The immutable program context for the interpreter. Relevant at simulation @@ -41,6 +42,10 @@ pub struct InterpretationContext { pub comb_groups: CombGroupMap, /// All assignment guards pub guards: GuardMap, + /// Map from guard to the ports it reads. Might be worth doing some extra + /// work to make this save memory since empty vecs for True guards is + /// probably not worth it + pub guard_read_map: AuxiliarySparseMap>, /// Control trees pub control: ControlMap, } @@ -100,6 +105,8 @@ pub struct PortDefinitionInfo { pub name: Identifier, /// The width of the port pub width: usize, + /// Whether the port is control + pub is_control: bool, } #[derive(Debug)] @@ -174,9 +181,13 @@ impl SecondaryContext { &mut self, name: Identifier, width: usize, + is_control: bool, ) -> PortDefinitionIdx { - self.local_port_defs - .push(PortDefinitionInfo { name, width }) + self.local_port_defs.push(PortDefinitionInfo { + name, + width, + is_control, + }) } /// Insert a new reference port definition into the context and return its index diff --git a/interp/src/flatten/structures/environment/assignments.rs b/interp/src/flatten/structures/environment/assignments.rs index ca3c82741..1fdb67211 100644 --- a/interp/src/flatten/structures/environment/assignments.rs +++ b/interp/src/flatten/structures/environment/assignments.rs @@ -1,4 +1,7 @@ -use crate::flatten::flat_ir::prelude::{GlobalCellIdx, LocalPortOffset}; +use crate::flatten::{ + flat_ir::prelude::{GlobalCellIdx, LocalPortOffset}, + structures::thread::ThreadIdx, +}; use super::env::AssignmentRange; @@ -14,6 +17,8 @@ pub struct ScheduledAssignments { pub active_cell: GlobalCellIdx, pub assignments: AssignmentRange, pub interface_ports: Option, + pub thread: Option, + pub is_cont: bool, } impl ScheduledAssignments { @@ -21,11 +26,15 @@ impl ScheduledAssignments { active_cell: GlobalCellIdx, assignments: AssignmentRange, interface_ports: Option, + thread: Option, + is_comb: bool, ) -> Self { Self { active_cell, assignments, interface_ports, + thread, + is_cont: is_comb, } } } diff --git a/interp/src/flatten/structures/environment/clock.rs b/interp/src/flatten/structures/environment/clock.rs new file mode 100644 index 000000000..9d30eabc2 --- /dev/null +++ b/interp/src/flatten/structures/environment/clock.rs @@ -0,0 +1,495 @@ +use std::{ + cmp::{max, Ordering}, + collections::HashMap, + hash::Hash, + num::NonZeroU32, +}; + +use crate::flatten::{ + flat_ir::base::GlobalCellIdx, + structures::{ + index_trait::{impl_index_nonzero, IndexRef}, + indexed_map::IndexedMap, + thread::ThreadIdx, + }, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ClockIdx(NonZeroU32); +impl_index_nonzero!(ClockIdx); + +use baa::BitVecValue; +use itertools::Itertools; +use thiserror::Error; + +pub type ClockMap = IndexedMap>; +pub type ThreadClockPair = (ThreadIdx, ClockIdx); + +impl ClockMap { + /// pushes a new clock into the map and returns its index + pub fn new_clock(&mut self) -> ClockIdx { + self.push(VectorClock::new()) + } + + /// Returns a new clock that is the clone of the given clock + pub fn fork_clock(&mut self, parent: ClockIdx) -> ClockIdx { + self.push(self[parent].clone()) + } +} + +pub trait Counter: Default { + /// Increment the counter, returning `None` if the counter overflowed. + #[must_use] + fn increment(&mut self) -> Option<()>; + + /// Increment the counter, panicking if the counter overflowed. + fn increment_expect(&mut self) { + self.increment().expect("counter overflowed"); + } +} + +impl Counter for u8 { + fn increment(&mut self) -> Option<()> { + *self = self.checked_add(1)?; + Some(()) + } +} + +impl Counter for u16 { + fn increment(&mut self) -> Option<()> { + *self = self.checked_add(1)?; + Some(()) + } +} + +impl Counter for u32 { + fn increment(&mut self) -> Option<()> { + *self = self.checked_add(1)?; + Some(()) + } +} + +impl Counter for u64 { + fn increment(&mut self) -> Option<()> { + *self = self.checked_add(1)?; + Some(()) + } +} + +// I don't expect this to be used much, but it's here for completeness +impl Counter for u128 { + fn increment(&mut self) -> Option<()> { + *self = self.checked_add(1)?; + Some(()) + } +} + +/// If the clock map is provided, use it to create a new clock. Otherwise, +/// return the 0th clock idx. +pub fn new_clock(clock_map: &mut Option<&mut ClockMap>) -> ClockIdx { + clock_map + .as_mut() + .map(|c| c.new_clock()) + .unwrap_or(ClockIdx::new(0)) +} + +/// A simple vector clock implementation. +/// +/// Internally uses a [`HashMap`] to store the clock values. Keys which are not +/// present in the map are assumed to be the default value for the given counter +/// type, which is zero for the standard integer counters. This means that all +/// threads implicitly start at zero, rather than some bottom value. +#[derive(Debug, Clone)] +pub struct VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ + // TODO: maybe use `ahash` instead + map: HashMap, +} + +impl std::ops::Index<&I> for VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ + type Output = C; + + fn index(&self, index: &I) -> &Self::Output { + &self.map[index] + } +} + +impl std::ops::Index for VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ + type Output = C; + + fn index(&self, index: I) -> &Self::Output { + &self.map[&index] + } +} + +impl Eq for VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ +} + +impl PartialEq for VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ + fn eq(&self, other: &Self) -> bool { + if let Some(c) = self.partial_cmp(other) { + matches!(c, Ordering::Equal) + } else { + false + } + } +} + +impl FromIterator<(I, C)> for VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ + fn from_iter>(iter: T) -> Self { + Self { + map: iter.into_iter().collect(), + } + } +} + +impl VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + + pub fn new_incr(id: I) -> Self { + let mut clock = Self::new(); + clock.increment(&id); + clock + } + + /// Increment the clock for the given id. Creates the id if it doesn't exist. + /// + /// # Panics + /// Panics if the clock overflows. + pub fn increment(&mut self, id: &I) { + if let Some(counter) = self.map.get_mut(id) { + counter.increment_expect(); + } else { + self.map.insert(id.clone(), C::default()); + self.map.get_mut(id).unwrap().increment_expect(); + } + } + + pub fn get(&self, id: &I) -> Option<&C> { + self.map.get(id) + } + + /// Takes two vector clocks and mutates the first such that it contains the + /// maximum value for each local clock across both vector clocks. + pub fn sync(&mut self, other: &Self) { + for (id, counter) in other.map.iter() { + let v = self.map.entry(id.clone()).or_default(); + *v = max(counter, v).clone(); + } + } + + /// Takes two vector clocks and produces a new vector clock that contains + /// the maximum value for each local clock across both vector clocks. + #[inline] + pub fn join(first: &Self, second: &Self) -> Self { + // might be better to use an iterator instead? + let mut merged = first.clone(); + merged.sync(second); + merged + } + + pub fn set_thread_clock(&mut self, thread_id: I, clock: C) { + self.map.insert(thread_id, clock); + } +} + +impl Default for VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ + fn default() -> Self { + Self::new() + } +} + +impl PartialOrd for VectorClock +where + I: Hash + Eq + Clone, + C: Ord + Clone + Counter, +{ + fn partial_cmp(&self, other: &Self) -> Option { + // there's probably a better way to do this but it'll suffice for now + // not sure if it's better to do extra redundant comparisons or incur + // the cost of the `unique` call. Something to investigate in the future + let iter = self.map.keys().chain(other.map.keys()).unique().map(|id| { + match (self.get(id), other.get(id)) { + (None, Some(count_other)) => C::default().cmp(count_other), + (Some(count_self), None) => count_self.cmp(&C::default()), + (Some(count_self), Some(count_other)) => { + count_self.cmp(count_other) + } + (None, None) => unreachable!(), + } + }); + + let mut current_answer = None; + for cmp in iter { + if let Some(current_answer) = current_answer.as_mut() { + match (¤t_answer, cmp) { + // Incomparable case + (Ordering::Less, Ordering::Greater) + | (Ordering::Greater, Ordering::Less) => { + return None; + } + (Ordering::Equal, Ordering::Less) => { + *current_answer = Ordering::Less; + } + (Ordering::Equal, Ordering::Greater) => { + *current_answer = Ordering::Greater; + } + _ => {} + } + } else { + current_answer = Some(cmp); + } + } + + // If we have an answer, return it. Otherwise, return `Equal` since the + // `None` case can only happen if the maps are both empty. The + // incomparable case exits early. + if let Some(answer) = current_answer { + Some(answer) + } else { + Some(Ordering::Equal) + } + } +} + +#[derive(Debug, Clone)] +pub struct ValueWithClock { + pub value: BitVecValue, + pub clocks: ClockPair, +} + +impl ValueWithClock { + pub fn zero( + width: u32, + reading_clock: ClockIdx, + writing_clock: ClockIdx, + ) -> Self { + Self { + value: BitVecValue::zero(width), + clocks: ClockPair::new(reading_clock, writing_clock), + } + } + + pub fn new( + value: BitVecValue, + write_clock: ClockIdx, + read_clock: ClockIdx, + ) -> Self { + Self { + value, + clocks: ClockPair::new(read_clock, write_clock), + } + } +} + +/// A struct containing the read and write clocks for a value. This is small +/// enough to be copied around easily +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct ClockPair { + pub read_clock: ClockIdx, + pub write_clock: ClockIdx, +} + +impl ClockPair { + pub fn new(read_clock: ClockIdx, write_clock: ClockIdx) -> Self { + Self { + read_clock, + write_clock, + } + } + + pub fn check_read( + &self, + (thread, reading_clock): ThreadClockPair, + clock_map: &mut ClockMap, + ) -> Result<(), ClockError> { + if clock_map[reading_clock] >= clock_map[self.write_clock] { + let v = clock_map[reading_clock][thread]; + clock_map[self.read_clock].set_thread_clock(thread, v); + Ok(()) + } else if clock_map[reading_clock] + .partial_cmp(&clock_map[self.write_clock]) + .is_none() + { + Err(ClockError::ReadWriteUnhelpful) + } else { + // This implies that the read happens before the write which I think + // shouldn't be possible + unreachable!( + "something weird happened. TODO griffin: Sort this out" + ) + } + } + + pub fn check_write( + &self, + writing_clock: ClockIdx, + clock_map: &mut ClockMap, + ) -> Result<(), ClockError> { + if clock_map[writing_clock] >= clock_map[self.write_clock] + && clock_map[writing_clock] >= clock_map[self.read_clock] + { + clock_map[self.write_clock] = clock_map[writing_clock].clone(); + Ok(()) + } else if clock_map[writing_clock] < clock_map[self.read_clock] + || clock_map[writing_clock] + .partial_cmp(&clock_map[self.read_clock]) + .is_none() + { + Err(ClockError::ReadWriteUnhelpful) + } else if clock_map[writing_clock] + .partial_cmp(&clock_map[self.write_clock]) + .is_none() + { + Err(ClockError::WriteWriteUnhelpful) + } else { + // This implies the current write happened before the prior write + // which I think shouldn't be possible + unreachable!( + "something weird happened. TODO griffin: Sort this out" + ) + } + } +} + +#[derive(Debug, Clone, Error)] +pub enum ClockError { + #[error("Concurrent read & write to the same register/memory")] + ReadWriteUnhelpful, + #[error("Concurrent writes to the same register/memory")] + WriteWriteUnhelpful, + #[error("Concurrent read & write to the same register/memory {0:?}")] + ReadWrite(GlobalCellIdx), + #[error("Concurrent writes to the same register/memory {0:?}")] + WriteWrite(GlobalCellIdx), +} + +impl ClockError { + pub fn add_cell_info(self, cell: GlobalCellIdx) -> Self { + match self { + ClockError::ReadWriteUnhelpful => ClockError::ReadWrite(cell), + ClockError::WriteWriteUnhelpful => ClockError::WriteWrite(cell), + _ => self, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basics() { + let clock = VectorClock::::new_incr(1); + let mut other = VectorClock::::new(); + + assert!(clock >= other); + + other.increment(&2); + + assert!(clock.partial_cmp(&other).is_none()); + + let merged = VectorClock::join(&clock, &other); + + assert!(merged >= clock); + assert!(merged >= other); + assert_eq!(VectorClock::::new(), VectorClock::::new()); + } + + // Adapted from VClock tests + #[test] + fn test_vector_clock_new() { + let vc1 = VectorClock::::new_incr(17); + assert_eq!(None, vc1.get(&0)); + assert_eq!(Some(1), vc1.get(&17).copied()); + + let vc2 = VectorClock::::new_incr(17u32); + assert_eq!(None, vc2.get(&0u32)); + assert_eq!(Some(1), vc2.get(&17u32).copied()); + + let vc3 = VectorClock::::new_incr(17i64); + assert_eq!(None, vc3.get(&0i64)); + assert_eq!(Some(1u8), vc3.get(&17i64).copied()); + } + + #[test] + fn test_vector_clock_increment() { + let mut vc = VectorClock::::new(); + + assert_eq!(None, vc.get(&0)); + assert_eq!(None, vc.get(&2)); + + vc.increment(&0); + assert_eq!(Some(1), vc.get(&0).copied()); + assert_eq!(None, vc.get(&2)); + + vc.increment(&2); + vc.increment(&0); + + assert_eq!(Some(2), vc.get(&0).copied()); + assert_eq!(Some(1), vc.get(&2).copied()); + + vc.increment(&0); + assert_eq!(Some(3), vc.get(&0).copied()); + assert_eq!(Some(1), vc.get(&2).copied()); + + vc.increment(&1); + assert_eq!(Some(3), vc.get(&0).copied()); + assert_eq!(Some(1), vc.get(&1).copied()); + assert_eq!(Some(1), vc.get(&2).copied()); + } + + #[test] + fn test_empty_comparison() { + let vc = VectorClock::::new(); + let vc2: VectorClock = [(12, 0), (10, 0)].into_iter().collect(); + let vc3: VectorClock = [(147, 0), (32, 0)].into_iter().collect(); + + assert_eq!(vc, vc2); + assert_eq!(vc2, vc3); + } + + #[test] + #[should_panic] + fn test_overflow() { + let mut vc = VectorClock::::new(); + for _ in 0..257 { + vc.increment(&0); + } + } +} diff --git a/interp/src/flatten/structures/environment/env.rs b/interp/src/flatten/structures/environment/env.rs index df29abaae..a7b9f54af 100644 --- a/interp/src/flatten/structures/environment/env.rs +++ b/interp/src/flatten/structures/environment/env.rs @@ -3,7 +3,8 @@ use super::{ context::Context, index_trait::IndexRange, indexed_map::IndexedMap, }, assignments::{GroupInterfacePorts, ScheduledAssignments}, - program_counter::{PcMaps, ProgramCounter, WithEntry}, + clock::{ClockMap, VectorClock}, + program_counter::{ControlTuple, PcMaps, ProgramCounter, WithEntry}, traverser::{Path, TraversalError}, }; use crate::flatten::structures::environment::wave::WaveWriter; @@ -16,23 +17,21 @@ use crate::{ LocalRefPortOffset, }, cell_prototype::{CellPrototype, SingleWidthType}, - prelude::{ - AssignedValue, AssignmentIdx, BaseIndices, - CellDefinitionRef::{Local, Ref}, - CellRef, ComponentIdx, ControlNode, GlobalCellIdx, - GlobalCellRef, GlobalPortIdx, GlobalPortRef, GlobalRefCellIdx, - GlobalRefPortIdx, GroupIdx, GuardIdx, Identifier, If, Invoke, - PortRef, PortValue, While, - }, + prelude::*, wires::guards::Guard, }, - primitives::{self, prim_trait::UpdateStatus, Primitive}, + primitives::{ + self, + prim_trait::{RaceDetectionPrimitive, UpdateStatus}, + Primitive, + }, structures::{ context::{LookupName, PortDefinitionInfo}, environment::{ program_counter::ControlPoint, traverser::Traverser, }, index_trait::IndexRef, + thread::{ThreadIdx, ThreadMap}, }, }, logging, @@ -44,6 +43,7 @@ use ahash::{HashMap, HashMapExt}; use baa::{BitVecOps, BitVecValue}; use itertools::Itertools; use owo_colors::OwoColorize; + use slog::warn; use std::fmt::Debug; use std::fmt::Write; @@ -58,7 +58,7 @@ impl PortMap { target: GlobalPortIdx, ) -> InterpreterResult<()> { if self[target].is_def() { - todo!("raise error") + Err(InterpreterError::UndefiningDefinedPort(target).into()) } else { Ok(()) } @@ -99,14 +99,21 @@ impl PortMap { Some(t) if *t == val => Ok(UpdateStatus::Unchanged), // conflict // TODO: Fix to make the error more helpful - Some(t) if t.has_conflict_with(&val) => InterpreterResult::Err( - InterpreterError::FlatConflictingAssignments { - target, - a1: t.clone(), - a2: val, - } - .into(), - ), + Some(t) + if t.has_conflict_with(&val) + // Assignment & cell values are allowed to override implicit but not the + // other way around + && !(*t.winner() == AssignmentWinner::Implicit) => + { + InterpreterResult::Err( + InterpreterError::FlatConflictingAssignments { + target, + a1: t.clone(), + a2: val, + } + .into(), + ) + } // changed Some(_) | None => { self[target] = PortValue::new(val); @@ -168,9 +175,30 @@ pub(crate) enum CellLedger { // wish there was a better option with this one cell_dyn: Box, }, + RaceDetectionPrimitive { + cell_dyn: Box, + }, Component(ComponentLedger), } +impl From for CellLedger { + fn from(v: ComponentLedger) -> Self { + Self::Component(v) + } +} + +impl From> for CellLedger { + fn from(cell_dyn: Box) -> Self { + Self::RaceDetectionPrimitive { cell_dyn } + } +} + +impl From> for CellLedger { + fn from(cell_dyn: Box) -> Self { + Self::Primitive { cell_dyn } + } +} + impl CellLedger { fn new_comp + Clone>( idx: ComponentIdx, @@ -204,6 +232,9 @@ impl CellLedger { pub fn as_primitive(&self) -> Option<&dyn Primitive> { match self { Self::Primitive { cell_dyn } => Some(&**cell_dyn), + Self::RaceDetectionPrimitive { cell_dyn } => { + Some(cell_dyn.as_primitive()) + } _ => None, } } @@ -218,6 +249,9 @@ impl Debug for CellLedger { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Primitive { .. } => f.debug_struct("Primitive").finish(), + Self::RaceDetectionPrimitive { .. } => { + f.debug_struct("RaceDetectionPrimitive").finish() + } Self::Component(ComponentLedger { index_bases, comp_id, @@ -273,12 +307,19 @@ pub struct Environment + Clone> { pinned_ports: PinnedPorts, + clocks: ClockMap, + thread_map: ThreadMap, + control_ports: HashMap, + /// The immutable context. This is retained for ease of use. /// This value should have a cheap clone implementation, such as &Context /// or RC. pub(super) ctx: C, memory_header: Option>, + + /// Whether to perform data race checking + check_data_race: bool, } impl + Clone> Environment { @@ -346,10 +387,17 @@ impl + Clone> Environment { } } - pub fn new(ctx: C, data_map: Option) -> Self { + pub fn new( + ctx: C, + data_map: Option, + check_data_races: bool, + ) -> Self { let root = ctx.as_ref().entry_point; let aux = &ctx.as_ref().secondary[root]; + let mut clocks = IndexedMap::new(); + let root_clock = clocks.push(VectorClock::new()); + let mut env = Self { ports: PortMap::with_capacity(aux.port_offset_map.count()), cells: CellMap::with_capacity(aux.cell_offset_map.count()), @@ -360,14 +408,21 @@ impl + Clone> Environment { aux.ref_port_offset_map.count(), ), pc: ProgramCounter::new_empty(), + clocks, + thread_map: ThreadMap::new(root_clock), ctx, memory_header: None, pinned_ports: PinnedPorts::new(), + control_ports: HashMap::new(), + check_data_race: check_data_races, }; let root_node = CellLedger::new_comp(root, &env); - let root = env.cells.push(root_node); - env.layout_component(root, &data_map, &mut HashSet::new()); + let root_cell = env.cells.push(root_node); + env.layout_component(root_cell, &data_map, &mut HashSet::new()); + + let root_thread = ThreadMap::root_thread(); + env.clocks[root_clock].increment(&root_thread); // Initialize program counter // TODO griffin: Maybe refactor into a separate function @@ -376,10 +431,17 @@ impl + Clone> Environment { if let Some(ctrl) = &env.ctx.as_ref().primary[comp.comp_id].control { - env.pc.vec_mut().push(ControlPoint { - comp: idx, - control_node_idx: *ctrl, - }) + env.pc.vec_mut().push(( + if comp.comp_id == root { + Some(root_thread) + } else { + None + }, + ControlPoint { + comp: idx, + control_node_idx: *ctrl, + }, + )) } } } @@ -426,7 +488,13 @@ impl + Clone> Environment { // first layout the signature for sig_port in comp_aux.signature().iter() { + let def_idx = comp_aux.port_offset_map[sig_port]; + let info = &self.ctx.as_ref().secondary[def_idx]; let idx = self.ports.push(PortValue::new_undef()); + if info.is_control { + self.control_ports + .insert(idx, info.width.try_into().unwrap()); + } debug_assert_eq!(index_bases + sig_port, idx); } // second group ports @@ -457,6 +525,8 @@ impl + Clone> Environment { debug_assert_eq!(go, done_actual); debug_assert_eq!(done, go_actual); } + self.control_ports.insert(go, 1); + self.control_ports.insert(done, 1); } for (cell_off, def_idx) in comp_aux.cell_offset_map.iter() { @@ -469,6 +539,12 @@ impl + Clone> Environment { &self.cells[comp].as_comp().unwrap().index_bases + port, idx ); + let def_idx = comp_aux.port_offset_map[port]; + let info = &self.ctx.as_ref().secondary[def_idx]; + if info.is_control { + self.control_ports + .insert(idx, info.width.try_into().unwrap()); + } } let cell_dyn = primitives::build_primitive( info, @@ -477,8 +553,9 @@ impl + Clone> Environment { self.ctx.as_ref(), data_map, memories_initialized, + self.check_data_race.then_some(&mut self.clocks), ); - let cell = self.cells.push(CellLedger::Primitive { cell_dyn }); + let cell = self.cells.push(cell_dyn); debug_assert_eq!( &self.cells[comp].as_comp().unwrap().index_bases + cell_off, @@ -559,7 +636,7 @@ impl + Clone> Environment { pub fn get_currently_running_groups( &self, ) -> impl Iterator + '_ { - self.pc.iter().filter_map(|point| { + self.pc.iter().filter_map(|(_, point)| { let node = &self.ctx.as_ref().primary[point.control_node_idx]; match node { ControlNode::Enable(x) => { @@ -673,7 +750,7 @@ impl + Clone> Environment { } pub fn print_pc(&self) { - let current_nodes = self.pc.iter().filter(|point| { + let current_nodes = self.pc.iter().filter(|(_thread, point)| { let node = &self.ctx.as_ref().primary[point.control_node_idx]; match node { ControlNode::Enable(_) | ControlNode::Invoke(_) => { @@ -687,7 +764,7 @@ impl + Clone> Environment { let ctx = &self.ctx.as_ref(); - for point in current_nodes { + for (_thread, point) in current_nodes { let node = &ctx.primary[point.control_node_idx]; match node { ControlNode::Enable(x) => { @@ -742,7 +819,7 @@ impl + Clone> Environment { /// Attempt to find the parent cell for a port. If no such cell exists (i.e. /// it is a hole port, then it returns None) - fn _get_parent_cell_from_port( + fn get_parent_cell_from_port( &self, port: PortRef, comp: GlobalCellIdx, @@ -1222,6 +1299,7 @@ impl + Clone> Simulator { ctx: C, data_file: &Option, wave_file: &Option, + check_races: bool, ) -> Result { let data_dump = data_file .as_ref() @@ -1232,7 +1310,10 @@ impl + Clone> Simulator { // flip to a result of an option .map_or(Ok(None), |res| res.map(Some))?; - Ok(Simulator::new(Environment::new(ctx, data_dump), wave_file)) + Ok(Simulator::new( + Environment::new(ctx, data_dump, check_races), + wave_file, + )) } pub fn is_group_running(&self, group_idx: GroupIdx) -> bool { @@ -1335,16 +1416,6 @@ impl + Clone> Simulator { self.lookup_global_cell_id(ledger.convert_to_global_cell(cell)) } - #[inline] - fn get_value( - &self, - port: &PortRef, - parent_comp: GlobalCellIdx, - ) -> &PortValue { - let port_idx = self.get_global_port_idx(port, parent_comp); - &self.env.ports[port_idx] - } - pub(crate) fn get_root_component(&self) -> &ComponentLedger { self.env.cells[Environment::::get_root()] .as_comp() @@ -1363,11 +1434,11 @@ impl + Clone> Simulator { // allocation is too expensive in this context fn get_assignments( &self, - control_points: &[ControlPoint], + control_points: &[ControlTuple], ) -> Vec { control_points .iter() - .filter_map(|node| { + .filter_map(|(thread, node)| { match &self.ctx().primary[node.control_node_idx] { ControlNode::Enable(e) => { let group = &self.ctx().primary[e.group()]; @@ -1379,6 +1450,8 @@ impl + Clone> Simulator { go: group.go, done: group.done, }), + *thread, + false, )) } @@ -1386,6 +1459,8 @@ impl + Clone> Simulator { node.comp, i.assignments, None, + *thread, + false, )), ControlNode::Empty(_) => None, @@ -1397,16 +1472,20 @@ impl + Clone> Simulator { | ControlNode::Par(_) => None, } }) - .chain( - self.env.pc.continuous_assigns().iter().map(|x| { - ScheduledAssignments::new(x.comp, x.assigns, None) - }), - ) + .chain(self.env.pc.continuous_assigns().iter().map(|x| { + ScheduledAssignments::new(x.comp, x.assigns, None, None, true) + })) .chain(self.env.pc.with_map().iter().map( |(ctrl_pt, with_entry)| { let assigns = self.ctx().primary[with_entry.group].assignments; - ScheduledAssignments::new(ctrl_pt.comp, assigns, None) + ScheduledAssignments::new( + ctrl_pt.comp, + assigns, + None, + with_entry.thread, + false, + ) }, )) .collect() @@ -1449,7 +1528,7 @@ impl + Clone> Simulator { let cell_info_idx = parent_info.get_cell_info_idx(*cell_ref); match cell_info_idx { - Local(l) => { + CellDefinitionRef::Local(l) => { let info = &self.env.ctx.as_ref().secondary[l]; assert_eq!( child_ref_cell_info.ports.size(), @@ -1464,7 +1543,7 @@ impl + Clone> Simulator { self.env.ref_ports[dest_idx] = Some(source_idx); } } - Ref(r) => { + CellDefinitionRef::Ref(r) => { let info = &self.env.ctx.as_ref().secondary[r]; assert_eq!( child_ref_cell_info.ports.size(), @@ -1531,10 +1610,14 @@ impl + Clone> Simulator { self.env.ports[*port] = PortValue::new_implicit(val.clone()); } - for comp in self.env.pc.finished_comps() { + for (comp, id) in self.env.pc.finished_comps() { let done_port = self.env.get_comp_done(*comp); - self.env.ports[done_port] = - PortValue::new_implicit(BitVecValue::tru()); + let v = PortValue::new_implicit(BitVecValue::tru()); + self.env.ports[done_port] = if self.env.check_data_race { + v.with_thread(id.expect("finished comps should have a thread")) + } else { + v + } } let (vecs, par_map, mut with_map, repeat_map) = @@ -1545,8 +1628,12 @@ impl + Clone> Simulator { let ctx = self.env.ctx.clone(); let ctx_ref = ctx.as_ref(); - for node in vecs.iter() { + for (thread, node) in vecs.iter() { let comp_done = self.env.get_comp_done(node.comp); + let comp_go = self.env.get_comp_go(node.comp); + let thread = thread.or_else(|| { + self.env.ports[comp_go].as_option().and_then(|t| t.thread()) + }); // if the done is not high & defined, we need to set it to low if !self.env.ports[comp_done].as_bool().unwrap_or_default() { @@ -1574,13 +1661,21 @@ impl + Clone> Simulator { { with_map.insert( node.clone(), - WithEntry::new(invoke.comb_group.unwrap()), + WithEntry::new(invoke.comb_group.unwrap(), thread), ); } let go = self.get_global_port_idx(&invoke.go, node.comp); self.env.ports[go] = - PortValue::new_implicit(BitVecValue::tru()); + PortValue::new_implicit(BitVecValue::tru()) + .with_thread_optional( + if self.env.check_data_race { + assert!(thread.is_some()); + thread + } else { + None + }, + ); // TODO griffin: should make this skip initialization if // it's already initialized @@ -1592,7 +1687,7 @@ impl + Clone> Simulator { { with_map.insert( node.clone(), - WithEntry::new(i.cond_group().unwrap()), + WithEntry::new(i.cond_group().unwrap(), thread), ); } } @@ -1601,7 +1696,7 @@ impl + Clone> Simulator { { with_map.insert( node.clone(), - WithEntry::new(w.cond_group().unwrap()), + WithEntry::new(w.cond_group().unwrap(), thread), ); } } @@ -1628,7 +1723,7 @@ impl + Clone> Simulator { let out: Result<(), BoxedInterpreterError> = { let mut result = Ok(()); - for (_, cell) in self.env.cells.iter_mut() { + for cell in self.env.cells.values_mut() { match cell { CellLedger::Primitive { cell_dyn } => { let res = cell_dyn.exec_cycle(&mut self.env.ports); @@ -1637,6 +1732,18 @@ impl + Clone> Simulator { break; } } + + CellLedger::RaceDetectionPrimitive { cell_dyn } => { + let res = cell_dyn.exec_cycle_checked( + &mut self.env.ports, + &mut self.env.clocks, + &self.env.thread_map, + ); + if res.is_err() { + result = Err(res.unwrap_err()); + break; + } + } CellLedger::Component(_) => {} } } @@ -1649,15 +1756,22 @@ impl + Clone> Simulator { let (mut vecs, mut par_map, mut with_map, mut repeat_map) = self.env.pc.take_fields(); - // TODO griffin: This has become an unwieldy mess and should really be - // refactored into a handful of internal functions - vecs.retain_mut(|node| { - self.evaluate_control_node( + let mut removed = vec![]; + + for (i, node) in vecs.iter_mut().enumerate() { + let keep_node = self.evaluate_control_node( node, &mut new_nodes, (&mut par_map, &mut with_map, &mut repeat_map), - ) - }); + )?; + if !keep_node { + removed.push(i); + } + } + + for i in removed.into_iter().rev() { + vecs.swap_remove(i); + } self.env .pc @@ -1671,14 +1785,19 @@ impl + Clone> Simulator { fn evaluate_control_node( &mut self, - node: &mut ControlPoint, - new_nodes: &mut Vec, + node: &mut ControlTuple, + new_nodes: &mut Vec, maps: PcMaps, - ) -> bool { + ) -> InterpreterResult { + let (node_thread, node) = node; let (par_map, with_map, repeat_map) = maps; let comp_go = self.env.get_comp_go(node.comp); let comp_done = self.env.get_comp_done(node.comp); + let thread = node_thread.or_else(|| { + self.env.ports[comp_go].as_option().and_then(|x| x.thread()) + }); + // mutability trick let ctx_clone = self.env.ctx.clone(); let ctx = ctx_clone.as_ref(); @@ -1688,7 +1807,7 @@ impl + Clone> Simulator { { // if the go port is low or the done port is high, we skip the // node without doing anything - return true; + return Ok(true); } // just considering a single node case for the moment @@ -1706,8 +1825,30 @@ impl + Clone> Simulator { if par_map.contains_key(node) { let count = par_map.get_mut(node).unwrap(); *count -= 1; + if *count == 0 { par_map.remove(node); + if self.env.check_data_race { + let thread = + thread.expect("par nodes should have a thread"); + + let child_clock_idx = + self.env.thread_map.unwrap_clock_id(thread); + let parent = + self.env.thread_map[thread].parent().unwrap(); + let parent_clock = + self.env.thread_map.unwrap_clock_id(parent); + let child_clock = std::mem::take( + &mut self.env.clocks[child_clock_idx], + ); + self.env.clocks[parent_clock].sync(&child_clock); + self.env.clocks[child_clock_idx] = child_clock; + assert!(self.env.thread_map[thread] + .parent() + .is_some()); + *node_thread = Some(parent); + self.env.clocks[parent_clock].increment(&parent); + } node.mutate_into_next(self.env.ctx.as_ref()) } else { false @@ -1719,14 +1860,58 @@ impl + Clone> Simulator { "More than (2^16 - 1 threads) in a par block. Are you sure this is a good idea?", ), ); - new_nodes.extend( - par.stms().iter().map(|x| node.new_retain_comp(*x)), - ); + new_nodes.extend(par.stms().iter().map(|x| { + let thread = if self.env.check_data_race { + let thread = + thread.expect("par nodes should have a thread"); + + let new_thread_idx: ThreadIdx = *(self + .env + .pc + .lookup_thread(node.comp, thread, *x) + .or_insert_with(|| { + let new_clock_idx = + self.env.clocks.new_clock(); + + self.env + .thread_map + .spawn(thread, new_clock_idx) + })); + + let new_clock_idx = self + .env + .thread_map + .unwrap_clock_id(new_thread_idx); + + self.env.clocks[new_clock_idx] = self.env.clocks + [self.env.thread_map.unwrap_clock_id(thread)] + .clone(); + + self.env.clocks[new_clock_idx] + .increment(&new_thread_idx); + + Some(new_thread_idx) + } else { + None + }; + + (thread, node.new_retain_comp(*x)) + })); + + if self.env.check_data_race { + let thread = + thread.expect("par nodes should have a thread"); + let clock = self.env.thread_map.unwrap_clock_id(thread); + self.env.clocks[clock].increment(&thread); + } + false } } - ControlNode::If(i) => self.handle_if(with_map, node, i), - ControlNode::While(w) => self.handle_while(w, with_map, node), + ControlNode::If(i) => self.handle_if(with_map, node, thread, i)?, + ControlNode::While(w) => { + self.handle_while(w, with_map, node, thread)? + } ControlNode::Repeat(rep) => { if let Some(count) = repeat_map.get_mut(node) { *count -= 1; @@ -1771,7 +1956,7 @@ impl + Clone> Simulator { if i.comb_group.is_some() && !with_map.contains_key(node) { with_map.insert( node.clone(), - WithEntry::new(i.comb_group.unwrap()), + WithEntry::new(i.comb_group.unwrap(), thread), ); } @@ -1793,16 +1978,23 @@ impl + Clone> Simulator { // either we are not a par node, or we are the last par node (!matches!(&self.env.ctx.as_ref().primary[node.control_node_idx], ControlNode::Par(_)) || !par_map.contains_key(node)) { - self.env.pc.set_finshed_comp(node.comp); + if self.env.check_data_race { + assert!( + thread.is_some(), + "finished comps should have a thread" + ); + } + + self.env.pc.set_finshed_comp(node.comp, thread); let comp_ledger = self.env.cells[node.comp].unwrap_comp(); *node = node.new_retain_comp( self.env.ctx.as_ref().primary[comp_ledger.comp_id] .control .unwrap(), ); - true + Ok(true) } else { - retain_bool + Ok(retain_bool) } } @@ -1811,34 +2003,53 @@ impl + Clone> Simulator { w: &While, with_map: &mut HashMap, node: &mut ControlPoint, - ) -> bool { + thread: Option, + ) -> InterpreterResult { let target = GlobalPortRef::from_local( w.cond_port(), &self.env.cells[node.comp].unwrap_comp().index_bases, ); - let result = match target { - GlobalPortRef::Port(p) => self.env.ports[p] - .as_bool() - .expect("while condition is undefined"), - GlobalPortRef::Ref(r) => { - let index = self.env.ref_ports[r].unwrap(); - self.env.ports[index] - .as_bool() - .expect("while condition is undefined") - } + let idx = match target { + GlobalPortRef::Port(p) => p, + GlobalPortRef::Ref(r) => self.env.ref_ports[r] + .expect("While condition (ref) is undefined"), }; + if self.env.check_data_race { + if let Some(clocks) = self.env.ports[idx].clocks() { + let read_clock = + self.env.thread_map.unwrap_clock_id(thread.unwrap()); + clocks + .check_read( + (thread.unwrap(), read_clock), + &mut self.env.clocks, + ) + .map_err(|e| { + e.add_cell_info( + self.env + .get_parent_cell_from_port( + w.cond_port(), + node.comp, + ) + .unwrap(), + ) + })?; + } + } + let result = self.env.ports[idx] + .as_bool() + .expect("While condition is undefined"); if result { // enter the body *node = node.new_retain_comp(w.body()); - true + Ok(true) } else { if w.cond_group().is_some() { with_map.remove(node); } // ascend the tree - node.mutate_into_next(self.env.ctx.as_ref()) + Ok(node.mutate_into_next(self.env.ctx.as_ref())) } } @@ -1846,11 +2057,12 @@ impl + Clone> Simulator { &mut self, with_map: &mut HashMap, node: &mut ControlPoint, + thread: Option, i: &If, - ) -> bool { + ) -> InterpreterResult { if i.cond_group().is_some() && with_map.get(node).unwrap().entered { with_map.remove(node); - node.mutate_into_next(self.env.ctx.as_ref()) + Ok(node.mutate_into_next(self.env.ctx.as_ref())) } else { if let Some(entry) = with_map.get_mut(node) { entry.set_entered() @@ -1860,21 +2072,41 @@ impl + Clone> Simulator { i.cond_port(), &self.env.cells[node.comp].unwrap_comp().index_bases, ); - let result = match target { - GlobalPortRef::Port(p) => self.env.ports[p] - .as_bool() - .expect("if condition is undefined"), - GlobalPortRef::Ref(r) => { - let index = self.env.ref_ports[r].unwrap(); - self.env.ports[index] - .as_bool() - .expect("if condition is undefined") - } + let idx = match target { + GlobalPortRef::Port(p) => p, + GlobalPortRef::Ref(r) => self.env.ref_ports[r] + .expect("If condition (ref) is undefined"), }; + if self.env.check_data_race { + if let Some(clocks) = self.env.ports[idx].clocks() { + let read_clock = + self.env.thread_map.unwrap_clock_id(thread.unwrap()); + clocks + .check_read( + (thread.unwrap(), read_clock), + &mut self.env.clocks, + ) + .map_err(|e| { + e.add_cell_info( + self.env + .get_parent_cell_from_port( + i.cond_port(), + node.comp, + ) + .unwrap(), + ) + })?; + } + } + + let result = self.env.ports[idx] + .as_bool() + .expect("If condition is undefined"); + let target = if result { i.tbranch() } else { i.fbranch() }; *node = node.new_retain_comp(target); - true + Ok(true) } } @@ -1892,7 +2124,7 @@ impl + Clone> Simulator { wave.write_values(time, &self.env.ports)?; } // self.print_pc(); - self.step()?; + self.step().map_err(|e| e.prettify_message(&self.env))?; time += 1; } if let Some(wave) = self.wave.as_mut() { @@ -1960,20 +2192,7 @@ impl + Clone> Simulator { assigns_bundle: &[ScheduledAssignments], ) -> InterpreterResult<()> { let mut has_changed = true; - - // TODO griffin: rewrite this so that someone can actually read it - let done_ports: Vec<_> = assigns_bundle - .iter() - .filter_map(|x| { - x.interface_ports.as_ref().map(|y| { - &self.env.cells[x.active_cell] - .as_comp() - .unwrap() - .index_bases - + y.done - }) - }) - .collect(); + let mut have_zeroed_control_ports = false; while has_changed { has_changed = false; @@ -1983,6 +2202,8 @@ impl + Clone> Simulator { active_cell, assignments, interface_ports, + thread, + is_cont, } in assigns_bundle.iter() { let ledger = self.env.cells[*active_cell].as_comp().unwrap(); @@ -1994,51 +2215,161 @@ impl + Clone> Simulator { .map(|x| &ledger.index_bases + x.done); let comp_go = self.env.get_comp_go(*active_cell); + let thread = self.compute_thread(comp_go, thread, go); - for assign_idx in assignments { - let assign = &self.env.ctx.as_ref().primary[assign_idx]; - - // TODO griffin: Come back to this unwrap default later - // since we may want to do something different if the guard - // does not have a defined value - if self - .evaluate_guard(assign.guard, *active_cell) - .unwrap_or_default() - // the go for the group is high - && go - .as_ref() - // the group must have its go signal high and the go - // signal of the component must also be high - .map(|g| self.env.ports[*g].as_bool().unwrap_or_default() && self.env.ports[comp_go].as_bool().unwrap_or_default()) + // the go for the group is high + if go + .as_ref() + // the group must have its go signal high and the go + // signal of the component must also be high + .map(|g| { + self.env.ports[*g].as_bool().unwrap_or_default() + && self.env.ports[comp_go] + .as_bool() + .unwrap_or_default() + }) + .unwrap_or_else(|| { // if there is no go signal, then we want to run the - // assignment - .unwrap_or(true) - { - let val = self.get_value(&assign.src, *active_cell); - let dest = - self.get_global_port_idx(&assign.dst, *active_cell); - - if let Some(done) = done { - if dest != done { - let done_val = &self.env.ports[done]; - - if done_val.as_bool().unwrap_or(true) { - // skip this assignment when we are done or - // or the done signal is undefined - continue; + // continuous assignments but not comb group assignments + if *is_cont { + true + } else { + self.env.ports[comp_go] + .as_bool() + .unwrap_or_default() + } + }) + { + for assign_idx in assignments { + let assign = &self.env.ctx.as_ref().primary[assign_idx]; + + // TODO griffin: Come back to this unwrap default later + // since we may want to do something different if the guard + // does not have a defined value + if self + .evaluate_guard(assign.guard, *active_cell) + .unwrap_or_default() + { + let port = self + .get_global_port_idx(&assign.src, *active_cell); + let val = &self.env.ports[port]; + + if self.env.check_data_race { + if let Some(clocks) = val.clocks() { + // skip checking clocks for continuous assignments + if !is_cont { + if let Some(thread) = thread { + let thread_clock = self + .env + .thread_map + .unwrap_clock_id(thread); + + clocks + .check_read( + (thread, thread_clock), + &mut self.env.clocks, + ) + .map_err(|e| { + // TODO griffin: find a less hacky way to + // do this + e.add_cell_info( + self.env + .get_parent_cell_from_port( + assign.src, + *active_cell, + ) + .unwrap(), + ) + })?; + } else { + panic!("cannot determine thread for non-continuous assignment that touches a checked port"); + } + } } } - } - if let Some(v) = val.as_option() { - let changed = self.env.ports.insert_val( - dest, - AssignedValue::new(v.val().clone(), assign_idx), - )?; + let dest = self + .get_global_port_idx(&assign.dst, *active_cell); + + if let Some(done) = done { + if dest != done { + let done_val = &self.env.ports[done]; + + if done_val.as_bool().unwrap_or(true) { + // skip this assignment when we are done or + // or the done signal is undefined + continue; + } + } + } + + if let Some(v) = val.as_option() { + let changed = self.env.ports.insert_val( + dest, + AssignedValue::new( + v.val().clone(), + assign_idx, + ) + .with_thread_optional(thread), + )?; + + has_changed |= changed.as_bool(); + } + // attempts to undefine a control port that is zero + // will be ignored otherwise it is an error + // this is a bit of a hack and should be removed in + // the long run + else if self.env.ports[dest].is_def() + && !(self.env.control_ports.contains_key(&dest) + && self.env.ports[dest].is_zero().unwrap()) + { + todo!("Raise an error here since this assignment is undefining things: {}. Port currently has value: {}", self.env.ctx.as_ref().printer().print_assignment(ledger.comp_id, assign_idx), &self.env.ports[dest]) + } + } - has_changed |= changed.as_bool(); - } else if self.env.ports[dest].is_def() { - todo!("Raise an error here since this assignment is undefining things: {}. Port currently has value: {}", self.env.ctx.as_ref().printer().print_assignment(ledger.comp_id, assign_idx), &self.env.ports[dest]) + if self.env.check_data_race { + if let Some(read_ports) = self + .env + .ctx + .as_ref() + .primary + .guard_read_map + .get(assign.guard) + { + for port in read_ports { + let port_idx = self.get_global_port_idx( + port, + *active_cell, + ); + if let Some(clocks) = + self.env.ports[port_idx].clocks() + { + let thread = thread + .expect("cannot determine thread"); + let thread_clock = self + .env + .thread_map + .unwrap_clock_id(thread); + clocks + .check_read( + (thread, thread_clock), + &mut self.env.clocks, + ) + .map_err(|e| { + // TODO griffin: find a less hacky way to + // do this + e.add_cell_info( + self.env + .get_parent_cell_from_port( + *port, + *active_cell, + ) + .unwrap(), + ) + })?; + } + } + } } } } @@ -2054,6 +2385,14 @@ impl + Clone> Simulator { CellLedger::Primitive { cell_dyn } => { Some(cell_dyn.exec_comb(&mut self.env.ports)) } + CellLedger::RaceDetectionPrimitive { cell_dyn } => { + Some(cell_dyn.exec_comb_checked( + &mut self.env.ports, + &mut self.env.clocks, + &self.env.thread_map, + )) + } + CellLedger::Component(_) => None, }) .fold_ok(UpdateStatus::Unchanged, |has_changed, update| { @@ -2065,12 +2404,14 @@ impl + Clone> Simulator { // check for undefined done ports. If any remain after we've // converged then they should be set to zero and we should continue - // convergence - if !has_changed { - for &done_port in &done_ports { - if self.env.ports[done_port].is_undef() { - self.env.ports[done_port] = - PortValue::new_implicit(BitVecValue::fals()); + // convergence. Since these ports cannot become undefined again we + // only need to do this once + if !has_changed && !have_zeroed_control_ports { + have_zeroed_control_ports = true; + for (port, width) in self.env.control_ports.iter() { + if self.env.ports[*port].is_undef() { + self.env.ports[*port] = + PortValue::new_implicit(BitVecValue::zero(*width)); has_changed = true; } } @@ -2080,6 +2421,30 @@ impl + Clone> Simulator { Ok(()) } + /// Attempts to compute the thread id for the given group/component. + /// + /// If the given thread is `None`, then the thread id is computed from the + /// go port for the group. If no such port exists, or it lacks a thread id, + /// then the thread id is computed from the go port for the component. If + /// none of these succeed then `None` is returned. + fn compute_thread( + &self, + comp_go: GlobalPortIdx, + thread: &Option, + go: Option, + ) -> Option { + thread.or_else(|| { + if let Some(go_idx) = go { + if let Some(go_thread) = + self.env.ports[go_idx].as_option().and_then(|a| a.thread()) + { + return Some(go_thread); + } + } + self.env.ports[comp_go].as_option().and_then(|x| x.thread()) + }) + } + /// Dump the current state of the environment as a DataDump pub fn dump_memories( &self, diff --git a/interp/src/flatten/structures/environment/mod.rs b/interp/src/flatten/structures/environment/mod.rs index 2f48b36d8..f5133e74a 100644 --- a/interp/src/flatten/structures/environment/mod.rs +++ b/interp/src/flatten/structures/environment/mod.rs @@ -1,4 +1,5 @@ mod assignments; +pub mod clock; mod env; mod program_counter; mod traverser; @@ -6,3 +7,5 @@ mod wave; pub use env::{Environment, PortMap, Simulator}; pub use traverser::{Path, PathError, PathResolution}; + +pub(crate) use env::CellLedger; diff --git a/interp/src/flatten/structures/environment/program_counter.rs b/interp/src/flatten/structures/environment/program_counter.rs index 7b1e52f95..3ce817a84 100644 --- a/interp/src/flatten/structures/environment/program_counter.rs +++ b/interp/src/flatten/structures/environment/program_counter.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroU32; +use std::{collections::hash_map::Entry, num::NonZeroU32}; use ahash::{HashMap, HashMapExt}; @@ -8,7 +8,10 @@ use crate::flatten::{ AssignmentIdx, CombGroupIdx, ControlIdx, ControlMap, ControlNode, GlobalCellIdx, }, - structures::index_trait::{impl_index_nonzero, IndexRange, IndexRef}, + structures::{ + index_trait::{impl_index_nonzero, IndexRange, IndexRef}, + thread::ThreadIdx, + }, }; use itertools::{FoldWhile, Itertools}; @@ -353,12 +356,14 @@ pub struct WithEntry { pub group: CombGroupIdx, /// Whether or not a body has been executed. Only used by if statements pub entered: bool, + pub thread: Option, } impl WithEntry { - pub fn new(group: CombGroupIdx) -> Self { + pub fn new(group: CombGroupIdx, thread: Option) -> Self { Self { group, + thread, entered: false, } } @@ -372,18 +377,20 @@ impl WithEntry { /// the active leaf statements for each component instance. #[derive(Debug, Default)] pub(crate) struct ProgramCounter { - vec: Vec, + vec: Vec, par_map: HashMap, continuous_assigns: Vec, with_map: HashMap, repeat_map: HashMap, - just_finished_comps: Vec, + just_finished_comps: Vec<(GlobalCellIdx, Option)>, + thread_memoizer: HashMap<(GlobalCellIdx, ThreadIdx, ControlIdx), ThreadIdx>, } +pub type ControlTuple = (Option, ControlPoint); // we need a few things from the program counter pub type PcFields = ( - Vec, + Vec, HashMap, HashMap, HashMap, @@ -404,22 +411,19 @@ impl ProgramCounter { with_map: HashMap::new(), repeat_map: HashMap::new(), just_finished_comps: Vec::new(), + thread_memoizer: HashMap::new(), } } - pub fn iter(&self) -> std::slice::Iter<'_, ControlPoint> { + pub fn iter(&self) -> std::slice::Iter<'_, ControlTuple> { self.vec.iter() } - pub fn node_slice(&self) -> &[ControlPoint] { + pub fn node_slice(&self) -> &[ControlTuple] { &self.vec } - pub fn _iter_mut(&mut self) -> impl Iterator { - self.vec.iter_mut() - } - - pub fn vec_mut(&mut self) -> &mut Vec { + pub fn vec_mut(&mut self) -> &mut Vec { &mut self.vec } @@ -465,23 +469,36 @@ impl ProgramCounter { &self.with_map } - pub fn set_finshed_comp(&mut self, comp: GlobalCellIdx) { - self.just_finished_comps.push(comp) + pub fn set_finshed_comp( + &mut self, + comp: GlobalCellIdx, + thread: Option, + ) { + self.just_finished_comps.push((comp, thread)) } - pub fn finished_comps(&self) -> &[GlobalCellIdx] { + pub fn finished_comps(&self) -> &[(GlobalCellIdx, Option)] { &self.just_finished_comps } pub fn clear_finished_comps(&mut self) { self.just_finished_comps.clear() } + + pub fn lookup_thread( + &mut self, + comp: GlobalCellIdx, + thread: ThreadIdx, + control: ControlIdx, + ) -> Entry<(GlobalCellIdx, ThreadIdx, ControlIdx), ThreadIdx> { + self.thread_memoizer.entry((comp, thread, control)) + } } impl<'a> IntoIterator for &'a ProgramCounter { - type Item = &'a ControlPoint; + type Item = &'a ControlTuple; - type IntoIter = std::slice::Iter<'a, ControlPoint>; + type IntoIter = std::slice::Iter<'a, ControlTuple>; fn into_iter(self) -> Self::IntoIter { self.iter() diff --git a/interp/src/flatten/structures/mod.rs b/interp/src/flatten/structures/mod.rs index af7edb104..16e2ee60a 100644 --- a/interp/src/flatten/structures/mod.rs +++ b/interp/src/flatten/structures/mod.rs @@ -4,3 +4,4 @@ pub mod index_trait; pub mod indexed_map; mod printer; pub mod sparse_map; +pub mod thread; diff --git a/interp/src/flatten/structures/sparse_map.rs b/interp/src/flatten/structures/sparse_map.rs index 729bcd455..fc4fcfab6 100644 --- a/interp/src/flatten/structures/sparse_map.rs +++ b/interp/src/flatten/structures/sparse_map.rs @@ -47,7 +47,7 @@ where { pub fn new() -> Self { Self { - data: Default::default(), + data: HashMap::new(), iteration_order: vec![IndexRange::empty_interval()], count: 0, } @@ -117,3 +117,42 @@ where self.data.contains_key(&index) } } + +/// An analogue to [AuxillaryMap](super::indexed_map::AuxillaryMap) for sparse +/// maps. This is used to store extra information that is only applicable to a +/// subset of the indices in a primary map. +#[derive(Debug, Clone)] +pub struct AuxiliarySparseMap +where + K: IndexRef + Hash, +{ + data: HashMap, +} + +impl AuxiliarySparseMap +where + K: IndexRef + Hash, +{ + pub fn new() -> Self { + Self { + data: HashMap::new(), + } + } + + pub fn insert_value(&mut self, key: K, value: D) { + self.data.insert(key, value); + } + + pub fn get(&self, key: K) -> Option<&D> { + self.data.get(&key) + } +} + +impl Default for AuxiliarySparseMap +where + K: IndexRef + Hash, +{ + fn default() -> Self { + Self::new() + } +} diff --git a/interp/src/flatten/structures/thread.rs b/interp/src/flatten/structures/thread.rs new file mode 100644 index 000000000..938990dba --- /dev/null +++ b/interp/src/flatten/structures/thread.rs @@ -0,0 +1,78 @@ +use std::{num::NonZeroU32, ops::Index}; + +use super::{ + environment::clock::ClockIdx, index_trait::impl_index_nonzero, + indexed_map::IndexedMap, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ThreadIdx(NonZeroU32); +impl_index_nonzero!(ThreadIdx); + +#[derive(Debug)] +pub struct ThreadInfo { + parent: Option, + clock_id: ClockIdx, +} + +impl ThreadInfo { + pub fn parent(&self) -> Option { + self.parent + } + + pub fn clock_id(&self) -> ClockIdx { + self.clock_id + } +} + +#[derive(Debug)] +pub struct ThreadMap { + map: IndexedMap, +} + +impl ThreadMap { + pub fn new(root_clock: ClockIdx) -> Self { + let mut map = IndexedMap::new(); + map.push(ThreadInfo { + parent: None, + clock_id: root_clock, + }); + Self { map } + } + + pub fn root_thread() -> ThreadIdx { + ThreadIdx::from(0) + } + /// Lookup the clock associated with the given thread id. Returns `None` if + /// the thread id is invalid. + pub fn get_clock_id(&self, thread_id: &ThreadIdx) -> Option { + self.map.get(*thread_id).map(|x| x.clock_id) + } + + /// Lookup the clock associated with the given thread id. Panics if the + /// thread id is invalid. + pub fn unwrap_clock_id(&self, thread_id: ThreadIdx) -> ClockIdx { + self.map.get(thread_id).unwrap().clock_id + } + + /// Create a new thread with the given parent and clock id. Returns the new + /// thread id. + pub fn spawn( + &mut self, + parent: ThreadIdx, + clock_id: ClockIdx, + ) -> ThreadIdx { + self.map.push(ThreadInfo { + parent: Some(parent), + clock_id, + }) + } +} + +impl Index for ThreadMap { + type Output = ThreadInfo; + + fn index(&self, index: ThreadIdx) -> &Self::Output { + &self.map[index] + } +} diff --git a/interp/src/main.rs b/interp/src/main.rs index 3776615b5..bcb785883 100644 --- a/interp/src/main.rs +++ b/interp/src/main.rs @@ -72,6 +72,10 @@ pub struct Opts { #[argh(option, long = "wave-file")] pub wave_file: Option, + /// perform data-race analysis + #[argh(switch, long = "check-data-race")] + check_data_race: bool, + #[argh(subcommand)] mode: Option, } @@ -127,6 +131,7 @@ fn main() -> InterpreterResult<()> { &i_ctx, &opts.data_file, &opts.wave_file, + opts.check_data_race, )?; sim.run_program()?; @@ -140,8 +145,12 @@ fn main() -> InterpreterResult<()> { Command::Debug(_) => { let mut info: Option = None; loop { - let debugger = - Debugger::new(&i_ctx, &opts.data_file, &opts.wave_file)?; + let debugger = Debugger::new( + &i_ctx, + &opts.data_file, + &opts.wave_file, + opts.check_data_race, + )?; let result = debugger.main_loop(info)?; match result { diff --git a/interp/src/serialization/data_dump.rs b/interp/src/serialization/data_dump.rs index 9e61f0f00..5a95ffedf 100644 --- a/interp/src/serialization/data_dump.rs +++ b/interp/src/serialization/data_dump.rs @@ -492,7 +492,7 @@ mod tests { #[test] fn comb_roundtrip(dump in arb_data_dump()) { for mem in &dump.header.memories { - let memory_prim = CombMemD1::new_with_init(GlobalPortIdx::new(0), GlobalCellIdx::new(0), mem.width(), false, mem.size(), dump.get_data(&mem.name).unwrap()); + let memory_prim = CombMemD1::new_with_init(GlobalPortIdx::new(0), GlobalCellIdx::new(0), mem.width(), false, mem.size(), dump.get_data(&mem.name).unwrap(), &mut None); let data = memory_prim.dump_data(); prop_assert_eq!(dump.get_data(&mem.name).unwrap(), data); } @@ -501,7 +501,7 @@ mod tests { #[test] fn seq_roundtrip(dump in arb_data_dump()) { for mem in &dump.header.memories { - let memory_prim = SeqMemD1::new_with_init(GlobalPortIdx::new(0), GlobalCellIdx::new(0), mem.width(), false, mem.size(), dump.get_data(&mem.name).unwrap()); + let memory_prim = SeqMemD1::new_with_init(GlobalPortIdx::new(0), GlobalCellIdx::new(0), mem.width(), false, mem.size(), dump.get_data(&mem.name).unwrap(), &mut None); let data = memory_prim.dump_data(); prop_assert_eq!(dump.get_data(&mem.name).unwrap(), data); } diff --git a/interp/tests/data-race/guard-conflict.expect b/interp/tests/data-race/guard-conflict.expect new file mode 100644 index 000000000..d3f49ee6d --- /dev/null +++ b/interp/tests/data-race/guard-conflict.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register/memory main.cond_reg diff --git a/interp/tests/data-race/guard-conflict.futil b/interp/tests/data-race/guard-conflict.futil new file mode 100644 index 000000000..d95368884 --- /dev/null +++ b/interp/tests/data-race/guard-conflict.futil @@ -0,0 +1,70 @@ +import "primitives/core.futil"; + +component main() -> () { + cells { + cond_reg = std_reg(1); + a = std_reg(32); + b = std_reg(32); + add = std_add(32); + sub = std_sub(32); + } + wires { + group write_cond { + cond_reg.in = 1'd1; + cond_reg.write_en = 1'd1; + write_cond[done] = cond_reg.done; + } + + // group incr_a { + // add.left = a.out; + // add.right = 32'd1; + // a.in = add.out; + // a.write_en = 1'd1; + // incr_a[done] = a.done; + // } + + // group decr_b { + // sub.left = b.out; + // sub.right = 32'd1; + // b.in = sub.out; + // b.write_en = 1'd1; + // decr_b[done] = b.done; + // } + + group write_b { + b.in = 32'd10; + b.write_en = 1'd1; + write_b[done] = b.done; + } + + group composite { + sub.left = !cond_reg.out ? b.out; + sub.right = !cond_reg.out ? 32'd1; + b.in = !cond_reg.out ? sub.out; + b.write_en = !cond_reg.out ? 1'd1; + + add.left = cond_reg.out ? a.out; + add.right = cond_reg.out ? 32'd1; + a.in = cond_reg.out ? add.out; + a.write_en = cond_reg.out ? 1'd1; + + composite[done] = (b.done | a.done) ? 1'd1; + } + + } + + control { + // T0 + par { // T1 + + seq { + composite; + }; + + seq { // T3 + write_cond; // T3_2 + write_b; // T3_1 + } + } + } +} diff --git a/interp/tests/data-race/par-conflict-cmem.expect b/interp/tests/data-race/par-conflict-cmem.expect new file mode 100644 index 000000000..59aad0e0b --- /dev/null +++ b/interp/tests/data-race/par-conflict-cmem.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register/memory main.cond_mem diff --git a/interp/tests/data-race/par-conflict-cmem.futil b/interp/tests/data-race/par-conflict-cmem.futil new file mode 100644 index 000000000..57fd4e61f --- /dev/null +++ b/interp/tests/data-race/par-conflict-cmem.futil @@ -0,0 +1,73 @@ +import "primitives/core.futil"; +import "primitives/memories/comb.futil"; + +component main() -> () { + cells { + cond_reg = std_reg(1); + cond_mem = comb_mem_d1(1, 1, 1); + a = comb_mem_d1(32, 1, 1); + b = comb_mem_d1(32, 1, 1); + add = std_add(32); + sub = std_sub(32); + } + wires { + group write_cond { + cond_mem.addr0 = 1'd0; + cond_mem.write_data = 1'd1; + cond_mem.write_en = 1'd1; + write_cond[done] = cond_mem.done; + } + + group read_cond { + cond_mem.addr0 = 1'd0; + cond_reg.in = cond_mem.read_data; + cond_reg.write_en = 1'd1; + read_cond[done] = cond_reg.done; + } + + group incr_a { + add.left = a.read_data; + add.right = 32'd1; + a.write_data = add.out; + a.write_en = 1'd1; + a.addr0 = 1'd0; + incr_a[done] = a.done; + } + + group decr_b { + sub.left = b.read_data; + sub.right = 32'd1; + b.write_data = sub.out; + b.addr0 = 1'd0; + b.write_en = 1'd1; + decr_b[done] = b.done; + } + + group write_b { + b.write_data = 32'd10; + b.write_en = 1'd1; + b.addr0 = 1'd0; + write_b[done] = b.done; + } + } + + control { + // T0 + par { // T1 + + seq { + read_cond; // T2_1 + if cond_reg.out { // T2_1 + incr_a; // T2_2 + } else { + decr_b; // T2_3 + } + }; + + seq { // T3 + write_b; // T3_1 + write_cond; // T3_2 + } + } + } +} diff --git a/interp/tests/data-race/par-conflict.expect b/interp/tests/data-race/par-conflict.expect new file mode 100644 index 000000000..d3f49ee6d --- /dev/null +++ b/interp/tests/data-race/par-conflict.expect @@ -0,0 +1,4 @@ +---CODE--- +1 +---STDERR--- +Error: Concurrent read & write to the same register/memory main.cond_reg diff --git a/interp/tests/data-race/par-conflict.futil b/interp/tests/data-race/par-conflict.futil new file mode 100644 index 000000000..50d3794eb --- /dev/null +++ b/interp/tests/data-race/par-conflict.futil @@ -0,0 +1,60 @@ +import "primitives/core.futil"; + +component main() -> () { + cells { + cond_reg = std_reg(1); + a = std_reg(32); + b = std_reg(32); + add = std_add(32); + sub = std_sub(32); + } + wires { + group write_cond { + cond_reg.in = 1'd1; + cond_reg.write_en = 1'd1; + write_cond[done] = cond_reg.done; + } + + group incr_a { + add.left = a.out; + add.right = 32'd1; + a.in = add.out; + a.write_en = 1'd1; + incr_a[done] = a.done; + } + + group decr_b { + sub.left = b.out; + sub.right = 32'd1; + b.in = sub.out; + b.write_en = 1'd1; + decr_b[done] = b.done; + } + + group write_b { + b.in = 32'd10; + b.write_en = 1'd1; + write_b[done] = b.done; + } + + } + + control { + // T0 + par { // T1 + + seq { + if cond_reg.out { // T2_1 + incr_a; // T2_2 + } else { + decr_b; // T2_3 + } + }; + + seq { // T3 + write_cond; // T3_2 + write_b; // T3_1 + } + } + } +} diff --git a/interp/tests/runt.toml b/interp/tests/runt.toml index 9e00d99b7..18c2967fa 100644 --- a/interp/tests/runt.toml +++ b/interp/tests/runt.toml @@ -108,6 +108,27 @@ fud2 --from calyx --to dat \ {} | jq --sort-keys """ +[[tests]] +name = "data-race detection" +paths = ["data-race/*.futil"] +cmd = """ +../../target/debug/cider {} -l ../../ --check-data-race +""" + +# [[tests]] +# name = "correctness lowered" +# paths = ["../../tests/correctness/*.futil"] +# cmd = """ +# fud2 --from calyx --to dat \ +# --through cider \ +# -s sim.data={}.data \ +# -s calyx.args="--log off" \ +# -s calyx.flags="-p all" \ +# {} | jq --sort-keys +# """ +# timeout = 60 + + [[tests]] name = "correctness ref cells" paths = ["../../tests/correctness/ref-cells/*.futil"] @@ -179,6 +200,7 @@ fud2 --from dahlia --to dat \ -s calyx.args="--log off" \ {} | jq --sort-keys """ +timeout = 180 # [[tests]] # name = "[frontend] systolic array correctness"