use crate::crate_prelude::*;
use crate::{ast_map::AstNode, common::arenas::Alloc};
use std::{
borrow::Cow,
collections::HashMap,
hash::{Hash, Hasher},
};
#[derive(Debug, PartialEq, Eq)]
pub struct PortList<'a> {
pub int: Vec<IntPort<'a>>,
pub ext_pos: Vec<ExtPort<'a>>,
pub ext_named: Option<HashMap<Name, usize>>,
pub(crate) tail_rib: NodeId,
}
#[derive(Debug, PartialEq, Eq)]
pub struct IntPort<'a> {
pub id: NodeId,
pub ast: &'a dyn ast::AnyNode<'a>,
pub node: &'a dyn PortedNode<'a>,
pub span: Span,
pub name: Spanned<Name>,
pub dir: ast::PortDir,
pub kind: ast::VarKind,
pub data: Option<IntPortData<'a>>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct IntPortData<'a> {
pub ty: &'a ast::Type<'a>,
pub unpacked_dims: &'a [ast::TypeDim<'a>],
pub matching: Option<(NodeId, ())>,
pub default: Option<NodeId>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ExtPort<'a> {
pub id: NodeId,
pub node: &'a dyn PortedNode<'a>,
pub span: Span,
pub name: Option<Spanned<Name>>,
pub exprs: Vec<ExtPortExpr>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ExtPortExpr {
pub port: usize,
pub selects: Vec<ExtPortSelect>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ExtPortSelect {
Error,
Index(hir::IndexMode),
}
impl HasSpan for IntPort<'_> {
fn span(&self) -> Span {
self.span
}
fn human_span(&self) -> Span {
self.name.span
}
}
impl HasDesc for IntPort<'_> {
fn desc(&self) -> &'static str {
"port"
}
fn desc_full(&self) -> String {
format!("port `{}`", self.name.value)
}
}
impl HasSpan for ExtPort<'_> {
fn span(&self) -> Span {
self.span
}
fn human_span(&self) -> Span {
self.name.map(|n| n.span).unwrap_or(self.span)
}
}
impl HasDesc for ExtPort<'_> {
fn desc(&self) -> &'static str {
"port"
}
fn desc_full(&self) -> String {
self.name
.map(|n| format!("port `{}`", n))
.unwrap_or_else(|| "port".to_string())
}
}
pub trait PortedNode<'a>: ast::AnyNode<'a> + resolver::ScopedNode<'a> {
fn ports(&self) -> &[ast::Port<'a>];
fn items(&self) -> &[ast::Item<'a>];
}
impl<'a> PortedNode<'a> for ast::Module<'a> {
fn ports(&self) -> &[ast::Port<'a>] {
&self.ports
}
fn items(&self) -> &[ast::Item<'a>] {
&self.items
}
}
impl<'a> PortedNode<'a> for ast::Interface<'a> {
fn ports(&self) -> &[ast::Port<'a>] {
&self.ports
}
fn items(&self) -> &[ast::Item<'a>] {
&self.items
}
}
impl<'a> Eq for &'a dyn PortedNode<'a> {}
impl<'a> PartialEq for &'a dyn PortedNode<'a> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.as_ptr(), other.as_ptr())
}
}
impl<'a> Hash for &'a dyn PortedNode<'a> {
fn hash<H: Hasher>(&self, h: &mut H) {
std::ptr::hash(self.as_ptr(), h)
}
}
pub trait AsPortedNode<'a> {
fn get_ported(&self) -> Option<&'a dyn PortedNode<'a>>;
fn is_ported(&self) -> bool {
self.get_ported().is_some()
}
}
impl<'a> AsPortedNode<'a> for ast::AllNode<'a> {
fn get_ported(&self) -> Option<&'a dyn PortedNode<'a>> {
match *self {
ast::AllNode::Module(x) => Some(x),
ast::AllNode::Interface(x) => Some(x),
_ => None,
}
}
}
#[moore_derive::query]
pub(crate) fn canonicalize_ports<'a>(
cx: &impl Context<'a>,
node: &'a dyn PortedNode<'a>,
) -> &'a PortList<'a> {
debug!("Building port list of {:?}", node);
let mut next_rib = node.id();
let ast_ports = node.ports();
let ast_items = node.items();
let (nonansi, first_span) = {
let first = match ast_ports.first() {
Some(p) => p,
None => {
return cx.gcx().arena.alloc_port_list(PortList {
int: Default::default(),
ext_pos: Default::default(),
ext_named: Default::default(),
tail_rib: next_rib,
})
}
};
let nonansi = match first.data {
ast::PortData::Explicit { ref dir, .. } if dir.is_none() => true,
ast::PortData::Named {
ref dir,
ref kind,
ref ty,
ref expr,
..
} if dir.is_none()
&& kind.is_none()
&& expr.is_none()
&& ty.kind.data == ast::ImplicitType
&& ty.sign == ast::TypeSign::None
&& ty.dims.is_empty() =>
{
true
}
ast::PortData::Implicit(_) => true,
_ => false,
};
(nonansi, first.span())
};
debug!(
"Module uses {} style",
if nonansi { "non-ANSI" } else { "ANSI" }
);
let partial_ports = match nonansi {
true => lower_node_ports_nonansi(cx, ast_ports, ast_items, first_span, node),
false => lower_node_ports_ansi(cx, ast_ports, ast_items, first_span, node),
};
trace!("Lowered ports: {:#?}", partial_ports);
let mut ports = vec![];
let default_net_type = ast::NetType::Wire;
for port in partial_ports.int {
let port_id = port.ast.id();
let kind = port.kind.unwrap_or_else(|| match port.dir {
ast::PortDir::Input | ast::PortDir::Inout => ast::VarKind::Net {
ty: default_net_type,
kind: ast::NetKind::None,
},
ast::PortDir::Output => {
if port.ty.data == ast::ImplicitType {
ast::VarKind::Net {
ty: default_net_type,
kind: ast::NetKind::None,
}
} else {
ast::VarKind::Var
}
}
ast::PortDir::Ref => ast::VarKind::Var,
});
match (port.dir, kind) {
(ast::PortDir::Inout, ast::VarKind::Var) => cx.emit(
DiagBuilder2::error(format!(
"inout port `{}` must be a net; but is declared as variable",
port.name
))
.span(port.name.span),
),
(ast::PortDir::Ref, ast::VarKind::Net { .. }) => cx.emit(
DiagBuilder2::error(format!(
"ref port `{}` must be a variable; but is declared as net",
port.name
))
.span(port.name.span),
),
_ => (),
}
cx.set_ast(port_id, AstNode::Port(port.span));
cx.set_parent(port_id, next_rib);
let data = if port.inferred {
None
} else {
let ty = if port.ty.data == ast::ImplicitType {
Cow::Owned(ast::TypeKind::new(port.ty.span(), ast::LogicType))
} else {
port.ty
};
let ty = cx.arena().alloc(ast::Type::new(
port.ty_span.unwrap_or(port.span),
ast::TypeData {
kind: ty.into_owned(),
sign: port.sign,
dims: port.packed_dims.to_vec(),
},
));
ty.link_attach(port.ast, port.ast.order());
cx.register_ast(ty);
cx.map_ast_with_parent(AstNode::Type(ty), next_rib);
let default = port
.default
.map(|expr| cx.map_ast_with_parent(AstNode::Expr(expr), port_id));
next_rib = port_id;
Some(IntPortData {
ty,
unpacked_dims: port.unpacked_dims,
matching: None,
default,
})
};
ports.push(IntPort {
id: port_id,
ast: port.ast,
node,
span: port.span,
name: port.name,
dir: port.dir,
kind,
data,
});
}
let list = PortList {
int: ports,
ext_pos: partial_ports.ext_pos,
ext_named: partial_ports.ext_named,
tail_rib: next_rib,
};
trace!("Port list of {:?} is: {:#?}", node, list);
cx.gcx().arena.alloc_port_list(list)
}
fn lower_node_ports_ansi<'a>(
cx: &impl Context<'a>,
ast_ports: &'a [ast::Port<'a>],
ast_items: &'a [ast::Item<'a>],
first_span: Span,
node: &'a dyn PortedNode<'a>,
) -> PartialPortList<'a> {
for item in ast_items {
let ast = match &item.data {
ast::ItemData::PortDecl(pd) => pd,
_ => continue,
};
cx.emit(
DiagBuilder2::error(format!("port declaration in body of ANSI-style {:#}", node))
.span(ast.span)
.add_note(format!(
"A {:#} with an ANSI-style port list cannot have port declarations in the \
body.",
node
))
.add_note("Consider using non-ANSI style.")
.add_note("First port uses ANSI style:")
.span(first_span),
);
}
let mut carry_dir = ast::PortDir::Inout;
let mut carry_kind: Option<ast::VarKind> = None;
let mut carry_ty = Cow::Owned(ast::TypeKind::new(
first_span.begin().into(),
ast::LogicType,
));
let mut carry_sign = ast::TypeSign::None;
let mut carry_packed_dims: &[ast::TypeDim] = &[];
let mut int_ports: Vec<PartialPort> = vec![];
let mut ext_pos: Vec<ExtPort> = vec![];
let mut ext_named: HashMap<Name, usize> = HashMap::new();
let mut explicit_named: HashMap<(ast::PortDir, Name), usize> = HashMap::new();
for port in ast_ports {
let port = match &port.data {
ast::PortData::Named {
dir,
kind,
ty,
name,
dims: unpacked_dims,
expr,
} => {
let dir = match *dir {
Some(dir) => {
carry_kind = None;
carry_ty = Cow::Owned(ast::TypeKind::new(port.span(), ast::LogicType));
carry_sign = ast::TypeSign::None;
carry_packed_dims = &[];
dir
}
None => carry_dir,
};
let kind = kind.or(carry_kind);
let (ty, sign, packed_dims, ty_span) = if ty.kind.data == ast::ImplicitType
&& ty.sign == ast::TypeSign::None
&& ty.dims.is_empty()
{
(carry_ty, carry_sign, carry_packed_dims, None)
} else {
(
Cow::Borrowed(&ty.kind),
ty.sign,
ty.dims.as_slice(),
Some(ty.span),
)
};
carry_dir = dir;
carry_kind = kind;
carry_ty = ty.clone();
carry_sign = sign;
carry_packed_dims = packed_dims;
let data = PartialPort {
name: *name,
span: port.span,
ast: port,
kind,
dir,
sign,
ty,
ty_span,
packed_dims,
unpacked_dims,
default: expr.as_ref(),
inferred: false,
var_decl: None,
net_decl: None,
match_ty: None,
};
let ext_port = ExtPort {
id: cx.alloc_id(port.span()),
node,
span: port.span(),
name: Some(data.name),
exprs: vec![ExtPortExpr {
port: int_ports.len(),
selects: vec![],
}],
};
int_ports.push(data);
ext_port
}
ast::PortData::Explicit { dir, name, expr } => {
let dir = dir.unwrap_or(carry_dir);
carry_dir = dir;
carry_kind = None;
carry_ty = Cow::Owned(ast::TypeKind::new(
port.span().begin().into(),
ast::LogicType,
));
carry_sign = ast::TypeSign::None;
carry_packed_dims = &[];
let pe = match expr {
Some(expr) => lower_port_expr(cx, expr, node.id()),
None => vec![],
};
let pe = pe
.into_iter()
.map(|pr| {
let index =
*explicit_named
.entry((dir, pr.name.value))
.or_insert_with(|| {
let index = int_ports.len();
trace!("Adding inferred port {}", pr.name);
int_ports.push(PartialPort {
name: pr.name,
span: port.span,
ast: port,
kind: None,
dir,
sign: ast::TypeSign::None,
ty: Cow::Owned(ast::TypeKind::new(
port.span().begin().into(),
ast::ImplicitType,
)), ty_span: None,
packed_dims: &[], unpacked_dims: &[],
default: None,
inferred: true,
var_decl: None,
net_decl: None,
match_ty: None,
});
index
});
ExtPortExpr {
port: index,
selects: pr.selects,
}
})
.collect();
ExtPort {
id: cx.alloc_id(port.span()),
node,
span: port.span(),
name: Some(*name),
exprs: pe,
}
}
_ => {
cx.emit(
DiagBuilder2::error("non-ANSI port in ANSI port list")
.span(port.span())
.add_note("First port uses ANSI style:")
.span(first_span),
);
error!("Invalid port: {:?}", port);
continue;
}
};
if let Some(prev) = ext_named.insert(port.name.unwrap().value, ext_pos.len()) {
cx.emit(
DiagBuilder2::error(format!(
"port `{}` declared multiple times",
port.name.unwrap().value
))
.span(port.name.unwrap().span)
.add_note("Previous declaration was here:")
.span(ext_pos[prev].name.unwrap().span),
);
}
ext_pos.push(port);
}
PartialPortList {
int: int_ports,
ext_pos,
ext_named: Some(ext_named),
}
}
fn lower_node_ports_nonansi<'a>(
cx: &impl Context<'a>,
ast_ports: &'a [ast::Port<'a>],
ast_items: &'a [ast::Item<'a>],
first_span: Span,
node: &'a dyn PortedNode<'a>,
) -> PartialPortList<'a> {
let mut decl_order: Vec<PartialPort> = vec![];
let mut decl_names = HashMap::new();
for item in ast_items {
let ast = match &item.data {
ast::ItemData::PortDecl(pd) => pd,
_ => continue,
};
for name in &ast.names {
let data = PartialPort {
span: ast.span,
name: Spanned::new(name.name, name.name_span),
ast: name,
kind: ast.kind,
dir: ast.dir,
sign: ast.ty.sign,
ty: Cow::Borrowed(&ast.ty.kind),
ty_span: Some(ast.ty.span),
packed_dims: &ast.ty.dims,
unpacked_dims: &name.dims,
default: name.init.as_ref(),
inferred: false,
match_ty: None,
var_decl: None,
net_decl: None,
};
let index = decl_order.len();
if let Some(prev) = decl_names.insert(data.name.value, index) {
cx.emit(
DiagBuilder2::error(format!(
"port `{}` declared multiple times",
data.name.value
))
.span(data.name.span)
.add_note("Previous declaration was here:")
.span(decl_order[prev].name.span),
);
}
decl_order.push(data);
}
}
for item in ast_items {
match &item.data {
ast::ItemData::VarDecl(vd) => {
for name in &vd.names {
let index = match decl_names.get(&name.name) {
Some(&e) => e,
None => continue,
};
let entry = &mut decl_order[index];
if let Some(prev) = std::mem::replace(&mut entry.var_decl, Some((vd, name))) {
cx.emit(
DiagBuilder2::error(format!(
"port variable `{}` declared multiple times",
name.name
))
.span(name.name_span)
.add_note("previous declaration was here:")
.span(prev.1.name_span),
);
}
}
}
ast::ItemData::NetDecl(nd) => {
for name in &nd.names {
let index = match decl_names.get(&name.name) {
Some(&e) => e,
None => continue,
};
let entry = &mut decl_order[index];
if let Some(prev) = std::mem::replace(&mut entry.net_decl, Some((nd, name))) {
cx.emit(
DiagBuilder2::error(format!(
"port net `{}` declared multiple times",
name.name
))
.span(name.name_span)
.add_note("previous declaration was here:")
.span(prev.1.name_span),
);
}
}
}
_ => continue,
}
}
for port in &mut decl_order {
if port.kind.is_some() || port.ty.data != ast::ImplicitType {
for span in port
.var_decl
.iter()
.map(|x| x.1.span)
.chain(port.net_decl.iter().map(|x| x.1.span))
{
cx.emit(
DiagBuilder2::error(format!(
"port `{}` is complete; additional declaration forbidden",
port.name
))
.span(span)
.add_note(
"Port already has a net/variable type. Cannot declare an additional \
net/variable with the same name.",
)
.add_note("Port declaration was here:")
.span(port.span),
);
}
port.var_decl = None;
port.net_decl = None;
}
let (add_span, add_ty, add_sign, add_packed, add_unpacked) =
match (port.var_decl, port.net_decl) {
(Some(vd), None) => {
if port.kind.is_some() && port.kind != Some(ast::VarKind::Var) {
cx.emit(
DiagBuilder2::error(format!(
"net port `{}` redeclared as variable",
port.name
))
.span(vd.1.span)
.add_note("Port declaration was here:")
.span(port.span),
);
}
port.kind = Some(ast::VarKind::Var);
(
vd.1.name_span,
&vd.0.ty.kind,
vd.0.ty.sign,
&vd.0.ty.dims,
&vd.1.dims,
)
}
(None, Some(nd)) => {
if port.kind.is_some() && port.kind == Some(ast::VarKind::Var) {
cx.emit(
DiagBuilder2::error(format!(
"variable port `{}` redeclared as net",
port.name
))
.span(nd.1.span)
.add_note("Port declaration was here:")
.span(port.span),
);
}
port.kind = Some(ast::VarKind::Net {
ty: nd.0.net_type,
kind: ast::NetKind::None,
});
(
nd.1.name_span,
&nd.0.ty.kind,
nd.0.ty.sign,
&nd.0.ty.dims,
&nd.1.dims,
)
}
(Some(vd), Some(nd)) => {
cx.emit(
DiagBuilder2::error(format!(
"port `{}` doubly declared as variable and net",
port.name
))
.span(vd.1.span)
.span(nd.1.span)
.add_note("Port declaration was here:")
.span(port.span),
);
continue;
}
(None, None) => continue,
};
match (port.sign, add_sign) {
(a, b) if a == b => port.sign = a,
(a, ast::TypeSign::None) => port.sign = a,
(ast::TypeSign::None, b) => port.sign = b,
(_, _) => {
cx.emit(
DiagBuilder2::error(format!("port `{}` has contradicting signs", port.name))
.span(port.span)
.span(add_span),
);
}
}
port.match_ty = Some((
match (&port.ty.data, &add_ty.data) {
(_, ast::ImplicitType) => None,
(ast::ImplicitType, _) => {
port.ty = Cow::Borrowed(add_ty);
None
}
(_, _) => Some(add_ty),
},
add_packed,
add_unpacked,
));
}
let mut ext_pos: Vec<ExtPort> = vec![];
let mut ext_named: HashMap<Name, usize> = HashMap::new();
let mut any_unnamed = false;
for port in ast_ports {
let (name, exprs) = match port.data {
ast::PortData::Explicit {
dir: None,
name,
ref expr,
} => {
if let Some(expr) = expr {
let pe = lower_port_expr(cx, expr, node.id());
(Some(name), pe)
} else {
(Some(name), vec![])
}
}
ast::PortData::Named {
dir: None,
kind: None,
ty:
ast::Type {
data:
ast::TypeData {
kind:
ast::TypeKind {
data: ast::ImplicitType,
..
},
sign: ast::TypeSign::None,
dims: ref packed_dims,
..
},
..
},
name,
ref dims,
expr: None,
} if packed_dims.is_empty() => {
let selects = dims
.iter()
.map(|dim| match dim {
ast::TypeDim::Expr(index) => ExtPortSelect::Index(hir::IndexMode::One(
cx.map_ast_with_parent(AstNode::Expr(index), node.id()),
)),
ast::TypeDim::Range(lhs, rhs) => {
ExtPortSelect::Index(hir::IndexMode::Many(
ast::RangeMode::Absolute,
cx.map_ast_with_parent(AstNode::Expr(lhs), node.id()),
cx.map_ast_with_parent(AstNode::Expr(rhs), node.id()),
))
}
_ => {
cx.emit(
DiagBuilder2::error(format!(
"invalid port range {}; on port `{}`",
dim.desc_full(),
name
))
.span(port.span()),
);
ExtPortSelect::Error
}
})
.collect();
let pe = vec![PartialPortExpr { name, selects }];
if dims.is_empty() {
(Some(name), pe)
} else {
(None, pe)
}
}
ast::PortData::Implicit(ref expr) => (None, lower_port_expr(cx, expr, node.id())),
_ => {
cx.emit(
DiagBuilder2::error("ANSI port in non-ANSI port list")
.span(port.span())
.add_note("First port uses non-ANSI style:")
.span(first_span),
);
error!("Invalid port: {:?}", port);
continue;
}
};
let exprs = exprs
.into_iter()
.flat_map(|expr| match decl_names.get(&expr.name.value) {
Some(&index) => Some(ExtPortExpr {
port: index,
selects: expr.selects,
}),
None => {
cx.emit(
DiagBuilder2::error(format!(
"port `{}` not declared in {:#} body",
expr.name, node
))
.span(expr.name.span)
.add_note(format!("Declare the port inside the {:#}, e.g.:", node))
.add_note(format!("input {};", expr.name)),
);
None
}
})
.collect();
let port = ExtPort {
id: cx.alloc_id(port.span()),
node,
span: port.span(),
name,
exprs,
};
if let Some(name) = port.name {
if ext_named.insert(name.value, ext_pos.len()).is_some() {
any_unnamed = true;
}
} else {
any_unnamed = true;
}
ext_pos.push(port);
}
PartialPortList {
int: decl_order,
ext_pos,
ext_named: if any_unnamed { None } else { Some(ext_named) },
}
}
fn lower_port_expr<'gcx>(
cx: &impl Context<'gcx>,
expr: &'gcx ast::Expr<'gcx>,
parent: NodeId,
) -> Vec<PartialPortExpr> {
match &expr.data {
ast::ConcatExpr {
repeat: None,
exprs,
} => exprs
.iter()
.flat_map(|expr| lower_port_ref(cx, expr, parent))
.collect(),
_ => lower_port_ref(cx, expr, parent).into_iter().collect(),
}
}
fn lower_port_ref<'gcx>(
cx: &impl Context<'gcx>,
expr: &'gcx ast::Expr<'gcx>,
parent: NodeId,
) -> Option<PartialPortExpr> {
match &expr.data {
ast::IdentExpr(ident) => Some(PartialPortExpr {
name: *ident,
selects: vec![],
}),
ast::IndexExpr { indexee, index } => {
let mut pe = lower_port_ref(cx, indexee, parent)?;
let mode = crate::hir::lowering::lower_index_mode(cx, index, parent);
pe.selects.push(ExtPortSelect::Index(mode));
Some(pe)
}
_ => {
cx.emit(
DiagBuilder2::error(format!(
"invalid port expression: `{}`",
expr.span.extract()
))
.span(expr.span),
);
error!("{:?}", expr);
None
}
}
}
#[derive(Debug)]
struct PartialPort<'a> {
span: Span,
name: Spanned<Name>,
ast: &'a dyn ast::AnyNode<'a>,
dir: ast::PortDir,
kind: Option<ast::VarKind>,
sign: ast::TypeSign,
ty: Cow<'a, ast::TypeKind<'a>>,
ty_span: Option<Span>,
packed_dims: &'a [ast::TypeDim<'a>],
unpacked_dims: &'a [ast::TypeDim<'a>],
default: Option<&'a ast::Expr<'a>>,
inferred: bool,
var_decl: Option<(&'a ast::VarDecl<'a>, &'a ast::VarDeclName<'a>)>,
net_decl: Option<(&'a ast::NetDecl<'a>, &'a ast::VarDeclName<'a>)>,
match_ty: Option<(
Option<&'a ast::TypeKind<'a>>,
&'a [ast::TypeDim<'a>],
&'a [ast::TypeDim<'a>],
)>,
}
#[derive(Debug)]
struct PartialPortExpr {
name: Spanned<Name>,
selects: Vec<ExtPortSelect>,
}
#[derive(Debug)]
struct PartialPortList<'a> {
int: Vec<PartialPort<'a>>,
ext_pos: Vec<ExtPort<'a>>,
ext_named: Option<HashMap<Name, usize>>,
}