Skip to content

Commit 2669b10

Browse files
authored
feat(hir): support interface modports (#292)
2 parents d5567d2 + b1cffb3 commit 2669b10

14 files changed

Lines changed: 478 additions & 15 deletions

crates/hir/src/def_id.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ impl DefId {
6464
DefLoc::Decl(InContainer { cont_id, .. }) => cont_id,
6565
DefLoc::Typedef(InContainer { cont_id, .. }) => cont_id,
6666
DefLoc::Instance(InModule { module_id, .. }) => module_id.into(),
67+
DefLoc::Modport(InModule { module_id, .. }) => module_id.into(),
6768
DefLoc::Stmt(InContainer { cont_id, .. }) => cont_id,
6869
}
6970
}
@@ -106,6 +107,7 @@ impl DefId {
106107
}
107108
DefLoc::Typedef(_) => DefKind::Typedef,
108109
DefLoc::Instance(_) => DefKind::Instance,
110+
DefLoc::Modport(_) => DefKind::Modport,
109111
DefLoc::Stmt(_) => DefKind::Stmt,
110112
}
111113
}
@@ -148,6 +150,9 @@ impl DefId {
148150
DefLoc::Instance(InModule { value, module_id }) => {
149151
module_id.to_container(db).get(value).name.clone()
150152
}
153+
DefLoc::Modport(InModule { value, module_id }) => {
154+
module_id.to_container(db).get(value).name.clone()
155+
}
151156
DefLoc::Stmt(InContainer { value, cont_id }) => {
152157
cont_id.to_container(db).get(value).label.clone()
153158
}
@@ -220,6 +225,10 @@ impl DefId {
220225
let range = module_id.to_container_src_map(db).get(value)?.name_range()?;
221226
Some(InFile::new(module_id.file_id, range))
222227
}
228+
DefLoc::Modport(InModule { value, module_id }) => {
229+
let range = module_id.to_container_src_map(db).get(value)?.name_range()?;
230+
Some(InFile::new(module_id.file_id, range))
231+
}
223232
DefLoc::Stmt(InContainer { value, cont_id }) => {
224233
let range = cont_id.to_container_src_map(db).get(value)?.name_range()?;
225234
Some(InFile::new(cont_id.file_id(db).into(), range))
@@ -290,6 +299,10 @@ impl DefId {
290299
let range = module_id.to_container_src_map(db).get(value)?.range();
291300
InFile::new(module_id.file_id, range)
292301
}
302+
DefLoc::Modport(InModule { value, module_id }) => {
303+
let range = module_id.to_container_src_map(db).get(value)?.range();
304+
InFile::new(module_id.file_id, range)
305+
}
293306
DefLoc::Stmt(InContainer { value, cont_id }) => {
294307
let range = cont_id.to_container_src_map(db).get(value)?.range();
295308
InFile::new(cont_id.file_id(db).into(), range)

crates/hir/src/display.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,19 @@ impl HirDisplay for Ty {
130130
f.write_str("module")
131131
}
132132
}
133+
Ty::VirtualInterface { def, modport } => {
134+
f.write_str("virtual interface ")?;
135+
if let Some(name) = def.name(f.db) {
136+
f.write_str(&name)?;
137+
} else {
138+
f.write_str("interface")?;
139+
}
140+
if let Some(modport_name) = modport.and_then(|modport| modport.name(f.db)) {
141+
f.write_str(".")?;
142+
f.write_str(&modport_name)?;
143+
}
144+
Ok(())
145+
}
133146
Ty::GenerateBlock(generate_block_id) => {
134147
let block = f.db.generate_block(*generate_block_id);
135148
if let Some(name) = &block.name {

crates/hir/src/hir_def/module.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use instantiation::{
88
ParamAssign, ParamAssignSrc, PortConn, PortConnSrc, impl_lower_instantiation,
99
};
1010
use la_arena::{Arena, Idx, IdxRange, RawIdx};
11+
use modport::{LowerModport, ModportDef, ModportId, ModportSrc};
1112
use port::{
1213
NonAnsiPort, NonAnsiPortId, NonAnsiPortSrc, PortDecl, PortDeclId, PortDeclSrc, PortRef,
1314
PortRefId, PortRefSrc, PortSrcs, Ports,
@@ -67,6 +68,7 @@ pub mod continuous_assgin;
6768
pub mod defparam;
6869
pub mod generate;
6970
pub mod instantiation;
71+
pub mod modport;
7072
pub mod port;
7173
pub mod specify;
7274

@@ -91,6 +93,7 @@ define_container! {
9193
typedefs: [Typedef],
9294
structs: [StructDef],
9395
subroutines: [Subroutine],
96+
modports: [ModportDef],
9497
package_imports: [PackageImport],
9598

9699
instantiations: [Instantiation],
@@ -131,6 +134,7 @@ define_container! {
131134
typedef_srcs: [Typedef | TypedefSrc],
132135
struct_srcs: [StructDef | StructSrc],
133136
subroutine_srcs: [Subroutine | SubroutineSrc],
137+
modport_srcs: [ModportDef | ModportSrc],
134138

135139
instantiation_srcs: [Instantiation | InstantiationSrc],
136140
inst_param_assign_srcs: [ParamAssign | ParamAssignSrc],
@@ -279,6 +283,7 @@ impl ModuleSourceMap {
279283
ModuleItem::PortDeclId(idx) => self.get(*idx)?.ptr(),
280284
ModuleItem::TypedefId(idx) => self.get(*idx)?.ptr(),
281285
ModuleItem::SubroutineId(idx) => self.get(*idx)?.node,
286+
ModuleItem::ModportId(idx) => self.get(*idx)?.node,
282287
})
283288
}
284289
}
@@ -298,6 +303,7 @@ define_enum_deriving_from! {
298303
PortDeclId(PortDeclId),
299304
TypedefId(TypedefId),
300305
SubroutineId(LocalSubroutineId),
306+
ModportId(ModportId),
301307
}
302308
}
303309

@@ -611,8 +617,14 @@ impl LowerModuleCtx<'_> {
611617
NetAlias(_) => continue,
612618

613619
// Modport
614-
ModportDeclaration(_)
615-
| ModportClockingPort(_)
620+
ModportDeclaration(modport) => {
621+
for modport_id in self.lower_modport_declaration(modport) {
622+
self.module_source_map.items.push(modport_id.into());
623+
}
624+
self.region_tree.handle_node(member.syntax());
625+
continue;
626+
}
627+
ModportClockingPort(_)
616628
| ModportSimplePortList(_)
617629
| ModportSubroutinePortList(_) => continue,
618630

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
use la_arena::Idx;
2+
use smallvec::SmallVec;
3+
use syntax::{
4+
SyntaxKind, SyntaxToken, TokenKind,
5+
ast::{self, AstNode},
6+
ptr::{SyntaxNodePtr, SyntaxTokenPtr},
7+
slang_ext::AstNodeExt,
8+
};
9+
use utils::text_edit::TextRange;
10+
11+
use super::{LowerModuleCtx, port::PortDirection};
12+
use crate::{
13+
hir_def::{Ident, alloc_idx_and_src, lower_ident_opt},
14+
source_map::{FromSourceAst, IsNamedSrc, IsSrc, SourceAst, root_token_in},
15+
};
16+
17+
#[derive(Debug, PartialEq, Eq, Clone)]
18+
pub struct ModportDef {
19+
pub name: Option<Ident>,
20+
pub ports: SmallVec<[ModportPort; 4]>,
21+
}
22+
23+
pub type ModportId = Idx<ModportDef>;
24+
25+
#[derive(Debug, PartialEq, Eq, Clone)]
26+
pub struct ModportPort {
27+
pub name: Ident,
28+
pub dir: PortDirection,
29+
}
30+
31+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
32+
pub struct ModportSrc {
33+
pub node: SyntaxNodePtr,
34+
pub name: Option<SyntaxTokenPtr>,
35+
}
36+
37+
impl IsSrc for ModportSrc {
38+
#[inline]
39+
fn kind(&self) -> SyntaxKind {
40+
self.node.kind()
41+
}
42+
43+
#[inline]
44+
fn range(&self) -> TextRange {
45+
self.node.range()
46+
}
47+
}
48+
49+
impl IsNamedSrc for ModportSrc {
50+
#[inline]
51+
fn name_kind(&self) -> Option<TokenKind> {
52+
self.name.map(|name| name.kind())
53+
}
54+
55+
#[inline]
56+
fn name_range(&self) -> Option<TextRange> {
57+
self.name.map(|name| name.range())
58+
}
59+
}
60+
61+
impl<'a> FromSourceAst<'a, ast::ModportItem<'a>> for ModportSrc {
62+
fn from_source_ast(item: SourceAst<ast::ModportItem<'a>>) -> Self {
63+
let item = item.into_inner();
64+
let syntax = item.syntax();
65+
let name = item
66+
.name()
67+
.and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token));
68+
Self { node: AstNodeExt::to_ptr(&item), name }
69+
}
70+
}
71+
72+
pub(crate) trait LowerModport {
73+
fn lower_modport_declaration(
74+
&mut self,
75+
modport: ast::ModportDeclaration<'_>,
76+
) -> SmallVec<[ModportId; 1]>;
77+
}
78+
79+
impl LowerModport for LowerModuleCtx<'_> {
80+
fn lower_modport_declaration(
81+
&mut self,
82+
modport: ast::ModportDeclaration<'_>,
83+
) -> SmallVec<[ModportId; 1]> {
84+
let mut lowered = SmallVec::new();
85+
for item in modport.items().children() {
86+
let name = lower_ident_opt(item.name());
87+
let ports = lower_modport_ports(item);
88+
let modport_id = alloc_idx_and_src! {
89+
self.file_id;
90+
ModportDef { name, ports } => self.module.modports,
91+
item => self.module_source_map.modport_srcs,
92+
};
93+
lowered.push(modport_id);
94+
}
95+
lowered
96+
}
97+
}
98+
99+
fn lower_modport_ports(item: ast::ModportItem<'_>) -> SmallVec<[ModportPort; 4]> {
100+
let mut ports = SmallVec::new();
101+
102+
// slang models a modport item as named entries whose `ports()` members are
103+
// simple or subroutine port lists; each list then owns its inner ports.
104+
for member in item.ports().ports().children() {
105+
match member {
106+
ast::Member::ModportSimplePortList(port_list) => {
107+
let dir = lower_modport_dir(port_list.direction()).unwrap_or_default();
108+
for port in port_list.ports().children() {
109+
if let Some(name) = lower_modport_port_name(port) {
110+
ports.push(ModportPort { name, dir });
111+
}
112+
}
113+
}
114+
ast::Member::ModportSubroutinePortList(port_list) => {
115+
let dir = lower_modport_subroutine_dir(port_list.import_export());
116+
for port in port_list.ports().children() {
117+
if let Some(name) = lower_modport_port_name(port) {
118+
ports.push(ModportPort { name, dir });
119+
}
120+
}
121+
}
122+
_ => {}
123+
}
124+
}
125+
126+
ports
127+
}
128+
129+
fn lower_modport_port_name(port: ast::ModportPort<'_>) -> Option<Ident> {
130+
match port {
131+
ast::ModportPort::ModportNamedPort(port) => lower_ident_opt(port.name()),
132+
ast::ModportPort::ModportExplicitPort(port) => lower_ident_opt(port.name()),
133+
ast::ModportPort::ModportSubroutinePort(port) => {
134+
lower_ident_opt(rightmost_name_token(port.prototype().name()))
135+
}
136+
}
137+
}
138+
139+
fn rightmost_name_token(name: ast::Name<'_>) -> Option<SyntaxToken<'_>> {
140+
match name {
141+
ast::Name::IdentifierName(name) => name.identifier(),
142+
ast::Name::IdentifierSelectName(name) => name.identifier(),
143+
ast::Name::ScopedName(name) => rightmost_name_token(name.right()),
144+
_ => None,
145+
}
146+
}
147+
148+
fn lower_modport_dir(tok: Option<SyntaxToken<'_>>) -> Option<PortDirection> {
149+
tok.and_then(|tok| match tok.kind() {
150+
TokenKind::INPUT_KEYWORD => Some(PortDirection::Input),
151+
TokenKind::OUTPUT_KEYWORD => Some(PortDirection::Output),
152+
TokenKind::IN_OUT_KEYWORD => Some(PortDirection::Inout),
153+
TokenKind::REF_KEYWORD => Some(PortDirection::Ref),
154+
_ => None,
155+
})
156+
}
157+
158+
fn lower_modport_subroutine_dir(tok: Option<SyntaxToken<'_>>) -> PortDirection {
159+
match tok.map(|tok| tok.kind()) {
160+
Some(TokenKind::EXPORT_KEYWORD) => PortDirection::Output,
161+
_ => PortDirection::Input,
162+
}
163+
}

crates/hir/src/scope.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ impl NameScope {
185185
scope.insert_value_opt(&subroutine.name, def_id(db, subroutine_id));
186186
}
187187

188+
for (modport_id, modport) in module.modports.iter() {
189+
scope.insert_value_opt(&modport.name, def_id(db, InModule::new(module_id, modport_id)));
190+
}
191+
188192
insert_decls_and_typedefs(
189193
&mut scope,
190194
db,

crates/hir/src/semantics/pathres.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ fn resolve_top_level_module_root(
118118
// segment value fallback: `top` alone remains a type-space module name.
119119
let type_res = resolve_name(db, cont_id, ident, NameContext::Type)?;
120120
let module_defs =
121-
type_res.def_ids().iter().copied().filter(|def_id| def_id.kind(db) == DefKind::Module);
121+
type_res.def_ids().iter().copied().filter(|def_id| def_id.kind(db).is_instantiable_def());
122122
PathResolution::from_def_ids(module_defs)
123123
}
124124

@@ -147,7 +147,7 @@ fn resolve_child_name(
147147

148148
pub fn descend_scope(db: &dyn HirDb, def_id: DefId) -> Option<ScopeId> {
149149
match def_id.kind(db) {
150-
DefKind::Module => def_id.as_module(db).map(Into::into),
150+
kind if kind.is_instantiable_def() => def_id.as_module(db).map(Into::into),
151151
DefKind::Instance => {
152152
let instance = def_id.as_instance(db)?;
153153
instance_target_module_id(db, instance.module_id, instance.value).map(Into::into)
@@ -460,4 +460,46 @@ endmodule
460460
DefKind::Net
461461
);
462462
}
463+
464+
#[test]
465+
fn resolve_path_descends_interface_instances_to_modports() {
466+
let db = db_with_root_text(
467+
r#"
468+
interface bus_if;
469+
wire clk;
470+
modport host(input clk);
471+
endinterface
472+
473+
module top;
474+
bus_if u_if();
475+
endmodule
476+
"#,
477+
);
478+
479+
let top = db
480+
.unit_scope()
481+
.module_ids(&db, &ident("top"))
482+
.unique()
483+
.expect("top module should resolve uniquely");
484+
485+
let res = resolve_path(&db, top.into(), &path(&["u_if", "host"]), NameContext::Value)
486+
.expect("interface instance modport should resolve");
487+
488+
let def = res.primary_def_id().expect("modport should produce a definition");
489+
assert_eq!(def.name(&db).as_deref(), Some("host"));
490+
assert_eq!(def.kind(&db), DefKind::Modport);
491+
assert_eq!(
492+
resolved_kind(&db, top.into(), &["u_if", "clk"], NameContext::Value),
493+
DefKind::Net
494+
);
495+
assert_eq!(
496+
resolved_kind(
497+
&db,
498+
ScopeId::File(HirFileId::File(TOP)),
499+
&["top", "u_if", "host"],
500+
NameContext::Value,
501+
),
502+
DefKind::Modport
503+
);
504+
}
463505
}

0 commit comments

Comments
 (0)