Skip to main content

veryl_analyzer/conv/
ir.rs

1use crate::conv::checker::alias::{AliasType, check_alias_target};
2use crate::conv::checker::clock_domain::check_clock_domain;
3use crate::conv::checker::generic::check_generic_bound;
4use crate::conv::checker::proto::check_proto;
5use crate::conv::utils::{check_module_with_unevaluable_generic_parameters, get_component};
6use crate::conv::{Affiliation, Context, Conv};
7use crate::ir::{self, IrResult, VarKind, VarPath, Variable};
8use crate::symbol::SymbolKind;
9use crate::symbol_table;
10use crate::{HashMap, ir_error};
11use veryl_parser::token_range::TokenRange;
12use veryl_parser::veryl_grammar_trait::*;
13
14/// Memo key for a top-level component's direct conversion, or `None` when
15/// sharing must not apply: a non-`veryl test` run (instantiation clears bodies)
16/// or a generic template.
17fn top_level_memo_sig(context: &Context, identifier: &Identifier) -> Option<ir::Signature> {
18    if context.config.retain_component_body && !context.in_generic {
19        symbol_table::resolve(identifier).ok().map(|s| {
20            let mut sig = ir::Signature::new(s.found.id);
21            sig.normalize();
22            sig
23        })
24    } else {
25        None
26    }
27}
28
29impl Conv<&Veryl> for ir::Ir {
30    fn conv(context: &mut Context, value: &Veryl) -> IrResult<Self> {
31        let mut components = vec![];
32
33        for x in &value.veryl_list {
34            // Dependency testbenches are skipped in pass1, so their symbols
35            // don't exist here; exclude them from conversion as well.
36            let items: Vec<&DescriptionItem> = if context.in_dependency {
37                crate::attribute::description_items_excluding_tests(&x.description_group)
38            } else {
39                x.description_group.as_ref().into()
40            };
41            for item in &items {
42                // ignore IrError of generic top-level components
43                let in_generic = context.in_generic;
44                if item.is_generic() {
45                    context.in_generic = true;
46                }
47
48                match item {
49                    DescriptionItem::DescriptionItemOptPublicDescriptionItem(x) => {
50                        match x.public_description_item.as_ref() {
51                            PublicDescriptionItem::ModuleDeclaration(x) => {
52                                let decl = x.module_declaration.as_ref();
53                                if let Some(sig) =
54                                    top_level_memo_sig(context, decl.identifier.as_ref())
55                                {
56                                    let token: TokenRange = decl.identifier.as_ref().into();
57                                    if let Ok(component) = get_component(context, &sig, token)
58                                        && let ir::Component::Module(m) = component.as_ref()
59                                    {
60                                        let mut component = m.clone();
61                                        if check_module_with_unevaluable_generic_parameters(
62                                            &decl.identifier,
63                                        ) {
64                                            component.suppress_unassigned = true;
65                                        }
66                                        components.push(ir::Component::Module(component));
67                                    }
68                                } else {
69                                    let ret: IrResult<ir::Module> = Conv::conv(context, decl);
70                                    if let Ok(mut component) = ret {
71                                        // suppress unassigned check for modules with unevaluable generic parameters
72                                        if check_module_with_unevaluable_generic_parameters(
73                                            &decl.identifier,
74                                        ) {
75                                            component.suppress_unassigned = true;
76                                        }
77                                        components.push(ir::Component::Module(component));
78                                    }
79                                }
80                            }
81                            PublicDescriptionItem::InterfaceDeclaration(x) => {
82                                let decl = x.interface_declaration.as_ref();
83                                if let Some(sig) =
84                                    top_level_memo_sig(context, decl.identifier.as_ref())
85                                {
86                                    let token: TokenRange = decl.identifier.as_ref().into();
87                                    let _ = get_component(context, &sig, token);
88                                } else {
89                                    let _: IrResult<ir::Interface> = Conv::conv(context, decl);
90                                }
91                            }
92                            PublicDescriptionItem::PackageDeclaration(x) => {
93                                let _: IrResult<()> =
94                                    Conv::conv(context, x.package_declaration.as_ref());
95                            }
96                            PublicDescriptionItem::ProtoDeclaration(x) => {
97                                match x.proto_declaration.proto_declaration_group.as_ref() {
98                                    ProtoDeclarationGroup::ProtoModuleDeclaration(x) => {
99                                        let _: IrResult<ir::Module> = Conv::conv(
100                                            context,
101                                            x.proto_module_declaration.as_ref(),
102                                        );
103                                    }
104                                    ProtoDeclarationGroup::ProtoInterfaceDeclaration(x) => {
105                                        let _: IrResult<()> = Conv::conv(
106                                            context,
107                                            x.proto_interface_declaration.as_ref(),
108                                        );
109                                    }
110                                    ProtoDeclarationGroup::ProtoPackageDeclaration(x) => {
111                                        let _: IrResult<()> = Conv::conv(
112                                            context,
113                                            x.proto_package_declaration.as_ref(),
114                                        );
115                                    }
116                                }
117                            }
118                            PublicDescriptionItem::AliasDeclaration(x) => {
119                                let _: IrResult<()> =
120                                    Conv::conv(context, x.alias_declaration.as_ref());
121                            }
122                            PublicDescriptionItem::FunctionDeclaration(x) => {
123                                conv_global_function(context, x.function_declaration.as_ref());
124                            }
125                        }
126                    }
127                    DescriptionItem::BindDeclaration(x) => {
128                        let _: IrResult<()> = Conv::conv(context, x.bind_declaration.as_ref());
129                    }
130                    DescriptionItem::EmbedDeclaration(x) => {
131                        let _: IrResult<()> = Conv::conv(context, x.embed_declaration.as_ref());
132                    }
133                    DescriptionItem::ImportDeclaration(x) => {
134                        let _: IrResult<ir::DeclarationBlock> =
135                            Conv::conv(context, x.import_declaration.as_ref());
136                    }
137                    _ => (),
138                }
139
140                if item.is_generic() {
141                    context.in_generic = in_generic;
142                }
143            }
144        }
145
146        Ok(ir::Ir { components })
147    }
148}
149
150fn conv_global_function(context: &mut Context, value: &FunctionDeclaration) {
151    let upper_context = context;
152    let mut context = Context::default();
153    context.inherit(upper_context);
154
155    context.in_global_func = Some(value.identifier.identifier_token.token);
156    let _: IrResult<()> = Conv::conv(&mut context, value);
157    context.in_global_func = None;
158
159    upper_context.inherit(&mut context);
160}
161
162fn collect_interface_members(context: &Context) -> HashMap<ir::VarId, Variable> {
163    // Rescanning every port per variable path is quadratic, and a module
164    // without modport ports has no interface members at all.
165    let modport_ports: Vec<(&VarPath, usize)> = context
166        .port_types
167        .iter()
168        .filter(|(_, (r#type, _))| matches!(r#type.kind, ir::TypeKind::Modport(_, _)))
169        .map(|(port, (r#type, _))| (port, r#type.array.dims()))
170        .collect();
171    if modport_ports.is_empty() {
172        return HashMap::default();
173    }
174
175    context
176        .var_paths
177        .iter()
178        .filter_map(|(path, (id, comptime))| {
179            if context.variables.contains_key(id) {
180                return None;
181            }
182            let (port, port_dimensions) = modport_ports
183                .iter()
184                .filter(|(port, _)| path.0.len() > port.0.len() && path.starts_with(&port.0))
185                .max_by_key(|(port, _)| port.0.len())?;
186            let mut variable = Variable::new(
187                *id,
188                path.clone(),
189                VarKind::Variable,
190                comptime.r#type.clone(),
191                vec![],
192                Affiliation::Module,
193                &comptime.token,
194                0,
195            );
196            variable.set_leading_array_path_offset(*port_dimensions, path.0.len() - port.0.len());
197            Some((*id, variable))
198        })
199        .collect()
200}
201
202impl Conv<&ModuleDeclaration> for ir::Module {
203    fn conv(context: &mut Context, value: &ModuleDeclaration) -> IrResult<Self> {
204        Conv::conv(context, (value, false))
205    }
206}
207
208impl Conv<(&ModuleDeclaration, bool)> for ir::Module {
209    fn conv(context: &mut Context, value: (&ModuleDeclaration, bool)) -> IrResult<Self> {
210        let (module_declaration, header_only) = value;
211
212        let mut declarations = vec![];
213
214        // each top-level component has independent context
215        let upper_context = context;
216        let mut context = Context::default();
217        context.inherit(upper_context);
218
219        let _guard = context.conv_profile_guard(module_declaration.identifier.text());
220
221        // pop_affiliation is not necessary because the local `context` will be dropped
222        context.push_affiliation(Affiliation::Module);
223
224        let signature = if let Ok(symbol) =
225            symbol_table::resolve(module_declaration.identifier.as_ref())
226            && let SymbolKind::Module(x) = &symbol.found.kind
227            && !x.is_proto
228        {
229            context.push_namespace(symbol.found.inner_namespace());
230            context.in_test_module = x.test.is_some();
231            if let Some(x) = x.default_clock {
232                let path = VarPath::new(symbol_table::get(x).unwrap().token.text);
233                context.set_default_clock(path, x);
234            }
235            if let Some(x) = x.default_reset {
236                let path = VarPath::new(symbol_table::get(x).unwrap().token.text);
237                context.set_default_reset(path, x);
238            }
239            ir::Signature::new(symbol.found.id)
240        } else {
241            let token: TokenRange = module_declaration.identifier.as_ref().into();
242            return Err(ir_error!(token));
243        };
244
245        if let Some(x) = &module_declaration.module_declaration_opt {
246            check_generic_bound(&mut context, &x.with_generic_parameter);
247            let items: Vec<_> = x
248                .with_generic_parameter
249                .with_generic_parameter_list
250                .as_ref()
251                .into();
252            for item in items {
253                let _ret: IrResult<()> = Conv::conv(&mut context, item);
254            }
255        }
256
257        if let Some(x) = &module_declaration.module_declaration_opt0 {
258            check_proto(
259                &mut context,
260                &module_declaration.identifier,
261                &x.scoped_identifier,
262            );
263        }
264
265        if let Some(x) = &module_declaration.module_declaration_opt1
266            && let Some(x) = &x.with_parameter.with_parameter_opt
267        {
268            let items: Vec<_> = x.with_parameter_list.as_ref().into();
269            for item in items {
270                let _ret: IrResult<()> = Conv::conv(&mut context, item);
271            }
272        }
273
274        if let Some(x) = &module_declaration.module_declaration_opt2
275            && let Some(x) = &x.port_declaration.port_declaration_opt
276        {
277            let items: Vec<_> = x.port_declaration_list.as_ref().into();
278            for item in items {
279                let _ret: IrResult<()> = Conv::conv(&mut context, item);
280            }
281        }
282
283        if !header_only {
284            let mut items = vec![];
285            for x in &module_declaration.module_declaration_list {
286                let group: Vec<_> = x.module_group.as_ref().into();
287                items.extend(group);
288            }
289
290            // Module items are order-free: initial/final bodies consume
291            // state that other declarations register during conversion
292            // (component parameters, elaborated instance components), so
293            // they convert after everything else. The declaration order in
294            // the resulting IR still follows the source.
295            let is_tb_block = |item: &&ModuleItem| {
296                matches!(
297                    item.generate_item.as_ref(),
298                    GenerateItem::InitialDeclaration(_) | GenerateItem::FinalDeclaration(_)
299                )
300            };
301            let mut blocks: Vec<Vec<ir::Declaration>> = Vec::new();
302            blocks.resize_with(items.len(), Vec::new);
303            for deferred in [false, true] {
304                for (i, item) in items.iter().enumerate() {
305                    if is_tb_block(item) != deferred {
306                        continue;
307                    }
308                    let ret: IrResult<ir::DeclarationBlock> =
309                        Conv::conv(&mut context, item.generate_item.as_ref());
310
311                    if let Ok(block) = ret {
312                        blocks[i] = block.0;
313                    }
314                }
315            }
316            for mut block in blocks {
317                declarations.append(&mut block);
318            }
319        }
320
321        // This check must be after default clock/reset are registered in context
322        if let (Some(clock), Some(reset)) =
323            (context.get_default_clock(), context.get_default_reset())
324        {
325            check_clock_domain(
326                &mut context,
327                &clock.0.comptime,
328                &reset.0.comptime,
329                &module_declaration.module.module_token.token,
330            );
331        }
332
333        declarations.retain(|x| !x.is_null());
334        let interface_members = collect_interface_members(&context);
335        let port_types = context.drain_port_types();
336        let variables = context.drain_variables();
337        let functions = context.drain_functions();
338
339        let mut ports = HashMap::default();
340
341        for (id, var) in &variables {
342            if var.kind.is_port() {
343                ports.insert(var.path.clone(), *id);
344            }
345        }
346
347        context.pop_namespace();
348        upper_context.inherit(&mut context);
349
350        Ok(ir::Module {
351            name: module_declaration.identifier.text(),
352            signature,
353            token: module_declaration.identifier.as_ref().into(),
354            ports,
355            port_types,
356            variables,
357            functions,
358            interface_members,
359            declarations,
360            suppress_unassigned: false,
361            per_decl_refs: HashMap::default(),
362            assign_tokens: HashMap::default(),
363            ff_table: ir::FfTable::default(),
364        })
365    }
366}
367
368impl Conv<&InterfaceDeclaration> for () {
369    fn conv(context: &mut Context, value: &InterfaceDeclaration) -> IrResult<Self> {
370        if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
371            && matches!(symbol.found.kind, SymbolKind::Interface(ref x) if !x.is_proto)
372        {
373            context.push_namespace(symbol.found.inner_namespace());
374        } else {
375            let token: TokenRange = value.identifier.as_ref().into();
376            return Err(ir_error!(token));
377        }
378
379        if let Some(x) = &value.interface_declaration_opt {
380            check_generic_bound(context, &x.with_generic_parameter);
381            let items: Vec<_> = x
382                .with_generic_parameter
383                .with_generic_parameter_list
384                .as_ref()
385                .into();
386            for item in items {
387                let _ret: IrResult<()> = Conv::conv(context, item);
388            }
389        }
390
391        if let Some(x) = &value.interface_declaration_opt0 {
392            check_proto(context, &value.identifier, &x.scoped_identifier);
393        }
394
395        if let Some(x) = &value.interface_declaration_opt1
396            && let Some(x) = &x.with_parameter.with_parameter_opt
397        {
398            let items: Vec<_> = x.with_parameter_list.as_ref().into();
399            for item in items {
400                let _ret: IrResult<()> = Conv::conv(context, item);
401            }
402        }
403
404        for x in &value.interface_declaration_list {
405            let items: Vec<_> = x.interface_group.as_ref().into();
406            for item in items {
407                match item {
408                    InterfaceItem::GenerateItem(x) => {
409                        let _: IrResult<ir::DeclarationBlock> =
410                            Conv::conv(context, x.generate_item.as_ref());
411                    }
412                    InterfaceItem::MixinDeclaration(x) => {
413                        let _: IrResult<()> = Conv::conv(context, x.mixin_declaration.as_ref());
414                    }
415                    InterfaceItem::ModportDeclaration(x) => {
416                        let _: IrResult<()> = Conv::conv(context, x.modport_declaration.as_ref());
417                    }
418                }
419            }
420        }
421
422        context.pop_namespace();
423
424        Ok(())
425    }
426}
427
428impl Conv<&InterfaceDeclaration> for ir::Interface {
429    fn conv(context: &mut Context, value: &InterfaceDeclaration) -> IrResult<Self> {
430        // each top-level component has independent context
431        let upper_context = context;
432        let mut context = Context::default();
433        context.inherit(upper_context);
434
435        let _guard = context.conv_profile_guard(value.identifier.text());
436
437        // pop_affiliation is not necessary because the local `context` will be dropped
438        context.push_affiliation(Affiliation::Interface);
439
440        let ret: IrResult<()> = Conv::conv(&mut context, value);
441
442        let var_paths = context.drain_var_paths();
443        let func_paths = context.drain_func_paths();
444        let mut variables = context.drain_variables();
445        let functions = context.drain_functions();
446        let modports = context.drain_modports();
447
448        let variables = variables
449            .extract_if(|_, v| v.affiliation != Affiliation::Function)
450            .collect();
451
452        context.pop_namespace();
453        upper_context.inherit(&mut context);
454
455        ret?;
456
457        Ok(ir::Interface {
458            name: value.identifier.text(),
459            var_paths,
460            func_paths,
461            variables,
462            functions,
463            modports,
464        })
465    }
466}
467
468impl Conv<&PackageDeclaration> for () {
469    fn conv(context: &mut Context, value: &PackageDeclaration) -> IrResult<Self> {
470        // each top-level component has independent context
471        let upper_context = context;
472        let mut context = Context::default();
473        context.inherit(upper_context);
474
475        let _guard = context.conv_profile_guard(value.identifier.text());
476
477        // pop_affiliation is not necessary because the local `context` will be dropped
478        context.push_affiliation(Affiliation::Package);
479
480        if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
481            && matches!(symbol.found.kind, SymbolKind::Package(ref x) if !x.is_proto)
482        {
483            context.push_namespace(symbol.found.inner_namespace());
484        } else {
485            let token: TokenRange = value.identifier.as_ref().into();
486            return Err(ir_error!(token));
487        }
488
489        if let Some(x) = &value.package_declaration_opt {
490            check_generic_bound(&mut context, &x.with_generic_parameter);
491        }
492
493        if let Some(x) = &value.package_declaration_opt0 {
494            check_proto(&mut context, &value.identifier, &x.scoped_identifier);
495        }
496
497        for x in &value.package_declaration_list {
498            let items: Vec<_> = x.package_group.as_ref().into();
499            for item in items {
500                match item {
501                    PackageItem::ConstDeclaration(x) => {
502                        let _: IrResult<ir::Declaration> =
503                            Conv::conv(&mut context, x.const_declaration.as_ref());
504                    }
505                    PackageItem::FunctionDeclaration(x) => {
506                        let _: IrResult<()> =
507                            Conv::conv(&mut context, x.function_declaration.as_ref());
508                    }
509                    PackageItem::StructUnionDeclaration(x) => {
510                        let _: IrResult<()> =
511                            Conv::conv(&mut context, x.struct_union_declaration.as_ref());
512                    }
513                    _ => (),
514                }
515            }
516        }
517
518        context.pop_namespace();
519        upper_context.inherit(&mut context);
520
521        Ok(())
522    }
523}
524
525impl Conv<&ProtoModuleDeclaration> for ir::Module {
526    fn conv(context: &mut Context, value: &ProtoModuleDeclaration) -> IrResult<Self> {
527        // each top-level component has independent context
528        let upper_context = context;
529        let mut context = Context::default();
530        context.inherit(upper_context);
531
532        // pop_affiliation is not necessary because the local `context` will be dropped
533        context.push_affiliation(Affiliation::ProtoModule);
534
535        let signature = if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
536            && matches!(symbol.found.kind, SymbolKind::Module(ref x) if x.is_proto)
537        {
538            context.push_namespace(symbol.found.inner_namespace());
539            ir::Signature::new(symbol.found.id)
540        } else {
541            let token: TokenRange = value.identifier.as_ref().into();
542            return Err(ir_error!(token));
543        };
544
545        if let Some(x) = &value.proto_module_declaration_opt
546            && let Some(x) = &x.with_parameter.with_parameter_opt
547        {
548            let items: Vec<_> = x.with_parameter_list.as_ref().into();
549            for item in items {
550                let _ret: IrResult<()> = Conv::conv(&mut context, item);
551            }
552        }
553
554        if let Some(x) = &value.proto_module_declaration_opt0
555            && let Some(x) = &x.port_declaration.port_declaration_opt
556        {
557            let items: Vec<_> = x.port_declaration_list.as_ref().into();
558            for item in items {
559                let _ret: IrResult<()> = Conv::conv(&mut context, item);
560            }
561        }
562
563        let interface_members = collect_interface_members(&context);
564        let port_types = context.drain_port_types();
565        let variables = context.drain_variables();
566
567        let mut ports = HashMap::default();
568
569        for (id, var) in &variables {
570            if var.kind.is_port() {
571                ports.insert(var.path.clone(), *id);
572            }
573        }
574
575        context.pop_namespace();
576        upper_context.inherit(&mut context);
577
578        Ok(ir::Module {
579            name: value.identifier.text(),
580            signature,
581            token: value.identifier.as_ref().into(),
582            ports,
583            port_types,
584            variables,
585            functions: HashMap::default(),
586            interface_members,
587            declarations: vec![],
588            suppress_unassigned: false,
589            per_decl_refs: HashMap::default(),
590            assign_tokens: HashMap::default(),
591            ff_table: ir::FfTable::default(),
592        })
593    }
594}
595
596impl Conv<&ProtoInterfaceDeclaration> for () {
597    fn conv(context: &mut Context, value: &ProtoInterfaceDeclaration) -> IrResult<Self> {
598        context.push_affiliation(Affiliation::ProtoInterface);
599
600        if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
601            && matches!(symbol.found.kind, SymbolKind::Interface(ref x) if x.is_proto)
602        {
603            context.push_namespace(symbol.found.inner_namespace());
604        } else {
605            let token: TokenRange = value.identifier.as_ref().into();
606            return Err(ir_error!(token));
607        }
608
609        for x in &value.proto_interface_declaration_list {
610            if let ProtoInterfaceItem::ProtoAliasDeclaration(x) = x.proto_interface_item.as_ref() {
611                let r#type = match x
612                    .proto_alias_declaration
613                    .proto_alias_declaration_group
614                    .as_ref()
615                {
616                    ProtoAliasDeclarationGroup::Module(_) => AliasType::ProtoModule,
617                    ProtoAliasDeclarationGroup::Interface(_) => AliasType::ProtoInterface,
618                    ProtoAliasDeclarationGroup::Package(_) => AliasType::ProtoPackage,
619                };
620                check_alias_target(
621                    context,
622                    &x.proto_alias_declaration.scoped_identifier,
623                    r#type,
624                );
625            }
626        }
627
628        context.pop_affiliation();
629        context.pop_namespace();
630        Ok(())
631    }
632}
633
634impl Conv<&ProtoPackageDeclaration> for () {
635    fn conv(context: &mut Context, value: &ProtoPackageDeclaration) -> IrResult<Self> {
636        context.push_affiliation(Affiliation::ProtoPackage);
637
638        if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
639            && matches!(symbol.found.kind, SymbolKind::Package(ref x) if x.is_proto)
640        {
641            context.push_namespace(symbol.found.inner_namespace());
642        } else {
643            let token: TokenRange = value.identifier.as_ref().into();
644            return Err(ir_error!(token));
645        }
646
647        for x in &value.proto_package_declaration_list {
648            if let ProtoPacakgeItem::ProtoAliasDeclaration(x) = x.proto_pacakge_item.as_ref() {
649                let r#type = match x
650                    .proto_alias_declaration
651                    .proto_alias_declaration_group
652                    .as_ref()
653                {
654                    ProtoAliasDeclarationGroup::Module(_) => AliasType::ProtoModule,
655                    ProtoAliasDeclarationGroup::Interface(_) => AliasType::ProtoInterface,
656                    ProtoAliasDeclarationGroup::Package(_) => AliasType::ProtoPackage,
657                };
658                check_alias_target(
659                    context,
660                    &x.proto_alias_declaration.scoped_identifier,
661                    r#type,
662                );
663            }
664        }
665
666        context.pop_affiliation();
667        context.pop_namespace();
668        Ok(())
669    }
670}