Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions crates/hir/src/def_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl DefId {
DefLoc::Decl(InContainer { cont_id, .. }) => cont_id,
DefLoc::Typedef(InContainer { cont_id, .. }) => cont_id,
DefLoc::Instance(InModule { module_id, .. }) => module_id.into(),
DefLoc::Modport(InModule { module_id, .. }) => module_id.into(),
DefLoc::Stmt(InContainer { cont_id, .. }) => cont_id,
}
}
Expand Down Expand Up @@ -106,6 +107,7 @@ impl DefId {
}
DefLoc::Typedef(_) => DefKind::Typedef,
DefLoc::Instance(_) => DefKind::Instance,
DefLoc::Modport(_) => DefKind::Modport,
DefLoc::Stmt(_) => DefKind::Stmt,
}
}
Expand Down Expand Up @@ -148,6 +150,9 @@ impl DefId {
DefLoc::Instance(InModule { value, module_id }) => {
module_id.to_container(db).get(value).name.clone()
}
DefLoc::Modport(InModule { value, module_id }) => {
module_id.to_container(db).get(value).name.clone()
}
DefLoc::Stmt(InContainer { value, cont_id }) => {
cont_id.to_container(db).get(value).label.clone()
}
Expand Down Expand Up @@ -220,6 +225,10 @@ impl DefId {
let range = module_id.to_container_src_map(db).get(value)?.name_range()?;
Some(InFile::new(module_id.file_id, range))
}
DefLoc::Modport(InModule { value, module_id }) => {
let range = module_id.to_container_src_map(db).get(value)?.name_range()?;
Some(InFile::new(module_id.file_id, range))
}
DefLoc::Stmt(InContainer { value, cont_id }) => {
let range = cont_id.to_container_src_map(db).get(value)?.name_range()?;
Some(InFile::new(cont_id.file_id(db).into(), range))
Expand Down Expand Up @@ -290,6 +299,10 @@ impl DefId {
let range = module_id.to_container_src_map(db).get(value)?.range();
InFile::new(module_id.file_id, range)
}
DefLoc::Modport(InModule { value, module_id }) => {
let range = module_id.to_container_src_map(db).get(value)?.range();
InFile::new(module_id.file_id, range)
}
DefLoc::Stmt(InContainer { value, cont_id }) => {
let range = cont_id.to_container_src_map(db).get(value)?.range();
InFile::new(cont_id.file_id(db).into(), range)
Expand Down
13 changes: 13 additions & 0 deletions crates/hir/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ impl HirDisplay for Ty {
f.write_str("module")
}
}
Ty::VirtualInterface { def, modport } => {
f.write_str("virtual interface ")?;
if let Some(name) = def.name(f.db) {
f.write_str(&name)?;
} else {
f.write_str("interface")?;
}
if let Some(modport_name) = modport.and_then(|modport| modport.name(f.db)) {
f.write_str(".")?;
f.write_str(&modport_name)?;
}
Ok(())
}
Ty::GenerateBlock(generate_block_id) => {
let block = f.db.generate_block(*generate_block_id);
if let Some(name) = &block.name {
Expand Down
16 changes: 14 additions & 2 deletions crates/hir/src/hir_def/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use instantiation::{
ParamAssign, ParamAssignSrc, PortConn, PortConnSrc, impl_lower_instantiation,
};
use la_arena::{Arena, Idx, IdxRange, RawIdx};
use modport::{LowerModport, ModportDef, ModportId, ModportSrc};
use port::{
NonAnsiPort, NonAnsiPortId, NonAnsiPortSrc, PortDecl, PortDeclId, PortDeclSrc, PortRef,
PortRefId, PortRefSrc, PortSrcs, Ports,
Expand Down Expand Up @@ -67,6 +68,7 @@ pub mod continuous_assgin;
pub mod defparam;
pub mod generate;
pub mod instantiation;
pub mod modport;
pub mod port;
pub mod specify;

Expand All @@ -91,6 +93,7 @@ define_container! {
typedefs: [Typedef],
structs: [StructDef],
subroutines: [Subroutine],
modports: [ModportDef],
package_imports: [PackageImport],

instantiations: [Instantiation],
Expand Down Expand Up @@ -131,6 +134,7 @@ define_container! {
typedef_srcs: [Typedef | TypedefSrc],
struct_srcs: [StructDef | StructSrc],
subroutine_srcs: [Subroutine | SubroutineSrc],
modport_srcs: [ModportDef | ModportSrc],

instantiation_srcs: [Instantiation | InstantiationSrc],
inst_param_assign_srcs: [ParamAssign | ParamAssignSrc],
Expand Down Expand Up @@ -279,6 +283,7 @@ impl ModuleSourceMap {
ModuleItem::PortDeclId(idx) => self.get(*idx)?.ptr(),
ModuleItem::TypedefId(idx) => self.get(*idx)?.ptr(),
ModuleItem::SubroutineId(idx) => self.get(*idx)?.node,
ModuleItem::ModportId(idx) => self.get(*idx)?.node,
})
}
}
Expand All @@ -298,6 +303,7 @@ define_enum_deriving_from! {
PortDeclId(PortDeclId),
TypedefId(TypedefId),
SubroutineId(LocalSubroutineId),
ModportId(ModportId),
}
}

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

// Modport
ModportDeclaration(_)
| ModportClockingPort(_)
ModportDeclaration(modport) => {
for modport_id in self.lower_modport_declaration(modport) {
self.module_source_map.items.push(modport_id.into());
}
self.region_tree.handle_node(member.syntax());
continue;
}
ModportClockingPort(_)
| ModportSimplePortList(_)
| ModportSubroutinePortList(_) => continue,

Expand Down
163 changes: 163 additions & 0 deletions crates/hir/src/hir_def/module/modport.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use la_arena::Idx;
use smallvec::SmallVec;
use syntax::{
SyntaxKind, SyntaxToken, TokenKind,
ast::{self, AstNode},
ptr::{SyntaxNodePtr, SyntaxTokenPtr},
slang_ext::AstNodeExt,
};
use utils::text_edit::TextRange;

use super::{LowerModuleCtx, port::PortDirection};
use crate::{
hir_def::{Ident, alloc_idx_and_src, lower_ident_opt},
source_map::{FromSourceAst, IsNamedSrc, IsSrc, SourceAst, root_token_in},
};

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ModportDef {
pub name: Option<Ident>,
pub ports: SmallVec<[ModportPort; 4]>,
}

pub type ModportId = Idx<ModportDef>;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ModportPort {
pub name: Ident,
pub dir: PortDirection,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct ModportSrc {
pub node: SyntaxNodePtr,
pub name: Option<SyntaxTokenPtr>,
}

impl IsSrc for ModportSrc {
#[inline]
fn kind(&self) -> SyntaxKind {
self.node.kind()
}

#[inline]
fn range(&self) -> TextRange {
self.node.range()
}
}

impl IsNamedSrc for ModportSrc {
#[inline]
fn name_kind(&self) -> Option<TokenKind> {
self.name.map(|name| name.kind())
}

#[inline]
fn name_range(&self) -> Option<TextRange> {
self.name.map(|name| name.range())
}
}

impl<'a> FromSourceAst<'a, ast::ModportItem<'a>> for ModportSrc {
fn from_source_ast(item: SourceAst<ast::ModportItem<'a>>) -> Self {
let item = item.into_inner();
let syntax = item.syntax();
let name = item
.name()
.and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token));
Self { node: AstNodeExt::to_ptr(&item), name }
}
}

pub(crate) trait LowerModport {
fn lower_modport_declaration(
&mut self,
modport: ast::ModportDeclaration<'_>,
) -> SmallVec<[ModportId; 1]>;
}

impl LowerModport for LowerModuleCtx<'_> {
fn lower_modport_declaration(
&mut self,
modport: ast::ModportDeclaration<'_>,
) -> SmallVec<[ModportId; 1]> {
let mut lowered = SmallVec::new();
for item in modport.items().children() {
let name = lower_ident_opt(item.name());
let ports = lower_modport_ports(item);
let modport_id = alloc_idx_and_src! {
self.file_id;
ModportDef { name, ports } => self.module.modports,
item => self.module_source_map.modport_srcs,
};
lowered.push(modport_id);
}
lowered
}
}

fn lower_modport_ports(item: ast::ModportItem<'_>) -> SmallVec<[ModportPort; 4]> {
let mut ports = SmallVec::new();

// slang models a modport item as named entries whose `ports()` members are
// simple or subroutine port lists; each list then owns its inner ports.
for member in item.ports().ports().children() {
match member {
ast::Member::ModportSimplePortList(port_list) => {
let dir = lower_modport_dir(port_list.direction()).unwrap_or_default();
for port in port_list.ports().children() {
if let Some(name) = lower_modport_port_name(port) {
ports.push(ModportPort { name, dir });
}
}
}
ast::Member::ModportSubroutinePortList(port_list) => {
let dir = lower_modport_subroutine_dir(port_list.import_export());
for port in port_list.ports().children() {
if let Some(name) = lower_modport_port_name(port) {
ports.push(ModportPort { name, dir });
}
}
}
_ => {}
}
}

ports
}

fn lower_modport_port_name(port: ast::ModportPort<'_>) -> Option<Ident> {
match port {
ast::ModportPort::ModportNamedPort(port) => lower_ident_opt(port.name()),
ast::ModportPort::ModportExplicitPort(port) => lower_ident_opt(port.name()),
ast::ModportPort::ModportSubroutinePort(port) => {
lower_ident_opt(rightmost_name_token(port.prototype().name()))
}
}
}

fn rightmost_name_token(name: ast::Name<'_>) -> Option<SyntaxToken<'_>> {
match name {
ast::Name::IdentifierName(name) => name.identifier(),
ast::Name::IdentifierSelectName(name) => name.identifier(),
ast::Name::ScopedName(name) => rightmost_name_token(name.right()),
_ => None,
}
}

fn lower_modport_dir(tok: Option<SyntaxToken<'_>>) -> Option<PortDirection> {
tok.and_then(|tok| match tok.kind() {
TokenKind::INPUT_KEYWORD => Some(PortDirection::Input),
TokenKind::OUTPUT_KEYWORD => Some(PortDirection::Output),
TokenKind::IN_OUT_KEYWORD => Some(PortDirection::Inout),
TokenKind::REF_KEYWORD => Some(PortDirection::Ref),
_ => None,
})
}

fn lower_modport_subroutine_dir(tok: Option<SyntaxToken<'_>>) -> PortDirection {
match tok.map(|tok| tok.kind()) {
Some(TokenKind::EXPORT_KEYWORD) => PortDirection::Output,
_ => PortDirection::Input,
}
}
4 changes: 4 additions & 0 deletions crates/hir/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ impl NameScope {
scope.insert_value_opt(&subroutine.name, def_id(db, subroutine_id));
}

for (modport_id, modport) in module.modports.iter() {
scope.insert_value_opt(&modport.name, def_id(db, InModule::new(module_id, modport_id)));
}

insert_decls_and_typedefs(
&mut scope,
db,
Expand Down
46 changes: 44 additions & 2 deletions crates/hir/src/semantics/pathres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ fn resolve_top_level_module_root(
// segment value fallback: `top` alone remains a type-space module name.
let type_res = resolve_name(db, cont_id, ident, NameContext::Type)?;
let module_defs =
type_res.def_ids().iter().copied().filter(|def_id| def_id.kind(db) == DefKind::Module);
type_res.def_ids().iter().copied().filter(|def_id| def_id.kind(db).is_instantiable_def());
PathResolution::from_def_ids(module_defs)
}

Expand Down Expand Up @@ -147,7 +147,7 @@ fn resolve_child_name(

pub fn descend_scope(db: &dyn HirDb, def_id: DefId) -> Option<ScopeId> {
match def_id.kind(db) {
DefKind::Module => def_id.as_module(db).map(Into::into),
kind if kind.is_instantiable_def() => def_id.as_module(db).map(Into::into),
DefKind::Instance => {
let instance = def_id.as_instance(db)?;
instance_target_module_id(db, instance.module_id, instance.value).map(Into::into)
Expand Down Expand Up @@ -460,4 +460,46 @@ endmodule
DefKind::Net
);
}

#[test]
fn resolve_path_descends_interface_instances_to_modports() {
let db = db_with_root_text(
r#"
interface bus_if;
wire clk;
modport host(input clk);
endinterface

module top;
bus_if u_if();
endmodule
"#,
);

let top = db
.unit_scope()
.module_ids(&db, &ident("top"))
.unique()
.expect("top module should resolve uniquely");

let res = resolve_path(&db, top.into(), &path(&["u_if", "host"]), NameContext::Value)
.expect("interface instance modport should resolve");

let def = res.primary_def_id().expect("modport should produce a definition");
assert_eq!(def.name(&db).as_deref(), Some("host"));
assert_eq!(def.kind(&db), DefKind::Modport);
assert_eq!(
resolved_kind(&db, top.into(), &["u_if", "clk"], NameContext::Value),
DefKind::Net
);
assert_eq!(
resolved_kind(
&db,
ScopeId::File(HirFileId::File(TOP)),
&["top", "u_if", "host"],
NameContext::Value,
),
DefKind::Modport
);
}
}
Loading
Loading