forked from libre-chip/fayalite
		
	WIP implementing simulator
This commit is contained in:
		
							parent
							
								
									6f904148c4
								
							
						
					
					
						commit
						479d59b287
					
				
					 3 changed files with 1104 additions and 248 deletions
				
			
		|  | @ -313,7 +313,7 @@ impl TargetChild { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, PartialEq, Eq, Hash)] | ||||
| #[derive(Copy, Clone, PartialEq, Eq, Hash)] | ||||
| pub enum Target { | ||||
|     Base(Interned<TargetBase>), | ||||
|     Child(TargetChild), | ||||
|  |  | |||
|  | @ -1,28 +1,32 @@ | |||
| //! Fayalite Simulation
 | ||||
| 
 | ||||
| use crate::{ | ||||
|     bundle::{Bundle, BundleType}, | ||||
|     bundle::{Bundle, BundleField, BundleType}, | ||||
|     enum_::Enum, | ||||
|     expr::Expr, | ||||
|     int::Bool, | ||||
|     intern::{Intern, Interned}, | ||||
|     module::{ | ||||
|         Block, Module, ModuleBody, NormalModuleBody, Stmt, StmtConnect, StmtFormal, StmtIf, | ||||
|         StmtMatch, | ||||
|     expr::{ | ||||
|         target::{ | ||||
|             Target, TargetBase, TargetPathArrayElement, TargetPathBundleField, TargetPathElement, | ||||
|         }, | ||||
|         Expr, | ||||
|     }, | ||||
|     int::Bool, | ||||
|     intern::{Intern, Interned, Memoize}, | ||||
|     module::{ | ||||
|         AnnotatedModuleIO, Block, Instance, Module, ModuleBody, NormalModuleBody, Stmt, | ||||
|         StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, | ||||
|     }, | ||||
|     sim::interpreter::{ | ||||
|         Insn, Insns, InsnsBuilding, InsnsBuildingDone, SlotDebugData, StatePartLayout, TypeIndex, | ||||
|         TypeIndexRange, TypeLayout, | ||||
|     }, | ||||
|     sim::interpreter::{Insn, Insns, InsnsBuilding}, | ||||
|     source_location::SourceLocation, | ||||
|     ty::CanonicalType, | ||||
| }; | ||||
| use hashbrown::HashMap; | ||||
| use std::{cell::RefCell, rc::Rc}; | ||||
| use std::fmt; | ||||
| 
 | ||||
| mod interpreter; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| struct CompilingModule { | ||||
|     compiled_module: Option<CompiledModule>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] | ||||
| enum CondStack { | ||||
|     Always, | ||||
|  | @ -44,32 +48,279 @@ enum CondStack { | |||
|     }, | ||||
| } | ||||
| 
 | ||||
| #[derive(PartialEq, Eq, Hash, Clone, Copy)] | ||||
| enum InstantiatedModule { | ||||
|     Base(Interned<Module<Bundle>>), | ||||
|     Child { | ||||
|         parent: Interned<InstantiatedModule>, | ||||
|         instance: Interned<Instance<Bundle>>, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| impl InstantiatedModule { | ||||
|     fn leaf_module(self) -> Interned<Module<Bundle>> { | ||||
|         match self { | ||||
|             InstantiatedModule::Base(base) => base, | ||||
|             InstantiatedModule::Child { instance, .. } => instance.instantiated(), | ||||
|         } | ||||
|     } | ||||
|     fn write_path(self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         match self { | ||||
|             InstantiatedModule::Base(base) => fmt::Debug::fmt(&base.name_id(), f), | ||||
|             InstantiatedModule::Child { parent, instance } => { | ||||
|                 parent.write_path(f)?; | ||||
|                 write!(f, ".{}", instance.name_id()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl fmt::Debug for InstantiatedModule { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         write!(f, "InstantiatedModule(")?; | ||||
|         self.write_path(f)?; | ||||
|         write!(f, ": {})", self.leaf_module().name_id()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] | ||||
| struct TargetInInstantiatedModule { | ||||
|     instantiated_module: InstantiatedModule, | ||||
|     target: Target, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] | ||||
| struct CompiledBundleField { | ||||
|     offset: TypeIndex, | ||||
|     ty: CompiledTypeLayout, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] | ||||
| enum CompiledTypeLayoutBody { | ||||
|     Scalar, | ||||
|     Array { | ||||
|         /// debug names are ignored, use parent's layout instead
 | ||||
|         element: Interned<CompiledTypeLayout>, | ||||
|     }, | ||||
|     Bundle { | ||||
|         /// debug names are ignored, use parent's layout instead
 | ||||
|         fields: Interned<[CompiledBundleField]>, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] | ||||
| struct CompiledTypeLayout { | ||||
|     ty: CanonicalType, | ||||
|     layout: TypeLayout<InsnsBuildingDone>, | ||||
|     body: CompiledTypeLayoutBody, | ||||
| } | ||||
| 
 | ||||
| impl CompiledTypeLayout { | ||||
|     fn with_prefixed_debug_names(self, prefix: &str) -> Self { | ||||
|         let Self { ty, layout, body } = self; | ||||
|         Self { | ||||
|             ty, | ||||
|             layout: layout.with_prefixed_debug_names(prefix), | ||||
|             body, | ||||
|         } | ||||
|     } | ||||
|     fn get(ty: CanonicalType) -> Self { | ||||
|         #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] | ||||
|         struct MyMemoize; | ||||
|         impl Memoize for MyMemoize { | ||||
|             type Input = CanonicalType; | ||||
|             type InputOwned = CanonicalType; | ||||
|             type Output = CompiledTypeLayout; | ||||
| 
 | ||||
|             fn inner(self, input: &Self::Input) -> Self::Output { | ||||
|                 match input { | ||||
|                     CanonicalType::UInt(_) | ||||
|                     | CanonicalType::SInt(_) | ||||
|                     | CanonicalType::Bool(_) | ||||
|                     | CanonicalType::Enum(_) | ||||
|                     | CanonicalType::AsyncReset(_) | ||||
|                     | CanonicalType::SyncReset(_) | ||||
|                     | CanonicalType::Reset(_) | ||||
|                     | CanonicalType::Clock(_) => { | ||||
|                         let mut layout = TypeLayout::empty(); | ||||
|                         let debug_data = SlotDebugData { name: "".intern() }; | ||||
|                         if input.bit_width() > interpreter::SmallUInt::BITS as usize { | ||||
|                             layout.big_slots = StatePartLayout::scalar(debug_data); | ||||
|                         } else { | ||||
|                             layout.small_slots = StatePartLayout::scalar(debug_data); | ||||
|                         }; | ||||
|                         CompiledTypeLayout { | ||||
|                             ty: *input, | ||||
|                             layout: layout.into(), | ||||
|                             body: CompiledTypeLayoutBody::Scalar, | ||||
|                         } | ||||
|                     } | ||||
|                     CanonicalType::Array(array) => { | ||||
|                         let mut layout = TypeLayout::empty(); | ||||
|                         let element = CompiledTypeLayout::get(array.element()).intern_sized(); | ||||
|                         for index in 0..array.len() { | ||||
|                             layout.allocate( | ||||
|                                 &element | ||||
|                                     .layout | ||||
|                                     .with_prefixed_debug_names(&format!("[{index}]")), | ||||
|                             ); | ||||
|                         } | ||||
|                         CompiledTypeLayout { | ||||
|                             ty: *input, | ||||
|                             layout: layout.into(), | ||||
|                             body: CompiledTypeLayoutBody::Array { element }, | ||||
|                         } | ||||
|                     } | ||||
|                     CanonicalType::Bundle(bundle) => { | ||||
|                         let mut layout = TypeLayout::empty(); | ||||
|                         let fields = bundle | ||||
|                             .fields() | ||||
|                             .iter() | ||||
|                             .map( | ||||
|                                 |BundleField { | ||||
|                                      name, | ||||
|                                      flipped: _, | ||||
|                                      ty, | ||||
|                                  }| { | ||||
|                                     let ty = CompiledTypeLayout::get(*ty); | ||||
|                                     let offset = layout | ||||
|                                         .allocate( | ||||
|                                             &ty.layout | ||||
|                                                 .with_prefixed_debug_names(&format!(".{name}")), | ||||
|                                         ) | ||||
|                                         .start(); | ||||
|                                     CompiledBundleField { offset, ty } | ||||
|                                 }, | ||||
|                             ) | ||||
|                             .collect(); | ||||
|                         CompiledTypeLayout { | ||||
|                             ty: *input, | ||||
|                             layout: layout.into(), | ||||
|                             body: CompiledTypeLayoutBody::Bundle { fields }, | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         MyMemoize.get_owned(ty) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] | ||||
| struct CompiledValue { | ||||
|     layout: CompiledTypeLayout, | ||||
|     range: TypeIndexRange, | ||||
|     write: Option<(CompiledTypeLayout, TypeIndexRange)>, | ||||
| } | ||||
| 
 | ||||
| impl CompiledValue { | ||||
|     fn map( | ||||
|         self, | ||||
|         mut f: impl FnMut(CompiledTypeLayout, TypeIndexRange) -> (CompiledTypeLayout, TypeIndexRange), | ||||
|     ) -> Self { | ||||
|         let (layout, range) = f(self.layout, self.range); | ||||
|         Self { | ||||
|             layout, | ||||
|             range, | ||||
|             write: self.write.map(|(layout, range)| f(layout, range)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct Compiler { | ||||
|     insns: Insns<InsnsBuilding>, | ||||
|     modules: HashMap<Interned<Module<Bundle>>, Rc<RefCell<CompilingModule>>>, | ||||
|     module_queue: Vec<Interned<Module<Bundle>>>, | ||||
|     base_module: Interned<Module<Bundle>>, | ||||
|     modules: HashMap<InstantiatedModule, CompiledModule>, | ||||
|     compiled_values: HashMap<TargetInInstantiatedModule, CompiledValue>, | ||||
| } | ||||
| 
 | ||||
| impl Compiler { | ||||
|     pub fn new() -> Self { | ||||
|     pub fn new(base_module: Interned<Module<Bundle>>) -> Self { | ||||
|         Self { | ||||
|             insns: Insns::new(), | ||||
|             base_module, | ||||
|             modules: HashMap::new(), | ||||
|             module_queue: Vec::new(), | ||||
|             compiled_values: HashMap::new(), | ||||
|         } | ||||
|     } | ||||
|     pub fn add_module(&mut self, module: Interned<Module<Bundle>>) { | ||||
|         self.modules.entry(module).or_insert_with(|| { | ||||
|             self.module_queue.push(module); | ||||
|             Rc::new(RefCell::new(CompilingModule { | ||||
|                 compiled_module: None, | ||||
|             })) | ||||
|     fn compile_value(&mut self, target: TargetInInstantiatedModule) -> CompiledValue { | ||||
|         if let Some(&retval) = self.compiled_values.get(&target) { | ||||
|             return retval; | ||||
|         } | ||||
|         match target.target { | ||||
|             Target::Base(base) => { | ||||
|                 let unprefixed_layout = CompiledTypeLayout::get(base.canonical_ty()); | ||||
|                 let layout = unprefixed_layout.with_prefixed_debug_names(&format!( | ||||
|                     "{:?}.{:?}", | ||||
|                     target.instantiated_module, | ||||
|                     base.target_name() | ||||
|                 )); | ||||
|                 let range = self.insns.allocate_variable(&layout.layout); | ||||
|                 let write = match *base { | ||||
|                     TargetBase::ModuleIO(_) | ||||
|                     | TargetBase::MemPort(_) | ||||
|                     | TargetBase::Wire(_) | ||||
|                     | TargetBase::Instance(_) => None, | ||||
|                     TargetBase::Reg(_) => { | ||||
|                         let write_layout = unprefixed_layout.with_prefixed_debug_names(&format!( | ||||
|                             "{:?}.{:?}$next", | ||||
|                             target.instantiated_module, | ||||
|                             base.target_name() | ||||
|                         )); | ||||
|                         Some(( | ||||
|                             write_layout, | ||||
|                             self.insns.allocate_variable(&write_layout.layout), | ||||
|                         )) | ||||
|                     } | ||||
|                 }; | ||||
|                 CompiledValue { | ||||
|                     range, | ||||
|                     layout, | ||||
|                     write, | ||||
|                 } | ||||
|             } | ||||
|             Target::Child(target_child) => { | ||||
|                 let parent = self.compile_value(TargetInInstantiatedModule { | ||||
|                     instantiated_module: target.instantiated_module, | ||||
|                     target: *target_child.parent(), | ||||
|                 }); | ||||
|                 match *target_child.path_element() { | ||||
|                     TargetPathElement::BundleField(TargetPathBundleField { name }) => { | ||||
|                         parent.map(|layout, range| { | ||||
|                             let CompiledTypeLayout { | ||||
|                                 ty: CanonicalType::Bundle(bundle), | ||||
|                                 layout: _, | ||||
|                                 body: CompiledTypeLayoutBody::Bundle { fields }, | ||||
|                             } = layout | ||||
|                             else { | ||||
|                                 unreachable!(); | ||||
|                             }; | ||||
|                             let field_index = bundle.name_indexes()[&name]; | ||||
|                             ( | ||||
|                                 fields[field_index].ty, | ||||
|                                 range.slice(TypeIndexRange::new( | ||||
|                                     fields[field_index].offset, | ||||
|                                     fields[field_index].ty.layout.len(), | ||||
|                                 )), | ||||
|                             ) | ||||
|                         }) | ||||
|                     } | ||||
|                     TargetPathElement::ArrayElement(TargetPathArrayElement { index }) => parent | ||||
|                         .map(|layout, range| { | ||||
|                             let CompiledTypeLayoutBody::Array { element } = layout.body else { | ||||
|                                 unreachable!(); | ||||
|                             }; | ||||
|                             (*element, range.index_array(element.layout.len(), index)) | ||||
|                         }), | ||||
|                     TargetPathElement::DynArrayElement(_) => unreachable!(), | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fn compile_block( | ||||
|         &mut self, | ||||
|         module: Interned<Module<Bundle>>, | ||||
|         parent_module: Interned<InstantiatedModule>, | ||||
|         block: Block, | ||||
|         cond_stack: Interned<CondStack>, | ||||
|     ) { | ||||
|  | @ -98,7 +349,7 @@ impl Compiler { | |||
|                     blocks: [then_block, else_block], | ||||
|                 }) => { | ||||
|                     self.compile_block( | ||||
|                         module, | ||||
|                         parent_module, | ||||
|                         then_block, | ||||
|                         CondStack::IfTrue { | ||||
|                             parent: cond_stack, | ||||
|  | @ -108,7 +359,7 @@ impl Compiler { | |||
|                         .intern_sized(), | ||||
|                     ); | ||||
|                     self.compile_block( | ||||
|                         module, | ||||
|                         parent_module, | ||||
|                         else_block, | ||||
|                         CondStack::IfFalse { | ||||
|                             parent: cond_stack, | ||||
|  | @ -125,7 +376,7 @@ impl Compiler { | |||
|                 }) => { | ||||
|                     for (variant_index, block) in blocks.into_iter().enumerate() { | ||||
|                         self.compile_block( | ||||
|                             module, | ||||
|                             parent_module, | ||||
|                             block, | ||||
|                             CondStack::MatchArm { | ||||
|                                 parent: cond_stack, | ||||
|  | @ -137,67 +388,79 @@ impl Compiler { | |||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
|                 Stmt::Declaration(stmt_declaration) => todo!(), | ||||
|                 Stmt::Declaration(declaration) => match declaration { | ||||
|                     StmtDeclaration::Wire(wire) => todo!(), | ||||
|                     StmtDeclaration::Reg(reg) => todo!(), | ||||
|                     StmtDeclaration::Instance(StmtInstance { | ||||
|                         annotations, | ||||
|                         instance, | ||||
|                     }) => { | ||||
|                         self.compile_module( | ||||
|                             InstantiatedModule::Child { | ||||
|                                 parent: parent_module, | ||||
|                                 instance: instance.intern_sized(), | ||||
|                             } | ||||
|                             .intern_sized(), | ||||
|                         ); | ||||
|                         todo!() | ||||
|                     } | ||||
|                 }, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fn compile_module(&mut self, module: Interned<Module<Bundle>>) -> CompiledModule { | ||||
|         let step_entry_pc = self.insns.next_insn_pc(); | ||||
|         match module.body() { | ||||
|     fn compile_module(&mut self, module: Interned<InstantiatedModule>) -> &CompiledModule { | ||||
|         let module_io = module | ||||
|             .leaf_module() | ||||
|             .module_io() | ||||
|             .iter() | ||||
|             .map( | ||||
|                 |&AnnotatedModuleIO { | ||||
|                      annotations: _, | ||||
|                      module_io, | ||||
|                  }| { | ||||
|                     self.compile_value(TargetInInstantiatedModule { | ||||
|                         instantiated_module: *module, | ||||
|                         target: Target::from(module_io), | ||||
|                     }) | ||||
|                 }, | ||||
|             ) | ||||
|             .collect(); | ||||
|         match module.leaf_module().body() { | ||||
|             ModuleBody::Normal(NormalModuleBody { body }) => { | ||||
|                 self.compile_block(module, body, CondStack::Always.intern_sized()) | ||||
|             } | ||||
|             ModuleBody::Extern(_extern_module_body) => { | ||||
|                 todo!("simulating extern module: {}", module.name_id()); | ||||
|                 todo!("simulating extern module: {:?}", module); | ||||
|             } | ||||
|         } | ||||
|         self.insns.push(Insn::Return, module.source_location()); | ||||
|         CompiledModule { step_entry_pc } | ||||
|         let hashbrown::hash_map::Entry::Vacant(entry) = self.modules.entry(*module) else { | ||||
|             unreachable!("compiled same instantiated module twice"); | ||||
|         }; | ||||
|         entry.insert(CompiledModule { module_io }) | ||||
|     } | ||||
|     pub fn run(mut self) -> Compiled { | ||||
|         while let Some(module) = self.module_queue.pop() { | ||||
|             let compiling_module = self.modules[&module].clone(); | ||||
|             let mut compiling_module = compiling_module.borrow_mut(); | ||||
|             let CompilingModule { compiled_module } = &mut *compiling_module; | ||||
|             assert!(compiled_module.is_none()); | ||||
|             *compiled_module = Some(self.compile_module(module)); | ||||
|         } | ||||
|         self.compile_module(InstantiatedModule::Base(self.base_module).intern_sized()); | ||||
|         Compiled { | ||||
|             insns: Insns::from(self.insns).intern_sized(), | ||||
|             modules: self | ||||
|                 .modules | ||||
|                 .into_iter() | ||||
|                 .map(|(module, compiling_module)| { | ||||
|                     ( | ||||
|                         module, | ||||
|                         Rc::into_inner(compiling_module) | ||||
|                             .expect("only reference is Compiler::modules") | ||||
|                             .into_inner() | ||||
|                             .compiled_module | ||||
|                             .expect("module is compiled by Compiler::run"), | ||||
|                     ) | ||||
|                 }) | ||||
|                 .collect(), | ||||
|             modules: self.modules, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| struct CompiledModule { | ||||
|     step_entry_pc: usize, | ||||
|     module_io: Interned<[CompiledValue]>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct Compiled { | ||||
|     insns: Interned<Insns>, | ||||
|     modules: HashMap<Interned<Module<Bundle>>, CompiledModule>, | ||||
|     insns: Interned<Insns<InsnsBuildingDone>>, | ||||
|     modules: HashMap<InstantiatedModule, CompiledModule>, | ||||
| } | ||||
| 
 | ||||
| impl Compiled { | ||||
|     pub fn new<T: BundleType>(module: Module<T>) -> Self { | ||||
|         let mut compiler = Compiler::new(); | ||||
|         compiler.add_module(module.canonical().intern()); | ||||
|         compiler.run() | ||||
|         Compiler::new(module.canonical().intern()).run() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue