Skip to main content

celox_frontend_core/shared/
lookup.rs

1use std::fmt;
2
3use celox_design::{AbsoluteAddrBase, InstanceId, ModuleId, StateAddr, VariableMetadata};
4use serde::{Deserialize, Serialize};
5
6use crate::{HashMap, HashSet};
7
8pub type SourceAddr = AbsoluteAddrBase<SourceVarId>;
9
10/// Frontend-local identity of a source variable within one module.
11///
12/// This is deliberately distinct from every parser or analyzer's variable ID.
13/// A frontend projects its native IDs into this namespace before constructing
14/// source lookup metadata retained by the runtime.
15#[derive(
16    Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
17)]
18pub struct SourceVarId(pub u32);
19
20impl fmt::Display for SourceVarId {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        write!(f, "var{}", self.0)
23    }
24}
25
26/// Source-language-independent role of a frontend variable.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28pub enum VariableKind {
29    Parameter,
30    Constant,
31    Input,
32    Output,
33    Inout,
34    Variable,
35    Let,
36}
37
38impl VariableKind {
39    pub const fn is_port(self) -> bool {
40        matches!(self, Self::Input | Self::Output | Self::Inout)
41    }
42
43    pub const fn description(self) -> &'static str {
44        match self {
45            Self::Parameter => "parameter",
46            Self::Constant => "constant",
47            Self::Input => "input",
48            Self::Output => "output",
49            Self::Inout => "inout",
50            Self::Variable => "variable",
51            Self::Let => "let-bounded variable",
52        }
53    }
54}
55
56#[derive(Clone, Serialize, Deserialize)]
57pub struct VariableInfo {
58    pub id: SourceVarId,
59    pub path: Vec<String>,
60    pub var_kind: VariableKind,
61    pub signed: bool,
62    pub metadata: VariableMetadata,
63    /// Per-dimension sizes for the packed shape of the variable.
64    ///
65    /// `VariableMetadata::array_dims` deliberately only describes unpacked
66    /// arrays because that is the source-independent storage shape. The
67    /// testbench adapter also needs packed shape for chained selects.
68    pub packed_dims: Vec<usize>,
69}
70
71impl std::ops::Deref for VariableInfo {
72    type Target = VariableMetadata;
73
74    fn deref(&self) -> &Self::Target {
75        &self.metadata
76    }
77}
78
79impl fmt::Debug for VariableInfo {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        f.debug_struct("VariableInfo")
82            .field("width", &self.width)
83            .field("id", &self.id)
84            .field("is_4state", &self.is_4state)
85            .field("signed", &self.signed)
86            .field("kind", &self.kind)
87            .field("type_kind", &self.type_kind)
88            .finish()
89    }
90}
91
92#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
93pub struct InstancePath(pub Vec<(String, usize)>);
94
95/// Source-language-independent lookup retained for diagnostics and public paths.
96/// Parser-native IDs are projected into [`SourceVarId`] before this artifact is
97/// built and do not cross into runtime metadata.
98#[derive(Clone, Default, Serialize, Deserialize)]
99pub struct FrontendLookup {
100    pub instance_ids: HashMap<InstancePath, InstanceId>,
101    pub instance_module: HashMap<InstanceId, ModuleId>,
102    /// Elaborated children whose source-facing name requires an index.
103    pub indexed_instances: HashSet<InstanceId>,
104    pub module_variables: HashMap<ModuleId, HashMap<SourceVarId, VariableInfo>>,
105    /// Reverse index from source path to source variable ID. `None` marks a
106    /// path that is ambiguous within the module.
107    pub module_var_path_index: HashMap<ModuleId, HashMap<Vec<String>, Option<SourceVarId>>>,
108    pub module_names: HashMap<ModuleId, String>,
109    /// Bidirectional boundary map between frontend source identities and the
110    /// dense source-independent state identities consumed by later phases.
111    pub source_to_state: HashMap<SourceAddr, StateAddr>,
112    pub state_to_source: HashMap<StateAddr, SourceAddr>,
113    /// Event aliases projected to the canonical runtime event domain.
114    pub event_aliases: HashMap<StateAddr, StateAddr>,
115    /// Instances that hold the variables of a package, outside the hierarchy.
116    /// Their variables are named `p::v`.
117    pub package_instances: HashSet<InstanceId>,
118}
119
120impl fmt::Debug for FrontendLookup {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        f.debug_struct("FrontendLookup")
123            .field("instances", &self.instance_module.len())
124            .field("modules", &self.module_variables.len())
125            .field("projected_state_objects", &self.source_to_state.len())
126            .finish_non_exhaustive()
127    }
128}
129
130impl FrontendLookup {
131    pub fn instance_path_segments(&self, path: &InstancePath) -> Vec<String> {
132        let mut prefix = Vec::with_capacity(path.0.len());
133        path.0
134            .iter()
135            .map(|(name, index)| {
136                prefix.push((name.clone(), *index));
137                let instance = self.instance_ids.get(&InstancePath(prefix.clone()));
138                if instance.is_some_and(|id| self.indexed_instances.contains(id)) {
139                    format!("{name}[{index}]")
140                } else {
141                    name.clone()
142                }
143            })
144            .collect()
145    }
146
147    pub fn root_instance_and_module(&self) -> Option<(InstanceId, ModuleId)> {
148        let instance_id = *self.instance_ids.get(&InstancePath(Vec::new()))?;
149        let module_id = *self.instance_module.get(&instance_id)?;
150        Some((instance_id, module_id))
151    }
152
153    pub fn root_variable(&self, var_id: SourceVarId) -> Option<(StateAddr, &VariableInfo)> {
154        let (instance_id, _) = self.root_instance_and_module()?;
155        self.instance_variable(instance_id, var_id)
156    }
157
158    pub fn instance_variable(
159        &self,
160        instance_id: InstanceId,
161        var_id: SourceVarId,
162    ) -> Option<(StateAddr, &VariableInfo)> {
163        let module_id = *self.instance_module.get(&instance_id)?;
164        let info = self.module_variables.get(&module_id)?.get(&var_id)?;
165        let address = self.state_address(&SourceAddr {
166            instance_id,
167            var_id,
168        })?;
169        Some((address, info))
170    }
171
172    pub fn root_named_variable(&self, name: &str) -> Option<(StateAddr, &VariableInfo)> {
173        let (_, module_id) = self.root_instance_and_module()?;
174        let var_id = self
175            .module_var_path_index
176            .get(&module_id)?
177            .get(&vec![name.to_string()])
178            .copied()
179            .flatten()?;
180        self.root_variable(var_id)
181    }
182
183    pub fn get_path(&self, address: &SourceAddr) -> String {
184        let instance_path = self
185            .instance_ids
186            .iter()
187            .find(|(_, id)| **id == address.instance_id)
188            .map(|(path, _)| path);
189        let module_id = self.instance_module.get(&address.instance_id).unwrap();
190        let module_vars = self.module_variables.get(module_id).unwrap();
191        let variable_path = module_vars
192            .values()
193            .find(|info| info.id == address.var_id)
194            .map(|info| &info.path);
195
196        let mut instance = Vec::new();
197        if let Some(instance_path) = instance_path {
198            instance.extend(self.instance_path_segments(instance_path));
199        }
200        let variable = variable_path.map(|path| path.join(".")).unwrap_or_default();
201        // The instance of package `p` is named `p::`.
202        if self.package_instances.contains(&address.instance_id) {
203            return format!("{}{variable}", instance.join("."));
204        }
205        instance.extend((!variable.is_empty()).then_some(variable));
206        instance.join(".")
207    }
208
209    pub fn get_state_path(&self, address: &StateAddr) -> String {
210        self.state_to_source
211            .get(address)
212            .map(|source| self.get_path(source))
213            .unwrap_or_else(|| address.to_string())
214    }
215
216    pub fn source_address(&self, address: &StateAddr) -> Option<SourceAddr> {
217        self.state_to_source.get(address).copied()
218    }
219
220    pub fn state_address(&self, address: &SourceAddr) -> Option<StateAddr> {
221        self.source_to_state.get(address).copied()
222    }
223}