use crate::analyzer_error::{
AnalyzerError, ComponentInterfaceMismatchKind, MismatchTypeKind, UnevaluableValueKind,
};
use crate::attribute::{AllowItem, Attribute};
use crate::attribute_table;
use crate::conv::checker::alias::{AliasType, check_alias_target};
use crate::conv::checker::bind::check_bind_target;
use crate::conv::checker::clock_domain::check_clock_domain;
use crate::conv::checker::generic::{
check_generic_bound, check_generic_bound_item, check_generic_expression,
check_generic_refereence,
};
use crate::conv::checker::import::check_import;
use crate::conv::checker::inst::check_inst;
use crate::conv::checker::modport::{check_modport, check_modport_default, check_modport_in_port};
use crate::conv::checker::port::{check_direction, check_port_default_value, check_port_direction};
use crate::conv::context::{FunctionOutputBinding, RuntimeFunctionEffect};
use crate::conv::utils::{
TypePosition, assign_rhs_context_type, check_assign_before_definition,
check_assign_clock_domain, eval_array_range_assign, eval_assign_statement, eval_clock,
eval_const_assign, eval_expr, eval_factor_symbol, eval_factor_symbol_external,
eval_generate_for_range, eval_reset, eval_size, eval_type, eval_variable, expand_connect,
expand_connect_const, expand_input_connect, get_component, get_overridden_params,
get_port_connects, get_return_str, insert_port_connect, try_infer_decl_type,
try_infer_var_assign, var_path_to_assign_destination,
};
use crate::conv::{Affiliation, Context, Conv};
use crate::definition_table::{self, Definition};
use crate::ir::{
self, Comptime, FuncArg, FuncPath, InstanceKind, IrResult, Shape, Signature, TypeKind,
ValueVariant, VarId, VarIndex, VarKind, VarPath, VarPathSelect, VarSelect, Variable,
};
use crate::namespace::{DefineContext, Namespace};
use crate::symbol::{
ClockDomain, Direction, GenericBoundKind, ProtoBound, Symbol, SymbolKind, TbComponentKind,
};
use crate::symbol_path::{GenericSymbol, GenericSymbolPath, SymbolPath, SymbolPathNamespace};
use crate::symbol_table;
use crate::value::Value;
use crate::{HashMap, HashSet, ir_error};
use std::sync::Arc;
use veryl_metadata::component_manifest::{ConnectionFacts, ConnectionTarget, ConnectionViolation};
use veryl_parser::resource_table::{self, StrId};
use veryl_parser::token_range::TokenRange;
use veryl_parser::veryl_grammar_trait::*;
use veryl_parser::veryl_token::Token;
impl Conv<&GenerateItem> for ir::DeclarationBlock {
fn conv(context: &mut Context, value: &GenerateItem) -> IrResult<Self> {
match value {
GenerateItem::LetDeclaration(x) => Ok(ir::DeclarationBlock::new(Conv::conv(
context,
x.let_declaration.as_ref(),
)?)),
GenerateItem::VarDeclaration(x) => Ok(ir::DeclarationBlock::new(Conv::conv(
context,
x.var_declaration.as_ref(),
)?)),
GenerateItem::AlwaysFfDeclaration(x) => {
let token: TokenRange = x.always_ff_declaration.as_ref().into();
match Conv::conv(context, x.always_ff_declaration.as_ref()) {
Ok(decl) => Ok(ir::DeclarationBlock::new(decl)),
Err(_) if context.in_generic => Ok(ir::DeclarationBlock::default()),
Err(_) => Ok(ir::DeclarationBlock::new(ir::Declaration::Unsupported(
token,
))),
}
}
GenerateItem::AlwaysCombDeclaration(x) => {
let token: TokenRange = x.always_comb_declaration.as_ref().into();
match Conv::conv(context, x.always_comb_declaration.as_ref()) {
Ok(decl) => Ok(ir::DeclarationBlock::new(decl)),
Err(_) if context.in_generic => Ok(ir::DeclarationBlock::default()),
Err(_) => Ok(ir::DeclarationBlock::new(ir::Declaration::Unsupported(
token,
))),
}
}
GenerateItem::GenerateIfDeclaration(x) => {
Conv::conv(context, x.generate_if_declaration.as_ref())
}
GenerateItem::GenerateForDeclaration(x) => {
Conv::conv(context, x.generate_for_declaration.as_ref())
}
GenerateItem::GenerateBlockDeclaration(x) => Conv::conv(
context,
x.generate_block_declaration.generate_named_block.as_ref(),
),
GenerateItem::ConstDeclaration(x) => Ok(ir::DeclarationBlock::new(Conv::conv(
context,
x.const_declaration.as_ref(),
)?)),
GenerateItem::GenDeclaration(x) => Ok(ir::DeclarationBlock::new(Conv::conv(
context,
x.gen_declaration.as_ref(),
)?)),
GenerateItem::AssignDeclaration(x) => {
let token: TokenRange = x.assign_declaration.as_ref().into();
match Conv::conv(context, x.assign_declaration.as_ref()) {
Ok(decl) => Ok(ir::DeclarationBlock::new(decl)),
Err(_) if context.in_generic => Ok(ir::DeclarationBlock::default()),
Err(_) => Ok(ir::DeclarationBlock::new(ir::Declaration::Unsupported(
token,
))),
}
}
GenerateItem::FunctionDeclaration(x) => {
let in_generic = context.in_generic;
if x.function_declaration.function_declaration_opt.is_some() {
context.in_generic = true;
}
let ret: IrResult<()> = Conv::conv(context, x.function_declaration.as_ref());
if x.function_declaration.function_declaration_opt.is_some() {
context.in_generic = in_generic;
}
ret?;
Ok(ir::DeclarationBlock::default())
}
GenerateItem::StructUnionDeclaration(x) => {
let in_generic = context.in_generic;
if x.struct_union_declaration
.struct_union_declaration_opt
.is_some()
{
context.in_generic = true;
}
let ret: IrResult<()> = Conv::conv(context, x.struct_union_declaration.as_ref());
if x.struct_union_declaration
.struct_union_declaration_opt
.is_some()
{
context.in_generic = in_generic;
}
ret?;
Ok(ir::DeclarationBlock::default())
}
GenerateItem::EnumDeclaration(x) => {
let _: () = Conv::conv(context, x.enum_declaration.as_ref())?;
Ok(ir::DeclarationBlock::default())
}
GenerateItem::InitialDeclaration(x) => {
let token: TokenRange = x.initial_declaration.as_ref().into();
match Conv::conv(context, x.initial_declaration.as_ref()) {
Ok(decl) => Ok(ir::DeclarationBlock::new(decl)),
Err(_) if context.in_generic => Ok(ir::DeclarationBlock::default()),
Err(_) => Ok(ir::DeclarationBlock::new(ir::Declaration::Unsupported(
token,
))),
}
}
GenerateItem::FinalDeclaration(x) => {
let token: TokenRange = x.final_declaration.as_ref().into();
match Conv::conv(context, x.final_declaration.as_ref()) {
Ok(decl) => Ok(ir::DeclarationBlock::new(decl)),
Err(_) if context.in_generic => Ok(ir::DeclarationBlock::default()),
Err(_) => Ok(ir::DeclarationBlock::new(ir::Declaration::Unsupported(
token,
))),
}
}
GenerateItem::InstDeclaration(x) => {
let token: TokenRange = x.inst_declaration.as_ref().into();
match Conv::conv(context, x.inst_declaration.as_ref()) {
Ok(decl) => Ok(ir::DeclarationBlock::new(decl)),
Err(_) if context.in_generic => Ok(ir::DeclarationBlock::default()),
Err(_) => Ok(ir::DeclarationBlock::new(ir::Declaration::Unsupported(
token,
))),
}
}
GenerateItem::ConnectDeclaration(x) => {
Conv::conv(context, x.connect_declaration.as_ref())
}
GenerateItem::UnsafeBlock(x) => Conv::conv(context, x.unsafe_block.as_ref()),
GenerateItem::ImportDeclaration(x) => {
Conv::conv(context, x.import_declaration.as_ref())
}
GenerateItem::BindDeclaration(x) => {
let _: () = Conv::conv(context, x.bind_declaration.as_ref())?;
Ok(ir::DeclarationBlock::default())
}
GenerateItem::AliasDeclaration(x) => {
let _: () = Conv::conv(context, x.alias_declaration.as_ref())?;
Ok(ir::DeclarationBlock::default())
}
GenerateItem::TypeDefDeclaration(x) => {
let _: () = Conv::conv(context, x.type_def_declaration.as_ref())?;
Ok(ir::DeclarationBlock::default())
}
GenerateItem::EmbedDeclaration(x) => {
let _: () = Conv::conv(context, x.embed_declaration.as_ref())?;
Ok(ir::DeclarationBlock::default())
}
}
}
}
fn get_label(block: &GenerateOptionalNamedBlock, default: StrId) -> StrId {
if let Some(x) = &block.generate_optional_named_block_opt {
x.identifier.text()
} else {
default
}
}
impl Conv<&GenerateIfDeclaration> for ir::DeclarationBlock {
fn conv(context: &mut Context, value: &GenerateIfDeclaration) -> IrResult<Self> {
let label = value.generate_named_block.identifier.text();
let (comptime, _) = eval_expr(context, None, value.expression.as_ref(), false)?;
let cond = comptime.get_value()?;
if cond.to_usize().unwrap_or(0) != 0 {
context.push_hierarchy(label);
let block = context.block(|c| {
let block: ir::DeclarationBlock =
Conv::conv(c, value.generate_named_block.as_ref())?;
Ok(block)
});
context.pop_hierarchy();
block
} else {
for x in &value.generate_if_declaration_list {
let (comptime, _) = eval_expr(context, None, x.expression.as_ref(), false)?;
let cond = comptime.get_value()?;
if cond.to_usize().unwrap_or(0) != 0 {
let label = get_label(&x.generate_optional_named_block, label);
context.push_hierarchy(label);
let block = context.block(|c| {
let block: ir::DeclarationBlock =
Conv::conv(c, x.generate_optional_named_block.as_ref())?;
Ok(block)
});
context.pop_hierarchy();
return block;
}
}
if let Some(x) = &value.generate_if_declaration_opt {
let label = get_label(&x.generate_optional_named_block, label);
context.push_hierarchy(label);
let block = context.block(|c| {
let block: ir::DeclarationBlock =
Conv::conv(c, x.generate_optional_named_block.as_ref())?;
Ok(block)
});
context.pop_hierarchy();
block
} else {
Ok(ir::DeclarationBlock::default())
}
}
}
}
impl Conv<&GenerateNamedBlock> for ir::DeclarationBlock {
fn conv(context: &mut Context, value: &GenerateNamedBlock) -> IrResult<Self> {
let mut ret = vec![];
for x in &value.generate_named_block_list {
let items: Vec<_> = x.generate_group.as_ref().into();
for item in items {
let item: IrResult<ir::DeclarationBlock> = Conv::conv(context, item);
if let Ok(mut item) = item {
ret.append(&mut item.0);
}
}
}
Ok(ir::DeclarationBlock(ret))
}
}
impl Conv<&GenerateOptionalNamedBlock> for ir::DeclarationBlock {
fn conv(context: &mut Context, value: &GenerateOptionalNamedBlock) -> IrResult<Self> {
let mut ret = vec![];
for x in &value.generate_optional_named_block_list {
let items: Vec<_> = x.generate_group.as_ref().into();
for item in items {
let item: IrResult<ir::DeclarationBlock> = Conv::conv(context, item);
if let Ok(mut item) = item {
ret.append(&mut item.0);
}
}
}
Ok(ir::DeclarationBlock(ret))
}
}
impl Conv<&GenerateForDeclaration> for ir::DeclarationBlock {
fn conv(context: &mut Context, value: &GenerateForDeclaration) -> IrResult<Self> {
let token: TokenRange = (&value.identifier.identifier_token).into();
let label = value.generate_named_block.identifier.text();
let rev = value.generate_for_declaration_opt.is_some();
let step = value
.generate_for_declaration_opt0
.as_ref()
.map(|x| (x.assignment_operator.as_ref(), x.expression.as_ref()));
let range = eval_generate_for_range(context, &value.range, rev, step, token)?;
let mut ret = ir::DeclarationBlock::default();
for i in range {
let label = crate::conv::generate_block_label(label, i);
let index = value.identifier.text();
let path = VarPath::new(index);
let kind = VarKind::Const;
let comptime = Comptime::create_value(Value::new(i as u64, 32, false), token);
context.push_hierarchy(label);
let block = context.block(|c| {
let id = c.insert_var_path(path.clone(), comptime.clone());
let array_limit = c.config.evaluate_array_limit;
let variable = Variable::new(
id,
path,
kind,
comptime.r#type.clone(),
vec![comptime.get_value().unwrap().clone()],
c.get_affiliation(),
&token,
array_limit,
);
c.insert_variable(id, variable);
let block: IrResult<ir::DeclarationBlock> =
Conv::conv(c, value.generate_named_block.as_ref());
block
});
context.pop_hierarchy();
if let Ok(mut block) = block {
ret.0.append(&mut block.0);
}
}
Ok(ret)
}
}
impl Conv<&WithGenericParameterItem> for () {
fn conv(context: &mut Context, value: &WithGenericParameterItem) -> IrResult<Self> {
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::GenericParameter(x) = &symbol.found.kind
{
check_generic_bound_item(context, &x.bound, &symbol.found.namespace);
if let Some(y) = &value.with_generic_parameter_item_opt {
let token: TokenRange = y.with_generic_argument_item.as_ref().into();
match y.with_generic_argument_item.as_ref() {
WithGenericArgumentItem::Number(_)
| WithGenericArgumentItem::BooleanLiteral(_) => {
if let GenericBoundKind::Proto(_) = &x.bound {
if !matches!(
x.bound.resolve_proto_bound(&symbol.found.namespace),
Ok(ProtoBound::FactorType(_))
) {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::GenericArgument {
name: value.identifier.text().to_string(),
expected: x.bound.to_string(),
actual: "number".into(),
},
&token,
));
}
} else {
context.insert_error(AnalyzerError::mismatch_assignment(
"number",
&x.bound.to_string(),
&token,
&[],
));
}
}
WithGenericArgumentItem::FixedType(_) => {
if !matches!(x.bound, GenericBoundKind::Type) {
context.insert_error(AnalyzerError::mismatch_assignment(
"type",
&x.bound.to_string(),
&token,
&[],
));
}
}
WithGenericArgumentItem::GenericArgIdentifier(_) => {
}
}
}
Ok(())
} else {
let token: TokenRange = value.identifier.as_ref().into();
Err(ir_error!(token))
}
}
}
impl Conv<&WithParameterItem> for () {
fn conv(context: &mut Context, value: &WithParameterItem) -> IrResult<Self> {
let define_context: DefineContext = (&value.colon.colon_token).into();
if !define_context.is_active(&context.config.defines) {
return Ok(());
}
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::Parameter(x) = &symbol.found.kind
&& !x.is_proto
{
let path = VarPath::new(symbol.found.token.text);
let kind: VarKind = (&x.kind).into();
let token: TokenRange = value.into();
let variable_token: TokenRange = (&value.identifier.identifier_token).into();
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
let expr = if context.is_affiliated(Affiliation::ProtoModule) {
let comptime = Comptime::create_unknown(token);
let expr = ir::Expression::Term(Box::new(ir::Factor::Unknown(comptime.clone())));
(comptime, expr)
} else {
let Some(expr) = &x.value else {
context.insert_error(AnalyzerError::missing_default_argument(
&value.identifier.text().to_string(),
&value.identifier.as_ref().into(),
));
return Err(ir_error!(token));
};
eval_expr(context, Some(r#type.clone()), expr, false)?
};
let mut expr = context.get_override(&path).cloned().unwrap_or(expr);
let dst = ir::AssignDestination {
id: VarId::default(),
path: path.clone(),
index: VarIndex::default(),
select: VarSelect::default(),
comptime: Comptime::from_type(r#type, ClockDomain::None, TokenRange::default()),
token: variable_token,
};
eval_const_assign(context, kind, &dst, &mut expr)?;
Ok(())
} else {
let token: TokenRange = value.identifier.as_ref().into();
Err(ir_error!(token))
}
}
}
impl Conv<&PortDeclarationItem> for () {
fn conv(context: &mut Context, value: &PortDeclarationItem) -> IrResult<Self> {
let define_context: DefineContext = (&value.colon.colon_token).into();
if !define_context.is_active(&context.config.defines) {
return Ok(());
}
let token: TokenRange = value.into();
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::Port(x) = &symbol.found.kind
{
check_modport_in_port(context, value);
check_port_direction(context, value);
let path = VarPath::new(symbol.found.token.text);
let variable_token: TokenRange = (&value.identifier.identifier_token).into();
let pos = match x.direction {
Direction::Modport => TypePosition::Modport,
_ => TypePosition::Variable,
};
let r#type = x.r#type.to_ir_type(context, pos)?;
let clock_domain = x.clock_domain;
if !context.is_affiliated(Affiliation::Function) {
context.insert_port_type(path.clone(), r#type.clone(), clock_domain);
}
let kind = match x.direction {
Direction::Input => VarKind::Input,
Direction::Output => VarKind::Output,
Direction::Inout => VarKind::Inout,
Direction::Modport => {
match &r#type.kind {
ir::TypeKind::Modport(sig, name) => {
let effective_sig = context
.get_modport_signature(&value.identifier)
.map(Arc::new)
.unwrap_or_else(|| Arc::clone(sig));
let component = get_component(context, &effective_sig, token)?;
context
.insert_inst_signature(value.identifier.as_ref(), &effective_sig);
let base = value.identifier.text();
let ir::Component::Interface(component) = component.as_ref() else {
return Err(ir_error!(token));
};
let component = component.clone();
context.extract_interface_member(
base,
&r#type.array,
component,
Some(*name),
clock_domain,
variable_token,
);
let path = VarPath::new(value.identifier.text());
let r#type = {
let mut t = ir::Type::new(TypeKind::Modport(sig.clone(), *name));
t.array = r#type.array;
t
};
let comptime =
Comptime::from_type(r#type.clone(), clock_domain, variable_token);
context.insert_var_path(path.clone(), comptime);
}
ir::TypeKind::SystemVerilog => (),
_ => {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::SymbolKind {
name: symbol.found.token.to_string(),
expected: "modport".to_string(),
actual: r#type.kind.to_string(),
},
&variable_token,
));
}
}
return Ok(());
}
Direction::Interface => {
if let ir::TypeKind::AbstractInterface(_) = &r#type.kind {
let path = VarPath::new(value.identifier.text());
let comptime =
Comptime::from_type(r#type.clone(), clock_domain, variable_token);
context.insert_var_path(path.clone(), comptime);
}
return Ok(());
}
_ => {
return Err(ir_error!(token));
}
};
let default_value = if let Some(x) = &x.default_value {
let allow_anonymous = kind == VarKind::Output;
let default_value = eval_expr(context, Some(r#type.clone()), x, allow_anonymous);
check_port_default_value(context, value, &default_value, kind, x);
let (comptime, _) = default_value?;
if x.is_anonymous_expression() {
None
} else {
let value = comptime.get_value()?.clone();
Some((value, comptime))
}
} else {
None
};
if let Some((value, _)) = default_value {
let comptime = Comptime::from_type(r#type.clone(), clock_domain, variable_token);
let id = context.insert_var_path(path.clone(), comptime);
for x in r#type.expand_struct_union(&path, &[], None) {
let r#type = x.part_select.last().unwrap().r#type.clone();
let mut comptime = Comptime::from_type(r#type, clock_domain, variable_token);
let path = x.path.clone();
comptime.part_select = Some(x);
context.insert_var_path_with_id(path, id, comptime);
}
let array_limit = context.config.evaluate_array_limit;
let variable = Variable::new(
id,
path,
kind,
r#type,
vec![value.clone()],
context.get_affiliation(),
&variable_token,
array_limit,
);
context.insert_variable(id, variable);
} else {
eval_variable(context, &path, kind, &r#type, clock_domain, variable_token);
}
Ok(())
} else {
Err(ir_error!(token))
}
}
}
impl Conv<&AlwaysFfDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &AlwaysFfDeclaration) -> IrResult<Self> {
let clock = eval_clock(context, value)?;
let reset = eval_reset(context, value)?;
if let Some(reset) = &reset {
check_clock_domain(
context,
&clock.comptime,
&reset.comptime,
&value.always_ff.always_ff_token.token,
);
}
context.current_clock = Some(clock.comptime.clone());
context.push_affiliation(Affiliation::AlwaysFf);
let statements: IrResult<ir::StatementBlock> =
context.block(|c| Conv::conv(c, value.statement_block.as_ref()));
context.pop_affiliation();
Ok(ir::Declaration::new_ff(clock, reset, statements?.0))
}
}
impl Conv<&AlwaysCombDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &AlwaysCombDeclaration) -> IrResult<Self> {
context.push_affiliation(Affiliation::AlwaysComb);
let statements: IrResult<ir::StatementBlock> =
context.block(|c| Conv::conv(c, value.statement_block.as_ref()));
context.pop_affiliation();
Ok(ir::Declaration::new_comb(statements?.0))
}
}
impl Conv<&VarDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &VarDeclaration) -> IrResult<Self> {
let define_context: DefineContext = (&value.var.var_token).into();
if !define_context.is_active(&context.config.defines) {
return Ok(ir::Declaration::Null);
}
let token: TokenRange = value.into();
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::Variable(x) = &symbol.found.kind
{
let path = VarPath::new(symbol.found.token.text);
let kind = VarKind::Variable;
let variable_token: TokenRange = (&value.identifier.identifier_token).into();
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
let clock_domain = x.clock_domain;
if let crate::symbol::TypeKind::UserDefined(ref ud) = x.r#type.kind
&& let Ok(ty_symbol) =
symbol_table::resolve_generic_structural(&ud.path, &symbol.found.namespace)
&& let SymbolKind::TbComponent(c) = &ty_symbol.found.kind
{
match &c.kind {
TbComponentKind::File | TbComponentKind::Random => {
attribute_table::insert(
variable_token,
Attribute::Allow(AllowItem::UnassignVariable),
);
}
TbComponentKind::External(component) => {
if !context.in_test_module {
context.insert_error(AnalyzerError::invalid_tb_usage(&token));
return Ok(ir::Declaration::Null);
}
if let Some(manifest) = crate::component_manifest_table::get(*component)
&& manifest.kind.as_deref() == Some("clocked")
{
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::ClockedNeedsInst,
Some(*component),
&token,
));
}
let params =
conv_component_generic_args(context, &ud.path, *component, token)?;
attribute_table::insert(
variable_token,
Attribute::Allow(AllowItem::UnassignVariable),
);
eval_variable(context, &path, kind, &r#type, clock_domain, variable_token);
insert_component_params(context, symbol.found.token.text, ¶ms);
return Ok(ir::Declaration::External(Box::new(
ir::ExternalDeclaration {
name: symbol.found.token.text,
component: *component,
params,
connects: vec![],
is_var_form: true,
token,
},
)));
}
_ => {}
}
}
eval_variable(context, &path, kind, &r#type, clock_domain, variable_token);
Ok(ir::Declaration::Null)
} else {
Err(ir_error!(token))
}
}
}
impl Conv<&LetDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &LetDeclaration) -> IrResult<Self> {
let define_context: DefineContext = (&value.r#let.let_token).into();
if !define_context.is_active(&context.config.defines) {
return Ok(ir::Declaration::Null);
}
let token: TokenRange = value.into();
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::Variable(x) = &symbol.found.kind
{
let path = VarPath::new(symbol.found.token.text);
let kind = VarKind::Let;
let variable_token: TokenRange = (&value.identifier.identifier_token).into();
let inferred = try_infer_decl_type(
context,
&x.r#type,
&value.expression,
value.identifier.identifier_token.token.id,
token,
)?;
let r#type = if let Some((ref comptime, _)) = inferred {
comptime.r#type.clone()
} else {
x.r#type.to_ir_type(context, TypePosition::Variable)?
};
let clock_domain = x.clock_domain;
eval_variable(context, &path, kind, &r#type, clock_domain, variable_token);
let (id, comptime) = context.find_path(&path).ok_or_else(|| ir_error!(token))?;
let mut dst = ir::AssignDestination {
id,
path,
index: VarIndex::default(),
select: VarSelect::default(),
comptime,
token: variable_token,
};
let mut expr = if let Some(inferred) = inferred {
inferred
} else {
eval_expr(context, Some(r#type.clone()), &value.expression, false)?
};
let statements = eval_assign_statement(context, &mut dst, &mut expr, token)?;
Ok(ir::Declaration::new_comb(statements))
} else {
Err(ir_error!(token))
}
}
}
impl Conv<&ConstDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &ConstDeclaration) -> IrResult<Self> {
let define_context: DefineContext = (&value.r#const.const_token).into();
if !define_context.is_active(&context.config.defines) {
return Ok(ir::Declaration::Null);
}
let token: TokenRange = value.into();
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::Parameter(x) = &symbol.found.kind
&& !x.is_proto
{
let path = VarPath::new(symbol.found.token.text);
let kind: VarKind = (&x.kind).into();
let variable_token: TokenRange = (&value.identifier.identifier_token).into();
let inferred = try_infer_decl_type(
context,
&x.r#type,
&value.expression,
value.identifier.identifier_token.token.id,
token,
)?;
let r#type = if let Some((ref comptime, _)) = inferred {
comptime.r#type.clone()
} else {
x.r#type.to_ir_type(context, TypePosition::Variable)?
};
let Some(expr) = &x.value else {
context.insert_error(AnalyzerError::missing_default_argument(
&value.identifier.text().to_string(),
&value.identifier.as_ref().into(),
));
return Err(ir_error!(token));
};
let (comptime, expr) = if let Some(inferred) = inferred {
inferred
} else {
eval_expr(context, Some(r#type.clone()), expr, false)?
};
if !comptime.is_const {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::ConstValue,
&expr.token_range(),
));
}
let dst = ir::AssignDestination {
id: VarId::default(),
path: path.clone(),
index: VarIndex::default(),
select: VarSelect::default(),
comptime: Comptime::from_type(r#type, ClockDomain::None, TokenRange::default()),
token: variable_token,
};
eval_const_assign(context, kind, &dst, &mut (comptime, expr))?;
Ok(ir::Declaration::Null)
} else {
Err(ir_error!(token))
}
}
}
impl Conv<&GenDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &GenDeclaration) -> IrResult<Self> {
let define_context: DefineContext = (&value.r#gen.gen_token).into();
if !define_context.is_active(&context.config.defines) {
return Ok(ir::Declaration::Null);
}
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::GenericConst(x) = &symbol.found.kind
{
check_generic_bound_item(context, &x.bound, &symbol.found.namespace);
check_generic_expression(
context,
&x.value,
&x.bound,
None,
value.identifier.as_ref().into(),
);
Ok(ir::Declaration::Null)
} else {
Err(ir_error!(value.into()))
}
}
}
impl Conv<&AssignDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &AssignDeclaration) -> IrResult<Self> {
let define_context: DefineContext = (&value.assign.assign_token).into();
if !define_context.is_active(&context.config.defines) {
return Ok(ir::Declaration::Null);
}
let token: TokenRange = value.into();
match value.assign_destination.as_ref() {
AssignDestination::HierarchicalIdentifier(x) => {
let ident = x.hierarchical_identifier.as_ref();
let inferred =
if let Ok(symbol) = symbol_table::resolve(x.hierarchical_identifier.as_ref()) {
try_infer_var_assign(context, &symbol.found, &value.expression, token)?
} else {
None
};
let dst: VarPathSelect = Conv::conv(context, ident)?;
if let Some(statements) =
eval_array_range_assign(context, &dst, &value.expression, token)?
{
return Ok(ir::Declaration::new_comb(statements));
}
if let Some(mut dst) = dst.to_assign_destination(context, false) {
let mut expr = if let Some(inferred) = inferred {
inferred
} else {
let ctx_type = assign_rhs_context_type(context, &dst);
eval_expr(context, Some(ctx_type), &value.expression, false)?
};
let statements = eval_assign_statement(context, &mut dst, &mut expr, token)?;
Ok(ir::Declaration::new_comb(statements))
} else {
check_assign_before_definition(
context,
x.hierarchical_identifier.as_ref(),
ident.identifier.identifier_token.token,
);
Err(ir_error!(token))
}
}
AssignDestination::LBraceAssignConcatenationListRBrace(x) => {
let items: Vec<_> = x.assign_concatenation_list.as_ref().into();
let mut dst = vec![];
for item in items {
let ident = item.hierarchical_identifier.as_ref();
let x: VarPathSelect = Conv::conv(context, ident)?;
if let Some(x) = x.to_assign_destination(context, false) {
dst.push(x);
} else {
check_assign_before_definition(
context,
item.hierarchical_identifier.as_ref(),
ident.identifier.identifier_token.token,
);
return Err(ir_error!(token));
}
}
let mut width = Some(0);
for x in &dst {
if let Some(x) = x.total_width(context)
&& let Some(width) = &mut width
{
*width += x;
} else {
width = None;
}
}
if let Some(x) = width {
width = context.check_size(x, token);
}
let r#type = {
let mut t = ir::Type::new(TypeKind::Logic);
t.set_concrete_width(Shape::new(vec![width]));
t
};
let (comptime, expr) = eval_expr(context, Some(r#type), &value.expression, false)?;
for d in &mut dst {
check_assign_clock_domain(context, d, &comptime, &token);
}
let statement = ir::Statement::Assign(ir::AssignStatement {
dst,
width,
expr,
token,
});
Ok(ir::Declaration::Comb(ir::CombDeclaration {
statements: vec![statement],
}))
}
}
}
}
impl Conv<(&FunctionDeclaration, Option<&FuncPath>)> for () {
fn conv(
context: &mut Context,
value: (&FunctionDeclaration, Option<&FuncPath>),
) -> IrResult<Self> {
let (func_def, path) = value;
let token: TokenRange = (&func_def.identifier.identifier_token).into();
if let Some(x) = &func_def.function_declaration_opt {
check_generic_bound(context, &x.with_generic_parameter);
}
if let Ok(symbol) = symbol_table::resolve(func_def.identifier.as_ref())
&& let SymbolKind::Function(x) = &symbol.found.kind
&& !x.is_proto
{
let port_declaration = func_def
.function_declaration_opt0
.as_ref()
.map(|x| x.port_declaration.as_ref());
let statement_block = Some(func_def.statement_block.as_ref());
conv_function(
context,
path,
&symbol.found,
port_declaration,
statement_block,
token,
)
} else {
Err(ir_error!(token))
}
}
}
impl Conv<&FunctionDeclaration> for () {
fn conv(context: &mut Context, value: &FunctionDeclaration) -> IrResult<Self> {
if value.function_declaration_opt.is_some() {
context.ignore_var_func = true;
}
let ret = Conv::conv(context, (value, None));
if value.function_declaration_opt.is_some() {
context.ignore_var_func = false;
Ok(())
} else {
ret
}
}
}
impl Conv<(&ProtoFunctionDeclaration, Option<&FuncPath>)> for () {
fn conv(
context: &mut Context,
value: (&ProtoFunctionDeclaration, Option<&FuncPath>),
) -> IrResult<Self> {
let (func_def, path) = value;
let token: TokenRange = (&func_def.identifier.identifier_token).into();
if let Some(x) = &func_def.proto_function_declaration_opt {
check_generic_bound(context, &x.with_generic_parameter);
}
if let Ok(symbol) = symbol_table::resolve(func_def.identifier.as_ref())
&& let SymbolKind::Function(x) = &symbol.found.kind
&& x.is_proto
{
let port_declaration = func_def
.proto_function_declaration_opt0
.as_ref()
.map(|x| x.port_declaration.as_ref());
conv_function(context, path, &symbol.found, port_declaration, None, token)
} else {
Err(ir_error!(token))
}
}
}
fn conv_function(
context: &mut Context,
path: Option<&FuncPath>,
symbol: &Symbol,
port_declaratoin: Option<&PortDeclaration>,
statement_block: Option<&StatementBlock>,
token: TokenRange,
) -> IrResult<()> {
let (path, name, namespace) = if let Some(path) = path {
let name = resource_table::insert_str(&path.sig.to_string());
(path.clone(), name, path.sig.namespace())
} else {
let path = FuncPath::new(symbol.id);
(path, symbol.token.text, symbol.inner_namespace())
};
if context.func_paths.contains_key(&path) {
return Ok(());
}
if context.converting_funcs.contains(&symbol.id) {
return Ok(());
}
let id = context.insert_func_path(path.clone());
let proeprty = match &symbol.kind {
SymbolKind::Function(x) => x,
_ => unreachable!(),
};
let ret_type = if let Some(ret_type) = proeprty.ret.as_ref() {
let mut r#type = ret_type.to_ir_type(context, TypePosition::Variable)?;
if r#type.is_struct() {
r#type.set_concrete_width(Shape::new(vec![r#type.total_width()]));
r#type.kind = if r#type.is_2state() {
TypeKind::Bit
} else {
TypeKind::Logic
};
}
Comptime::from_type(r#type, ClockDomain::None, token)
} else {
let r#type = ir::Type::new(TypeKind::Void);
Comptime::from_type(r#type, ClockDomain::None, token)
};
let mut args = vec![];
let ports: Vec<_> = if let Some(port_declaratoin) = port_declaratoin
&& let Some(ref x) = port_declaratoin.port_declaration_opt
{
x.port_declaration_list.as_ref().into()
} else {
vec![]
};
let arity = ports.len();
context.converting_funcs.push(symbol.id);
context.push_affiliation(Affiliation::Function);
context.push_hierarchy(name);
context.push_namespace(namespace);
let token: TokenRange = symbol.token.into();
let func = context.block(|c| {
let ret_id = if ret_type.r#type.kind != TypeKind::Void {
let path = VarPath::new(get_return_str());
let kind = VarKind::Variable;
let r#type = ret_type.r#type.clone();
if let Some(_total_array) = r#type.total_array()
&& let Some(total_width) = r#type.total_width()
{
let values = vec![Value::new_x(total_width, false)];
let ret_id = c.insert_var_path(path.clone(), ret_type.clone());
let array_limit = c.config.evaluate_array_limit;
let variable = Variable::new(
ret_id,
path,
kind,
r#type,
values,
c.get_affiliation(),
&token,
array_limit,
);
c.insert_variable(ret_id, variable);
Some(ret_id)
} else {
None
}
} else {
None
};
let mut arg_map = HashMap::default();
for port in ports {
let _: IrResult<()> = Conv::conv(c, port);
let name = port.identifier.text();
let path = VarPath::new(name);
if let Some((_, comptime)) = c.var_paths.get(&path).cloned() {
let modport_members = comptime.r#type.expand_modport(c, &path, token)?;
if modport_members.is_empty() {
let mut members = vec![];
if let Some((id, comptime)) = c.var_paths.get(&path).cloned() {
let variable = c.variables.get(&id).ok_or_else(|| ir_error!(token))?;
let direction = match variable.kind {
VarKind::Input => Direction::Input,
VarKind::Output => Direction::Output,
_ => unreachable!(),
};
arg_map.insert(path.clone(), id);
members.push((path.clone(), comptime, direction));
}
let arg = FuncArg {
name: path.first(),
comptime,
members,
};
args.push(arg);
} else {
let mut members = vec![];
for (path, direction) in modport_members {
if let Some((id, comptime)) = c.var_paths.get(&path) {
arg_map.insert(path.clone(), *id);
members.push((path, comptime.clone(), direction));
}
}
let arg = FuncArg {
name: path.first(),
comptime,
members,
};
args.push(arg);
}
}
}
let mut output_ids = HashMap::default();
for (arg, port) in args.iter().zip(&proeprty.ports) {
let Some(port) = symbol_table::get(port.symbol) else {
continue;
};
let SymbolKind::Port(port) = &port.kind else {
continue;
};
for (path, _, direction) in &arg.members {
if !matches!(direction, Direction::Output | Direction::Inout) {
continue;
}
if let Some(id) = arg_map.get(path) {
output_ids.insert(
*id,
FunctionOutputBinding {
scheduler_external: port.direction != Direction::Output,
},
);
}
}
}
let (specialized_external_write, specialized_formal_writes) =
symbol_table::specialized_direct_effect(symbol, c);
let mut effect = RuntimeFunctionEffect {
external_write: proeprty.has_side_effect_in(&c.config.defines),
written_outputs: proeprty
.written_output_paths(&c.config.defines)
.into_iter()
.filter_map(|path| path.to_var_path())
.collect(),
};
effect.external_write |= specialized_external_write;
effect.external_write |= output_ids.values().any(|output| output.scheduler_external);
effect.written_outputs.extend(
specialized_formal_writes
.into_iter()
.filter_map(|path| path.to_var_path()),
);
for arg in &args {
for (path, _, direction) in &arg.members {
if matches!(direction, Direction::Output | Direction::Inout) {
effect.written_outputs.insert(path.clone());
}
}
}
c.begin_function_effect(path.clone(), output_ids, effect);
let body = if let Some(block) = statement_block {
let disable_const_opt = c.disalbe_const_opt;
c.disalbe_const_opt = true;
let in_tb_block = c.in_tb_block;
let in_initial = c.in_initial;
c.in_tb_block = false;
c.in_initial = None;
let statements: IrResult<ir::StatementBlock> = Conv::conv(c, block);
c.in_tb_block = in_tb_block;
c.in_initial = in_initial;
c.disalbe_const_opt = disable_const_opt;
let statements = match statements {
Ok(statements) => statements,
Err(error) => {
c.finish_function_effect();
return Err(error);
}
};
c.record_function_statement_writes(&statements.0);
c.finish_function_effect();
vec![ir::FunctionBody {
ret: ret_id,
arg_map,
statements: statements.0,
}]
} else {
c.finish_function_effect();
vec![]
};
Ok((args, body))
});
context.pop_affiliation();
context.pop_hierarchy();
context.pop_namespace();
context.converting_funcs.pop();
let (args, body) = func?;
let func = ir::Function {
name,
id,
path,
r#type: ret_type,
array: Shape::default(),
arity,
args,
is_const: proeprty.constantable.unwrap_or_default(),
functions: body,
token,
};
context.insert_function(id, func);
Ok(())
}
impl Conv<&StructUnionDeclaration> for () {
fn conv(context: &mut Context, value: &StructUnionDeclaration) -> IrResult<Self> {
eval_type(
context,
&value.identifier.as_ref().into(),
TypePosition::TypeDef,
)?;
if let Some(x) = &value.struct_union_declaration_opt {
check_generic_bound(context, &x.with_generic_parameter);
}
if context.is_affiliated(Affiliation::Interface) {
let kind = match value.struct_union.as_ref() {
StructUnion::Struct(_) => "struct",
StructUnion::Union(_) => "union",
};
context.insert_error(AnalyzerError::invalid_type_declaration(kind, &value.into()));
}
Ok(())
}
}
impl Conv<&EnumDeclaration> for () {
fn conv(context: &mut Context, value: &EnumDeclaration) -> IrResult<Self> {
eval_type(
context,
&value.identifier.as_ref().into(),
TypePosition::TypeDef,
)?;
if context.is_affiliated(Affiliation::Interface) {
context.insert_error(AnalyzerError::invalid_type_declaration(
"enum",
&value.into(),
));
}
Ok(())
}
}
impl Conv<&InitialDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &InitialDeclaration) -> IrResult<Self> {
context.in_tb_block = true;
context.in_initial = Some(context.var_id);
context.tb_hoist = Some(Vec::new());
let statements: IrResult<ir::StatementBlock> =
Conv::conv(context, value.statement_block.as_ref());
context.tb_hoist = None;
context.in_initial = None;
context.in_tb_block = false;
Ok(ir::Declaration::Initial(ir::InitialDeclaration {
statements: statements?.0,
}))
}
}
impl Conv<&FinalDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &FinalDeclaration) -> IrResult<Self> {
context.in_tb_block = true;
context.tb_hoist = Some(Vec::new());
let statements: IrResult<ir::StatementBlock> =
Conv::conv(context, value.statement_block.as_ref());
context.tb_hoist = None;
context.in_tb_block = false;
Ok(ir::Declaration::Final(ir::FinalDeclaration {
statements: statements?.0,
}))
}
}
impl Conv<&InstDeclaration> for ir::Declaration {
fn conv(context: &mut Context, value: &InstDeclaration) -> IrResult<Self> {
let define_context: DefineContext = (&value.inst.inst_token).into();
if !define_context.is_active(&context.config.defines) {
return Ok(ir::Declaration::Null);
}
let in_module = context.is_affiliated(Affiliation::Module);
let token = value.inst.inst_token.token;
check_inst(context, in_module, &token, &value.component_instantiation);
let path: SymbolPathNamespace = value
.component_instantiation
.scoped_identifier
.as_ref()
.into();
if let Ok(symbol) = symbol_table::resolve(&path)
&& let SymbolKind::TbComponent(ref tb_prop) = symbol.found.kind
{
if !context.in_test_module {
let token: TokenRange = value
.component_instantiation
.scoped_identifier
.as_ref()
.into();
context.insert_error(AnalyzerError::invalid_tb_usage(&token));
return Ok(ir::Declaration::Null);
}
if matches!(tb_prop.kind, TbComponentKind::File) {
let token: TokenRange = value
.component_instantiation
.scoped_identifier
.as_ref()
.into();
context.insert_error(AnalyzerError::invalid_tb_usage(&token));
return Ok(ir::Declaration::Null);
}
if let TbComponentKind::External(component) = &tb_prop.kind {
return conv_external_component(context, value, *component);
}
let inst_token: TokenRange = value.component_instantiation.identifier.as_ref().into();
let inst_name = value
.component_instantiation
.identifier
.identifier_token
.token
.text;
let var_path = ir::VarPath::new(inst_name);
let type_kind = match tb_prop.kind {
TbComponentKind::ClockGen => ir::TypeKind::Clock,
TbComponentKind::ResetGen => ir::TypeKind::Reset,
TbComponentKind::File | TbComponentKind::Random | TbComponentKind::External(_) => {
unreachable!()
}
};
let ir_type = {
let mut t = ir::Type::new(type_kind);
t.set_concrete_width(ir::Shape::new(vec![Some(1)]));
t
};
eval_variable(
context,
&var_path,
ir::VarKind::Variable,
&ir_type,
ClockDomain::None,
inst_token,
);
attribute_table::insert(inst_token, Attribute::Allow(AllowItem::UnassignVariable));
if let Some(ref opt1) = value.component_instantiation.component_instantiation_opt1
&& let Some(ref param_opt) = opt1.inst_parameter.inst_parameter_opt
{
let items: Vec<_> = param_opt.inst_parameter_list.as_ref().into();
for item in items {
let param_name = item.identifier.identifier_token.token.text.to_string();
match (&tb_prop.kind, param_name.as_str()) {
(TbComponentKind::ResetGen, "cycles") => {
if let Some(ref opt) = item.inst_parameter_item_opt {
let (_, expr) = eval_expr(context, None, &opt.expression, false)?;
context.tb_reset_cycles.insert(inst_name, expr);
}
}
(TbComponentKind::ClockGen, "period") => {
if let Some(ref opt) = item.inst_parameter_item_opt {
let (_, expr) = eval_expr(context, None, &opt.expression, false)?;
context.tb_clock_period.insert(inst_name, expr);
}
}
_ => {}
}
}
}
let mut connected_clk: Option<StrId> = None;
if let Some(ref opt2) = value.component_instantiation.component_instantiation_opt2
&& let Some(ref port_opt) = opt2.inst_port.inst_port_opt
{
let items: Vec<_> = port_opt.inst_port_list.as_ref().into();
for item in items {
let port_name = item.identifier.identifier_token.token.text;
let port_name_str =
resource_table::get_str_value(port_name).unwrap_or_default();
let port_token: TokenRange = item.identifier.as_ref().into();
match (&tb_prop.kind, port_name_str.as_str()) {
(TbComponentKind::ResetGen, "clk") => {
let signal_name = if let Some(ref x) = item.inst_port_item_opt {
let dst: Vec<VarPathSelect> =
Conv::conv(context, x.expression.as_ref())?;
dst.first().and_then(|p| p.0.0.first().copied())
} else {
Some(port_name)
};
if let Some(name) = signal_name {
connected_clk = Some(name);
}
}
_ => {
let type_name = match tb_prop.kind {
TbComponentKind::ClockGen => "$tb::clock_gen",
TbComponentKind::ResetGen => "$tb::reset_gen",
TbComponentKind::File
| TbComponentKind::Random
| TbComponentKind::External(_) => {
unreachable!()
}
};
context.insert_error(AnalyzerError::unknown_tb_port(
type_name,
&port_name_str,
&port_token,
));
}
}
}
}
if matches!(tb_prop.kind, TbComponentKind::ResetGen) {
if let Some(clk) = connected_clk {
context.tb_reset_clock.insert(inst_name, clk);
} else {
let type_name: TokenRange = value
.component_instantiation
.scoped_identifier
.as_ref()
.into();
context.insert_error(AnalyzerError::missing_tb_port(
"$tb::reset_gen",
"clk",
&type_name,
));
}
}
return Ok(ir::Declaration::Null);
}
let clock_domain = if let Ok(symbol) =
symbol_table::resolve(value.component_instantiation.identifier.as_ref())
&& let SymbolKind::Instance(x) = &symbol.found.kind
{
x.clock_domain
} else {
ClockDomain::None
};
let value = value.component_instantiation.as_ref();
let token: TokenRange = value.identifier.as_ref().into();
let generic_path: GenericSymbolPath = value.scoped_identifier.as_ref().into();
check_generic_refereence(context, &generic_path);
let mut sig =
Signature::from_path(context, generic_path).ok_or_else(|| ir_error!(token))?;
let symbol = symbol_table::get(sig.symbol).unwrap();
let parameters = symbol.kind.get_parameters();
let overridden_params = get_overridden_params(context, value)?;
for x in parameters {
let path = VarPath::new(x.name);
if let Some(value) = overridden_params.get(&path) {
sig.add_parameter(x.name, value.0.value.clone());
}
}
context.push_override(overridden_params);
let modport_signatures = context.collect_modport_signatures(value);
let mut modport_signatures: Vec<_> = modport_signatures.into_iter().collect();
modport_signatures.sort();
for (port_name, modport_sig) in modport_signatures {
sig.add_modport_signature(port_name, modport_sig);
}
let component = context.block(|c| get_component(c, &sig, token));
context.pop_override();
context.pop_modport_signatures();
let component_arc = component?;
match component_arc.as_ref() {
ir::Component::Module(component) => {
let mut inputs = vec![];
let mut outputs = vec![];
let mut interface_bindings = vec![];
if let Some(x) = &value.component_instantiation_opt2
&& let Some(x) = &x.inst_port.inst_port_opt
{
let ports: Vec<_> = x.inst_port_list.as_ref().into();
let mut clock_domain_table = HashMap::default();
for port in ports {
let name = port.identifier.text();
let path = VarPath::new(name);
let token: TokenRange = port.identifier.as_ref().into();
if let Some((dst_type, clock_domain)) = component.port_types.get(&path) {
context.in_inst_port = component
.ports
.get(&path)
.and_then(|id| component.variables.get(id))
.is_some_and(|v| v.kind == ir::VarKind::Input);
let connects =
get_port_connects(context, component, port, &path, dst_type, token);
context.in_inst_port = false;
let Ok(connects) = connects else {
continue;
};
if let Some(actual) = connects.interface_binding {
for (child, child_variable) in &component.interface_members {
if !child_variable.path.starts_with(&path.0) {
continue;
}
let mut parent_path = actual.0.clone();
parent_path.append(&child_variable.path.0[path.0.len()..]);
let Some((parent, comptime)) = context.find_path(&parent_path)
else {
continue;
};
let (array_select, width_select) =
actual.1.clone().split(comptime.r#type.array.dims());
interface_bindings.push(ir::InstInterfaceBinding {
child: *child,
parent,
index: array_select.to_index(),
select: width_select,
});
}
}
let dst_comptime =
Comptime::from_type(dst_type.clone(), *clock_domain, token);
for (path, dst, expr) in connects.connects {
if let Some(id) = component.ports.get(&path)
&& let Some(variable) = component.variables.get(id)
{
let mut exprs =
expand_input_connect(context, variable, &dst, expr);
for expr in exprs.iter_mut() {
let expr_comptime = expr.eval_comptime(context, None);
if let Some(x) =
clock_domain_table.get(&dst_comptime.clock_domain)
{
check_clock_domain(
context,
x,
expr_comptime,
&token.beg,
);
} else {
clock_domain_table.insert(
dst_comptime.clock_domain,
expr_comptime.clone(),
);
}
}
insert_port_connect(
context,
variable,
dst,
exprs,
&mut inputs,
&mut outputs,
);
}
}
}
}
}
let name = value.identifier.text();
if value.component_instantiation_opt0.is_none() {
context.inst_components.insert(name, component_arc.clone());
}
Ok(ir::Declaration::Inst(Box::new(ir::InstDeclaration {
name,
hierarchy: context.current_hierarchy().to_vec(),
inputs,
outputs,
interface_bindings,
component: component_arc,
token,
})))
}
ir::Component::Interface(component) => {
context.insert_inst_signature(value.identifier.as_ref(), &sig);
let mut array = Shape::default();
if let Some(x) = &value.component_instantiation_opt0 {
let exprs: Vec<_> = x.array.as_ref().into();
for expr in exprs {
let (_, value) = eval_size(context, expr, false)?;
array.push(value)
}
}
let base = value.identifier.text();
context.extract_interface_member(
base,
&array,
component.clone(),
None,
clock_domain,
token,
);
let path = VarPath::new(value.identifier.text());
let r#type = {
let mut t =
ir::Type::new(TypeKind::Instance(sig.into(), InstanceKind::Interface));
t.array = array;
t
};
let comptime = Comptime::from_type(r#type.clone(), clock_domain, token);
context.insert_var_path(path.clone(), comptime);
Ok(ir::Declaration::Null)
}
ir::Component::SystemVerilog(component) => {
let mut component = component.clone();
if let Some(x) = &value.component_instantiation_opt2
&& let Some(x) = &x.inst_port.inst_port_opt
{
let ports: Vec<_> = x.inst_port_list.as_ref().into();
let mut prev_port = None;
for port in ports {
let dst_paths = if let Some(x) = &port.inst_port_item_opt {
let dst: Vec<VarPathSelect> =
Conv::conv(context, x.expression.as_ref())?;
dst
} else {
let name = port.identifier.text();
let path = VarPath::new(name);
let path = VarPathSelect(
path,
VarSelect::default(),
port.identifier.identifier_token.token.into(),
);
vec![path]
};
let mut expanded_paths = vec![];
for dst_path in dst_paths {
if let Some((_, comptime)) = context.find_path(&dst_path.0) {
if comptime.r#type.is_interface() {
let paths = comptime.r#type.expand_interface(
context,
&dst_path.0,
comptime.token,
)?;
for x in paths {
let path =
VarPathSelect(x.0, dst_path.1.clone(), dst_path.2);
expanded_paths.push(path);
}
} else {
expanded_paths.push(dst_path);
}
}
}
let mut dst_paths =
var_path_to_assign_destination(context, expanded_paths, true);
for dst_path in &dst_paths {
if let Some((_, comptime)) = context.find_path(&dst_path.path) {
if let Some(prev) = &prev_port {
check_clock_domain(context, &comptime, prev, &token.beg);
}
if comptime.r#type.is_reset()
&& !comptime.r#type.is_explicit_reset()
{
context.insert_error(AnalyzerError::sv_with_implicit_reset(
&token,
));
}
prev_port = Some(comptime);
}
}
component.connects.append(&mut dst_paths);
}
}
let name = value.identifier.text();
let component = Arc::new(ir::Component::SystemVerilog(component));
Ok(ir::Declaration::Inst(Box::new(ir::InstDeclaration {
name,
hierarchy: context.current_hierarchy().to_vec(),
inputs: vec![],
outputs: vec![],
interface_bindings: vec![],
component,
token,
})))
}
}
}
}
fn insert_component_params(
context: &mut Context,
inst: StrId,
params: &[(StrId, ir::ExternalParamValue)],
) {
let numeric: Vec<(String, u64)> = params
.iter()
.filter_map(|(n, v)| match v {
ir::ExternalParamValue::Value(v) => Some((n.to_string(), v.to_u64()?)),
ir::ExternalParamValue::Str(_) => None,
})
.collect();
context.tb_component_params.insert(inst, numeric);
}
struct GroupConnect {
group: StrId,
sig: Arc<Signature>,
modport: StrId,
token: TokenRange,
}
fn insert_group_params(
context: &mut Context,
inst: StrId,
component: StrId,
group_sigs: &[GroupConnect],
token: TokenRange,
) {
let Some(manifest) = crate::component_manifest_table::get(component) else {
return;
};
let mut wanted: Vec<(String, String)> = vec![];
for m in &manifest.methods {
if let Some(expr) = &m.ret_width {
expr.group_refs(&mut wanted);
}
}
wanted.sort();
wanted.dedup();
let mut resolved: Vec<(String, u64)> = vec![];
for (group, cname) in &wanted {
let Some(gc) = group_sigs.iter().find(|gc| gc.group.to_string() == *group) else {
continue;
};
let name = resource_table::insert_str(cname);
if let Some(v) = eval_group_param(context, &gc.sig, name, token) {
resolved.push((format!("{group}.{cname}"), v));
}
}
if !resolved.is_empty() {
context
.tb_component_params
.entry(inst)
.or_default()
.extend(resolved);
}
}
fn synthesize_name_path(name: StrId, template: Token, range: TokenRange) -> GenericSymbolPath {
let tok = Token::new(
&name.to_string(),
template.line,
template.column,
template.length,
template.pos,
template.source,
);
GenericSymbolPath {
paths: vec![GenericSymbol {
base: tok,
arguments: vec![],
}],
kind: crate::symbol_path::GenericSymbolPathKind::Identifier,
range,
}
}
fn eval_group_param(
context: &mut Context,
sig: &Signature,
name: StrId,
token: TokenRange,
) -> Option<u64> {
let iface = symbol_table::get(sig.symbol)?;
let ns = iface.inner_namespace();
let segs = vec![name];
let spn: SymbolPathNamespace = (&segs, &ns).into();
let found = symbol_table::resolve(&spn).ok()?;
if !found.imported {
let path = synthesize_name_path(name, iface.token, token);
return eval_resolved_const(context, path, (*found).clone(), sig.to_generic_map(), token);
}
let scope = crate::scope::inner_scope(iface.scope, iface.token.text);
for wildcard in crate::scope::wildcards_get(scope) {
let Some(seg) = wildcard.package_path.paths.first() else {
continue;
};
let Some((_, arg)) = sig
.generic_parameters
.iter()
.find(|(n, _)| *n == seg.base())
else {
continue;
};
let mut path = arg.clone();
let Some(last) = path.paths.last() else {
continue;
};
let member = synthesize_name_path(name, last.base, token);
path.paths.extend(member.paths);
let Ok(found) = symbol_table::resolve_generic_structural(&path, path.paths[0].base.id)
else {
continue;
};
let maps = path.to_generic_maps();
if let Some(v) = eval_resolved_const(context, path, (*found).clone(), maps, token) {
return Some(v);
}
}
let path = synthesize_name_path(name, iface.token, token);
eval_resolved_const(context, path, (*found).clone(), sig.to_generic_map(), token)
}
fn eval_resolved_const(
context: &mut Context,
path: GenericSymbolPath,
symbol: crate::symbol_table::ResolveResult,
maps: Vec<crate::symbol::GenericMap>,
token: TokenRange,
) -> Option<u64> {
let factor = eval_factor_symbol_external(context, path, symbol, maps, false, token);
if let Ok(ir::Factor::Value(comptime)) = factor
&& comptime.is_const
&& let ValueVariant::Numeric(v) = comptime.value
{
v.to_u64()
} else {
None
}
}
fn conv_component_generic_args(
context: &mut Context,
ud_path: &crate::symbol_path::GenericSymbolPath,
component: StrId,
token: TokenRange,
) -> IrResult<Vec<(StrId, ir::ExternalParamValue)>> {
let args = &ud_path.paths.last().unwrap().arguments;
let manifest = crate::component_manifest_table::get(component);
let Some(manifest) = manifest else {
if !args.is_empty() {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::GenericParamsNeedManifest,
Some(component),
&token,
));
}
return Ok(vec![]);
};
if args.len() > manifest.params.len() {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::TooManyGenericArgs {
declared: manifest.params.len(),
supplied: args.len(),
},
Some(component),
&token,
));
}
let mut params = vec![];
for (arg, mp) in args.iter().zip(manifest.params.iter()) {
let name = resource_table::insert_str(&mp.name);
match eval_component_generic_arg(context, arg, token)? {
Some(v) => params.push((name, ir::ExternalParamValue::Value(v))),
None => {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::ParameterValue,
&token,
));
}
}
}
for mp in &manifest.params[args.len().min(manifest.params.len())..] {
if !mp.optional {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::RequiredParamMissing {
param: mp.name.clone(),
},
Some(component),
&token,
));
}
}
Ok(params)
}
fn eval_component_generic_arg(
context: &mut Context,
arg: &GenericSymbolPath,
token: TokenRange,
) -> IrResult<Option<Value>> {
use crate::symbol_path::GenericSymbolPathKind;
match arg.kind {
GenericSymbolPathKind::ValueLiteral if arg.paths.len() == 1 => {
let text = arg.paths[0].base.to_string();
if !text.starts_with(|c: char| c.is_ascii_digit() || c == '\'') {
return Ok(None);
}
let value: Value = text.parse().unwrap();
Ok(value.to_u64().map(|v| Value::new(v, 64, false)))
}
GenericSymbolPathKind::Identifier if arg.paths.len() == 1 => {
let identifier = Identifier {
identifier_token: veryl_parser::veryl_token::VerylToken::new(arg.paths[0].base),
};
let src: Expression = (&identifier).into();
let (comptime, _) = eval_expr(context, None, &src, false)?;
if comptime.is_const
&& let ValueVariant::Numeric(v) = comptime.value
{
Ok(Some(v))
} else {
Ok(None)
}
}
GenericSymbolPathKind::Identifier => {
let Ok(symbol) = symbol_table::resolve_generic_structural(arg, arg.paths[0].base.id)
else {
return Ok(None);
};
let factor = eval_factor_symbol(context, arg.clone(), (*symbol).clone(), false, token);
if let Ok(ir::Factor::Value(comptime)) = factor
&& comptime.is_const
&& let ValueVariant::Numeric(v) = comptime.value
{
Ok(Some(v))
} else {
Ok(None)
}
}
_ => Ok(None),
}
}
fn conv_external_component(
context: &mut Context,
value: &InstDeclaration,
component: StrId,
) -> IrResult<ir::Declaration> {
let ci = value.component_instantiation.as_ref();
let token: TokenRange = ci.identifier.as_ref().into();
let name = ci.identifier.text();
if ci.component_instantiation_opt0.is_some() {
context.insert_error(AnalyzerError::invalid_tb_usage(&token));
return Ok(ir::Declaration::Null);
}
let type_path: GenericSymbolPath = ci.scoped_identifier.as_ref().into();
if type_path.paths.iter().any(|x| !x.arguments.is_empty()) {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::InstFormUsesParen,
Some(component),
&token,
));
}
let mut params = vec![];
if let Some(ref opt1) = ci.component_instantiation_opt1
&& let Some(ref param_opt) = opt1.inst_parameter.inst_parameter_opt
{
let items: Vec<_> = param_opt.inst_parameter_list.as_ref().into();
for item in items {
let param_name =
resource_table::canonical_str_id(item.identifier.identifier_token.token.text);
let param_token: TokenRange = item.identifier.as_ref().into();
let (comptime, _) = if let Some(ref opt) = item.inst_parameter_item_opt {
eval_expr(context, None, &opt.expression, false)?
} else {
let src: Expression = item.identifier.as_ref().into();
eval_expr(context, None, &src, false)?
};
if comptime.is_const
&& let ValueVariant::Numeric(v) = comptime.value
{
let value = if comptime.r#type.is_string() {
match crate::value::byte_value_to_string(&v) {
Some(s) => {
let s = s.strip_prefix('"').unwrap_or(&s);
let s = s.strip_suffix('"').unwrap_or(s);
ir::ExternalParamValue::Str(s.to_string())
}
None => {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::ParameterValue,
¶m_token,
));
continue;
}
}
} else {
ir::ExternalParamValue::Value(v)
};
params.push((param_name, value));
} else {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::ParameterValue,
¶m_token,
));
}
}
}
let mut connects = vec![];
let mut group_sigs: Vec<GroupConnect> = vec![];
let mut written: HashSet<String> = HashSet::default();
if let Some(ref opt2) = ci.component_instantiation_opt2
&& let Some(ref port_opt) = opt2.inst_port.inst_port_opt
{
let items: Vec<_> = port_opt.inst_port_list.as_ref().into();
for item in items {
written.insert(
resource_table::canonical_str_id(item.identifier.identifier_token.token.text)
.to_string(),
);
let saved_tb_block = context.in_tb_block;
context.in_tb_block = true;
let connect = conv_external_connect(context, item, &mut group_sigs);
context.in_tb_block = saved_tb_block;
connects.extend(connect?);
}
}
if let Some(manifest) = crate::component_manifest_table::get(component) {
if manifest.kind.as_deref() == Some("method_only") {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::MethodOnlyNeedsVar,
Some(component),
&token,
));
}
for (param_name, _) in ¶ms {
if !manifest.params.is_empty() && manifest.param(¶m_name.to_string()).is_none() {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::UnknownParam {
param: param_name.to_string(),
},
Some(component),
&token,
));
}
}
for mp in &manifest.params {
if !mp.optional && !params.iter().any(|(n, _)| n.to_string() == mp.name) {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::RequiredParamMissing {
param: mp.name.clone(),
},
Some(component),
&token,
));
}
}
let has_decls = !(manifest.ports.is_empty() && manifest.groups.is_empty());
let mut seen_groups: HashSet<StrId> = HashSet::default();
for gc in &group_sigs {
if !seen_groups.insert(gc.group) {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::PortMultiplyConnected {
port: gc.group.to_string(),
},
Some(component),
&gc.token,
));
}
if has_decls
&& !manifest
.groups
.iter()
.any(|g| g.name == gc.group.to_string())
{
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::UnknownGroup {
group: gc.group.to_string(),
},
Some(component),
&gc.token,
));
}
}
for grp in &manifest.groups {
if grp.members.is_empty() || grp.members.iter().any(|m| m.member.is_empty()) {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::InvalidManifest {
reason: format!("interface port `{}` declares no valid members", grp.name),
},
Some(component),
&token,
));
}
}
let mut connected: HashSet<&str> = HashSet::default();
let mut connected_members: HashSet<(String, String)> = HashSet::default();
for connect in &connects {
if !has_decls {
break;
}
let port_name = connect.port.to_string();
let group = connect.group.map(|g| g.to_string());
let member = connect.member.map(|m| m.to_string());
let target =
manifest.connection_target(&port_name, group.as_deref(), member.as_deref());
let Some(target) = target else {
if connect.group.is_none() {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::UnknownPort {
port: port_name.clone(),
},
Some(component),
&connect.token,
));
}
continue;
};
let display = match &target {
ConnectionTarget::Loose(port) => {
if !connected.insert(port.name.as_str()) {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::PortMultiplyConnected {
port: port.name.clone(),
},
Some(component),
&connect.token,
));
continue;
}
port.name.clone()
}
ConnectionTarget::Member(grp, m) => {
connected_members.insert((grp.name.clone(), m.member.clone()));
port_name.clone()
}
};
let facts = ConnectionFacts {
input: connect.input,
drivable: connect.output.is_some(),
is_clock: connect.is_clock,
is_reset: connect.is_reset,
};
for violation in target.check(&facts) {
let kind = match violation {
ConnectionViolation::InvalidDirection(dir) => {
ComponentInterfaceMismatchKind::InvalidPortDirection {
port: display.clone(),
dir,
}
}
ConnectionViolation::NotInput => ComponentInterfaceMismatchKind::PortNotInput {
port: display.clone(),
},
ConnectionViolation::NotDrivable => {
ComponentInterfaceMismatchKind::PortNotDrivable {
port: display.clone(),
}
}
ConnectionViolation::NotClock => ComponentInterfaceMismatchKind::PortNotClock {
port: display.clone(),
},
ConnectionViolation::NotReset => ComponentInterfaceMismatchKind::PortNotReset {
port: display.clone(),
},
ConnectionViolation::ClockUndeclared => {
ComponentInterfaceMismatchKind::ClockPortUndeclared {
port: display.clone(),
}
}
ConnectionViolation::ResetUndeclared => {
ComponentInterfaceMismatchKind::ResetPortUndeclared {
port: display.clone(),
}
}
};
context.insert_error(AnalyzerError::component_interface_mismatch(
kind,
Some(component),
&connect.token,
));
}
}
let connected_groups: HashSet<String> =
group_sigs.iter().map(|gc| gc.group.to_string()).collect();
for port in &manifest.ports {
if connected.contains(port.name.as_str()) || written.contains(&port.name) {
continue;
}
let kind = match port.role.as_deref() {
Some(role) => ComponentInterfaceMismatchKind::RolePortUnconnected {
role: role.to_string(),
port: port.name.clone(),
},
None => ComponentInterfaceMismatchKind::PortUnconnected {
port: port.name.clone(),
},
};
context.insert_error(AnalyzerError::component_interface_mismatch(
kind,
Some(component),
&token,
));
}
for grp in &manifest.groups {
if !connected_groups.contains(&grp.name) {
if !written.contains(&grp.name) {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::GroupUnconnected {
group: grp.name.clone(),
},
Some(component),
&token,
));
}
continue;
}
for m in &grp.members {
if !connected_members.contains(&(grp.name.clone(), m.member.clone())) {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::GroupMemberUnconnected {
group: grp.name.clone(),
member: m.member.clone(),
},
Some(component),
&token,
));
}
}
}
let defining_project = component
.to_string()
.split_once("::")
.map(|(prj, _)| resource_table::insert_str(prj))
.or_else(|| {
context
.current_namespace()
.and_then(|ns| ns.paths.first().copied())
});
for grp in &manifest.groups {
let Some(gc) = group_sigs
.iter()
.find(|gc| gc.group.to_string() == grp.name)
else {
continue;
};
let path = SymbolPath::from(grp.interface.as_str());
let spn: SymbolPathNamespace = (&path, &Namespace::new()).into();
let mut want = symbol_table::resolve(&spn).ok();
if want.is_none()
&& let Some(prj) = defining_project
{
let mut ns = Namespace::new();
ns.push(prj);
let spn: SymbolPathNamespace = (&path, &ns).into();
want = symbol_table::resolve(&spn).ok();
}
if let Some(want) = want
&& want.found.id != gc.sig.symbol
{
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::InterfaceMismatch {
group: grp.name.clone(),
expected: grp.interface.clone(),
},
Some(component),
&gc.token,
));
}
if let Some(mp) = resource_table::get_str_value(gc.modport)
&& mp != grp.modport
{
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::ModportMismatch {
group: grp.name.clone(),
expected: grp.modport.clone(),
connected: mp,
},
Some(component),
&gc.token,
));
}
}
}
insert_component_params(context, name, ¶ms);
insert_group_params(context, name, component, &group_sigs, token);
Ok(ir::Declaration::External(Box::new(
ir::ExternalDeclaration {
name,
component,
params,
connects,
is_var_form: false,
token,
},
)))
}
fn conv_external_connect(
context: &mut Context,
item: &InstPortItem,
group_sigs: &mut Vec<GroupConnect>,
) -> IrResult<Vec<ir::ExternalConnect>> {
let port_raw = item.identifier.identifier_token.token.text;
let port = resource_table::canonical_str_id(port_raw);
let token: TokenRange = item.identifier.as_ref().into();
let (comptime, expr, dst_paths) = if let Some(ref x) = item.inst_port_item_opt {
let (comptime, expr) = eval_expr(context, None, &x.expression, false)?;
let dst: Vec<VarPathSelect> =
Conv::conv(context, x.expression.as_ref()).unwrap_or_default();
(comptime, expr, dst)
} else {
let port_path = VarPath::new(port_raw);
let Some((var_id, mut comptime)) = context.find_path(&port_path) else {
let name = resource_table::get_str_value(port_raw).unwrap_or_default();
context.insert_error(AnalyzerError::undefined_identifier(&name, &token));
return Ok(vec![]);
};
comptime.token = token;
let expr = ir::Expression::Term(Box::new(ir::Factor::Variable(
var_id,
VarIndex::default(),
VarSelect::default(),
comptime.clone(),
)));
let dst = vec![VarPathSelect(port_path, VarSelect::default(), token)];
(comptime, expr, dst)
};
match &comptime.r#type.kind {
ir::TypeKind::Modport(_, _) => {
return conv_external_modport_connect(
context, port, &comptime, dst_paths, token, group_sigs,
);
}
ir::TypeKind::Instance(_, kind) if *kind == ir::InstanceKind::Interface => {
context.insert_error(AnalyzerError::invalid_factor(
None,
"interface connection to a component port; connect a modport",
&token,
&[],
));
return Ok(vec![]);
}
_ => {}
}
if comptime.r#type.is_array() {
context.insert_error(AnalyzerError::invalid_factor(
None,
"array connection to a component port",
&token,
&[],
));
return Ok(vec![]);
}
let output = if dst_paths.len() == 1 {
var_path_to_assign_destination(context, dst_paths, true)
.into_iter()
.next()
} else {
None
};
let width = comptime.r#type.total_width().unwrap_or(0) as u32;
Ok(vec![ir::ExternalConnect {
port,
expr,
output,
input: true,
group: None,
member: None,
is_clock: comptime.r#type.is_clock(),
is_reset: comptime.r#type.is_reset(),
width,
token,
}])
}
fn conv_external_modport_connect(
context: &mut Context,
port: StrId,
comptime: &Comptime,
dst_paths: Vec<VarPathSelect>,
token: TokenRange,
group_sigs: &mut Vec<GroupConnect>,
) -> IrResult<Vec<ir::ExternalConnect>> {
let Some(dst) = dst_paths.first() else {
return Err(ir_error!(token));
};
let ir::TypeKind::Modport(sig, modport_name) = &comptime.r#type.kind else {
return Err(ir_error!(token));
};
if !dst.1.0.is_empty() || dst.1.1.is_some() {
context.insert_error(AnalyzerError::invalid_factor(
None,
"selected modport connection to a component port",
&token,
&[],
));
return Ok(vec![]);
}
group_sigs.push(GroupConnect {
group: port,
sig: sig.clone(),
modport: *modport_name,
token,
});
let mut dst_path = dst.0.clone();
if dst_path.0.last() == Some(modport_name) {
dst_path.0.pop();
}
let port_str = resource_table::get_str_value(port).unwrap_or_default();
let mut ret = vec![];
for (member_path, direction) in comptime.r#type.expand_modport(context, &dst_path, token)? {
if !direction.is_input() && !direction.is_output() {
continue;
}
let Some((var_id, mut member_comptime)) = context.find_path(&member_path) else {
context.insert_error(AnalyzerError::undefined_identifier(
&member_path.to_string(),
&token,
));
continue;
};
member_comptime.token = token;
let member_name: Vec<String> = member_path.0[dst_path.0.len()..]
.iter()
.map(|x| resource_table::canonical_str_id(*x).to_string())
.collect();
let member_name = member_name.join(".");
let port_name = resource_table::insert_str(&veryl_component_sys::member_port_name(
&port_str,
&member_name,
));
let member = resource_table::insert_str(&member_name);
let expr = ir::Expression::Term(Box::new(ir::Factor::Variable(
var_id,
VarIndex::default(),
VarSelect::default(),
member_comptime.clone(),
)));
let (output, input) = if direction.is_output() {
let dst = vec![VarPathSelect(member_path, VarSelect::default(), token)];
let output = var_path_to_assign_destination(context, dst, true)
.into_iter()
.next();
(output, false)
} else {
(None, true)
};
let width = member_comptime.r#type.total_width().unwrap_or(0) as u32;
ret.push(ir::ExternalConnect {
port: port_name,
expr,
output,
input,
group: Some(port),
member: Some(member),
is_clock: member_comptime.r#type.is_clock(),
is_reset: member_comptime.r#type.is_reset(),
width,
token,
});
}
Ok(ret)
}
impl Conv<&ConnectDeclaration> for ir::DeclarationBlock {
fn conv(context: &mut Context, value: &ConnectDeclaration) -> IrResult<Self> {
let token: TokenRange = value.into();
let lhs: VarPathSelect = Conv::conv(context, value.hierarchical_identifier.as_ref())?;
let rhs: Vec<VarPathSelect> = Conv::conv(context, value.expression.as_ref())?;
let (comptime, _) = eval_expr(context, None, value.expression.as_ref(), false)?;
let statements = if comptime.is_const {
expand_connect_const(context, lhs, comptime, token)?
} else {
if rhs.len() != 1 {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::ConnectMultipleExpression,
&token,
));
return Err(ir_error!(token));
}
let rhs = rhs[0].clone();
expand_connect(context, lhs, rhs, token)?
};
let ret = ir::CombDeclaration { statements };
let ret = ir::Declaration::Comb(ret);
Ok(ir::DeclarationBlock(vec![ret]))
}
}
impl Conv<&MixinDeclaration> for () {
fn conv(context: &mut Context, value: &MixinDeclaration) -> IrResult<Self> {
let token: TokenRange = value.scoped_identifier.as_ref().into();
let source_path: GenericSymbolPath = value.scoped_identifier.as_ref().into();
check_generic_refereence(context, &source_path);
let Some(mut sig) = Signature::from_path(context, source_path) else {
return Err(ir_error!(token));
};
sig.normalize();
let symbol = symbol_table::get(sig.symbol).unwrap();
if symbol.kind.has_mixin_sources() {
return Ok(());
}
match &symbol.kind {
SymbolKind::Interface(x) if !x.is_proto => {
mixin_interface(context, &symbol, &sig, token)
}
_ => Err(ir_error!(token)),
}
}
}
fn mixin_interface(
context: &mut Context,
symbol: &Symbol,
sig: &Signature,
token: TokenRange,
) -> IrResult<()> {
let definition = symbol
.kind
.get_definition()
.and_then(definition_table::get)
.ok_or_else(|| ir_error!(token))?;
let Definition::Interface(definition) = definition.as_ref() else {
unreachable!();
};
context.push_generic_map(sig.to_generic_map());
let ret: IrResult<()> = Conv::conv(context, definition);
context.pop_generic_map();
ret
}
impl Conv<&UnsafeBlock> for ir::DeclarationBlock {
fn conv(context: &mut Context, value: &UnsafeBlock) -> IrResult<Self> {
let mut ret = vec![];
for x in &value.unsafe_block_list {
let items: Vec<_> = x.generate_group.as_ref().into();
for item in items {
let item: IrResult<ir::DeclarationBlock> = Conv::conv(context, item);
if let Ok(mut item) = item {
ret.append(&mut item.0);
}
}
}
Ok(ir::DeclarationBlock(ret))
}
}
impl Conv<&ImportDeclaration> for ir::DeclarationBlock {
fn conv(context: &mut Context, value: &ImportDeclaration) -> IrResult<Self> {
check_import(context, value);
Ok(ir::DeclarationBlock::default())
}
}
impl Conv<&BindDeclaration> for () {
fn conv(context: &mut Context, value: &BindDeclaration) -> IrResult<Self> {
if let Ok(symbol) = symbol_table::resolve(value.scoped_identifier.as_ref()) {
if !check_bind_target(context, &value.scoped_identifier, &symbol.found) {
return Ok(());
}
let in_module = symbol.found.is_module(true);
let token = value.bind.bind_token.token;
check_inst(context, in_module, &token, &value.component_instantiation);
}
Ok(())
}
}
impl Conv<&EmbedDeclaration> for () {
fn conv(context: &mut Context, value: &EmbedDeclaration) -> IrResult<Self> {
for x in &value.embed_content.embed_content_list {
if let EmbedItem::EmbedScopedIdentifier(x) = x.embed_item.as_ref() {
let path = x.embed_scoped_identifier.scoped_identifier.as_ref();
let token: TokenRange = path.identifier().into();
let generic_path: GenericSymbolPath = path.into();
if let Ok(symbol) = symbol_table::resolve(path)
&& matches!(
symbol.found.kind,
SymbolKind::Module(_) | SymbolKind::Interface(_)
)
{
let sig = Signature::from_path(context, generic_path)
.ok_or_else(|| ir_error!(token))?;
let _component = context.block(|c| get_component(c, &sig, token));
}
}
}
Ok(())
}
}
impl Conv<&AliasDeclaration> for () {
fn conv(context: &mut Context, value: &AliasDeclaration) -> IrResult<Self> {
let r#type = match value.alias_declaration_group.as_ref() {
AliasDeclarationGroup::Module(_) => AliasType::Module,
AliasDeclarationGroup::Interface(_) => AliasType::Interface,
AliasDeclarationGroup::Package(_) => AliasType::Package,
};
check_alias_target(context, &value.scoped_identifier, r#type);
Ok(())
}
}
impl Conv<&TypeDefDeclaration> for () {
fn conv(context: &mut Context, value: &TypeDefDeclaration) -> IrResult<Self> {
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::TypeDef(x) = &symbol.found.kind
&& !x.is_proto
&& let Some(r#type) = &x.r#type
{
r#type.to_ir_type(context, TypePosition::TypeDef)?;
}
Ok(())
}
}
impl Conv<&ModportDeclaration> for () {
fn conv(context: &mut Context, value: &ModportDeclaration) -> IrResult<Self> {
context.push_affiliation(Affiliation::Modport);
let ret = context.block(|c| {
if let Some(x) = &value.modport_declaration_opt {
let items: Vec<_> = x.modport_list.as_ref().into();
for item in &items {
check_modport(c, item);
check_direction(c, &item.direction);
}
}
if let Some(x) = &value.modport_declaration_opt0 {
check_modport_default(c, x.modport_default.as_ref(), value.identifier.text());
}
if let Ok(symbol) = symbol_table::resolve(value.identifier.as_ref())
&& let SymbolKind::Modport(x) = &symbol.found.kind
{
let sig = if let Some(x) = c.get_current_signature() {
x.clone()
} else {
Signature::new(x.interface)
};
let name = value.identifier.text();
let path = VarPath::new(name);
let r#type =
ir::Type::new(ir::TypeKind::Modport(sig.into(), symbol.found.token.text));
let token: TokenRange = value.identifier.as_ref().into();
let comptime = Comptime::from_type(r#type, ClockDomain::None, token);
c.insert_var_path(path, comptime);
let mut members = vec![];
for x in &x.members {
let symbol = symbol_table::get(*x).unwrap();
match &symbol.kind {
SymbolKind::ModportVariableMember(x) => {
members.push((symbol.token.text, x.direction));
}
SymbolKind::ModportFunctionMember(_) => {
members.push((symbol.token.text, Direction::Import));
}
_ => (),
}
}
c.insert_modport(name, members);
}
Ok(())
});
context.pop_affiliation();
ret
}
}