use crate::analyzer_error::{
AnalyzerError, ComponentInterfaceMismatchKind, ExceedLimitKind, InvalidForRangeKind,
InvalidForStepKind, MismatchTypeKind, MultipleDefaultKind, UnevaluableValueKind,
};
use crate::conv::checker::anonymous::check_anonymous;
use crate::conv::checker::clock_domain::check_clock_domain;
use crate::conv::checker::generic::check_generic_refereence;
use crate::conv::checker::portability::check_initial_assign_system_function_args;
use crate::conv::context::RuntimeFunctionEffect;
use crate::conv::instance::InstanceHistoryError;
use crate::conv::{Context, Conv};
use crate::definition_table::{self, Definition, DefinitionId};
use crate::ir::{
self, Arguments, Comptime, FuncPath, FuncProto, IrResult, Op, PartSelectPath, Shape, ShapeRef,
Signature, SystemFunctionInput, TbMethod, TbMethodCall, ValueVariant, VarIndex, VarKind,
VarPath, VarPathSelect, VarSelect, Variable,
};
use crate::symbol::{
self, Affiliation, ClockDomain, EnumMemberValue, GenericBoundKind, GenericMap, ProtoBound,
Symbol, SymbolKind, TbComponentKind, TypeKind,
};
use crate::symbol_path::{
GenericSymbolPath, GenericSymbolPathKind, SymbolPath, SymbolPathNamespace,
};
use crate::symbol_table::{self, ResolveResult};
use crate::value::Value;
use crate::{HashMap, HashSet, ir_error};
use std::rc::Rc;
use std::sync::Arc;
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;
pub fn is_inferable_expression(expr: &Expression) -> bool {
let if_expr = &*expr.if_expression;
if !if_expr.if_expression_list.is_empty() {
return false;
}
let e01 = &*if_expr.expression01;
if !e01.expression01_list.is_empty() {
return false;
}
let e02 = &*e01.expression02;
if !e02.expression02_list.is_empty() || e02.expression02_opt.is_some() {
return false;
}
is_inferable_factor(&e02.factor)
}
fn is_inferable_factor(factor: &Factor) -> bool {
match factor {
Factor::IdentifierFactor(_) => true,
Factor::BooleanLiteral(_) => true,
Factor::LParenExpressionRParen(x) => is_inferable_expression(&x.expression),
Factor::Number(x) => match x.number.as_ref() {
Number::IntegralNumber(x) => match x.integral_number.as_ref() {
IntegralNumber::Based(_) => true,
IntegralNumber::BaseLess(_) | IntegralNumber::AllBit(_) => false,
},
Number::RealNumber(_) => true,
},
_ => false,
}
}
pub fn try_infer_var_assign(
context: &mut Context,
dst_symbol: &Symbol,
expr: &Expression,
token: TokenRange,
) -> IrResult<Option<(Comptime, ir::Expression)>> {
let SymbolKind::Variable(var_prop) = &dst_symbol.kind else {
return Ok(None);
};
if !var_prop.r#type.is_inferred() {
return Ok(None);
}
if !is_inferable_expression(expr) {
context.insert_error(AnalyzerError::type_inference_not_supported(&expr.into()));
return Err(ir_error!(token));
}
let (comptime, ir_expr) = eval_expr(context, None, expr, false)?;
let inferred = comptime.r#type.clone();
check_inferred_type_emittable(context, &inferred, expr, token)?;
if let Some(existing) = crate::resolved_type_table::get(&dst_symbol.token.id) {
if existing != inferred {
context.insert_error(AnalyzerError::type_inference_conflict(
&dst_symbol.token.text.to_string(),
&expr.into(),
));
return Err(ir_error!(token));
}
} else {
crate::resolved_type_table::insert(dst_symbol.token.id, inferred.clone());
let path = VarPath::new(dst_symbol.token.text);
if let Some((id, mut ct)) = context.find_path(&path) {
ct.r#type = inferred.clone();
context.insert_var_path_with_id(path, id, ct);
}
}
Ok(Some((comptime, ir_expr)))
}
pub fn try_infer_decl_type(
context: &mut Context,
sym_type: &symbol::Type,
expr: &Expression,
decl_token_id: veryl_parser::resource_table::TokenId,
token: TokenRange,
) -> IrResult<Option<(Comptime, ir::Expression)>> {
if !sym_type.is_inferred() {
return Ok(None);
}
if !is_inferable_expression(expr) {
context.insert_error(AnalyzerError::type_inference_not_supported(&expr.into()));
return Err(ir_error!(token));
}
let (comptime, ir_expr) = eval_expr(context, None, expr, false)?;
check_inferred_type_emittable(context, &comptime.r#type, expr, token)?;
crate::resolved_type_table::insert(decl_token_id, comptime.r#type.clone());
Ok(Some((comptime, ir_expr)))
}
fn check_inferred_type_emittable(
context: &mut Context,
r#type: &ir::Type,
expr: &Expression,
token: TokenRange,
) -> IrResult<()> {
if matches!(
r#type.kind,
ir::TypeKind::Struct(_) | ir::TypeKind::Union(_) | ir::TypeKind::Enum(_)
) {
context.insert_error(AnalyzerError::type_inference_not_supported(&expr.into()));
return Err(ir_error!(token));
}
Ok(())
}
fn format_positive_type_name(r#type: &ir::Type) -> Option<String> {
if !r#type.is_positive {
return None;
}
if let Some(width) = r#type.width().first()
&& let Some(w) = width
{
return Some(match *w {
8 => "p8".to_string(),
16 => "p16".to_string(),
32 => "p32".to_string(),
64 => "p64".to_string(),
_ => r#type.to_string(),
});
}
Some(r#type.to_string())
}
pub fn eval_expr(
context: &mut Context,
dst_type: Option<ir::Type>,
expr: &Expression,
allow_anonymous: bool,
) -> IrResult<(Comptime, ir::Expression)> {
let token: TokenRange = expr.into();
let expr: IrResult<ir::Expression> = Conv::conv(context, expr);
if let Ok(mut expr) = expr {
check_anonymous(context, &expr, allow_anonymous, token);
let mut comptime = if let Some(dst_type) = dst_type {
let mut comptime = expr.eval_comptime(context, dst_type.total_width()).clone();
check_compatibility(context, &dst_type, &comptime, &token);
comptime.r#type = dst_type;
comptime
} else {
expr.eval_comptime(context, None).clone()
};
if matches!(expr, ir::Expression::ArrayLiteral(..))
&& !comptime.r#type.array.is_empty()
&& let Some(values) = eval_array_literal_values(context, &comptime.r#type, &mut expr)?
{
comptime.value = ValueVariant::NumericArray(values);
}
Ok((comptime, expr))
} else {
let comptime = Comptime::create_unknown(token);
let expr = ir::Expression::Term(Box::new(ir::Factor::Unknown(comptime.clone())));
Ok((comptime, expr))
}
}
pub fn eval_generic_expr(
context: &mut Context,
expr: &Expression,
) -> IrResult<(Comptime, ir::Expression)> {
let allow_component_as_factor = context.allow_component_as_factor;
context.allow_component_as_factor = true;
let ret = eval_expr(context, None, expr, false);
context.allow_component_as_factor = allow_component_as_factor;
ret
}
pub fn eval_range(
context: &mut Context,
range: &Range,
) -> IrResult<(ir::ForBound, ir::ForBound, bool)> {
eval_range_inner(context, range, false)
}
pub fn eval_range_const(
context: &mut Context,
range: &Range,
) -> IrResult<(ir::ForBound, ir::ForBound, bool)> {
eval_range_inner(context, range, true)
}
fn eval_range_inner(
context: &mut Context,
range: &Range,
require_const: bool,
) -> IrResult<(ir::ForBound, ir::ForBound, bool)> {
let mut beg: ir::Expression = Conv::conv(context, range.expression.as_ref())?;
let beg_comptime = beg.eval_comptime(context, None);
if require_const && !beg_comptime.is_const {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::ForRange,
&range.into(),
));
}
let beg = if beg_comptime.is_const {
let value = beg_comptime.get_value()?;
if value.is_semantically_not_positive() && value.is_positive() {
let token: TokenRange = range.into();
context.insert_error(AnalyzerError::invalid_for_range(
InvalidForRangeKind::NegativeBound,
&token,
));
return Err(ir_error!(token));
}
let val = value.to_usize().unwrap_or(0);
ir::ForBound::Const(val)
} else {
ir::ForBound::Expression(Box::new(beg))
};
let (end, inclusive) = if let Some(x) = &range.range_opt {
let mut end: ir::Expression = Conv::conv(context, x.expression.as_ref())?;
let end_comptime = end.eval_comptime(context, None);
if require_const && !end_comptime.is_const {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::ForRange,
&range.into(),
));
}
let end = if end_comptime.is_const {
let value = end_comptime.get_value()?;
if value.is_semantically_not_positive() && value.is_positive() {
let token: TokenRange = range.into();
context.insert_error(AnalyzerError::invalid_for_range(
InvalidForRangeKind::NegativeBound,
&token,
));
return Err(ir_error!(token));
}
let val = value.to_usize().unwrap_or(0);
ir::ForBound::Const(val)
} else {
ir::ForBound::Expression(Box::new(end))
};
if matches!(x.range_operator.as_ref(), RangeOperator::DotDotEqu(_)) {
(end, true)
} else {
(end, false)
}
} else {
(beg.clone(), false)
};
Ok((beg, end, inclusive))
}
#[derive(Clone)]
pub struct ArrayLiteralExpression {
pub index: Vec<usize>,
pub select: Vec<usize>,
pub expr: ir::Expression,
}
impl ArrayLiteralExpression {
pub fn to_var_index(&self) -> VarIndex {
let mut ret = vec![];
for i in &self.index {
let expr = ir::Expression::create_value(
Value::new(*i as u64, 32, false),
TokenRange::default(),
);
ret.push(expr);
}
VarIndex(ret)
}
pub fn to_var_select(&self) -> VarSelect {
let mut ret = vec![];
for i in &self.select {
let expr = ir::Expression::create_value(
Value::new(*i as u64, 32, false),
TokenRange::default(),
);
ret.push(expr);
}
VarSelect(ret, None)
}
}
fn eval_const_array(
context_array: Option<&ShapeRef>,
expr: &ir::Expression,
) -> Option<Vec<ArrayLiteralExpression>> {
let array = context_array?;
if array.is_empty() {
return None;
}
let ir::Expression::Term(factor) = expr else {
return None;
};
let ir::Factor::Value(comptime) = factor.as_ref() else {
return None;
};
let ValueVariant::NumericArray(values) = &comptime.value else {
return None;
};
if comptime.r#type.array.as_slice() != array.as_slice() {
return None;
}
let dims: Vec<usize> = array.iter().copied().collect::<Option<_>>()?;
if dims.contains(&0) || array.total()? != values.len() {
return None;
}
let mut element = comptime.clone();
element.r#type.array = Shape::default();
element.value = ValueVariant::Unknown;
let ret = values
.iter()
.enumerate()
.map(|(flat, value)| {
let mut index = vec![0; dims.len()];
let mut rest = flat;
for (i, dim) in dims.iter().enumerate().rev() {
index[i] = rest % dim;
rest /= dim;
}
let mut comptime = element.clone();
comptime.value = ValueVariant::Numeric(value.clone());
ArrayLiteralExpression {
index,
select: vec![],
expr: ir::Expression::Term(Box::new(ir::Factor::Value(comptime))),
}
})
.collect();
Some(ret)
}
pub fn eval_array_literal(
context: &mut Context,
context_array: Option<&ShapeRef>,
context_width: Option<&ShapeRef>,
expr: &mut ir::Expression,
) -> IrResult<Option<Vec<ArrayLiteralExpression>>> {
let token = expr.token_range();
if let Some(x) = eval_const_array(context_array, expr) {
return Ok(Some(x));
}
let ir::Expression::ArrayLiteral(items, _) = expr else {
return Ok(None);
};
let (is_array, context_array_width) = if let Some(x) = context_array
&& !x.is_empty()
{
(true, context_array)
} else {
(false, context_width)
};
let mut ret = vec![];
if let Some(array_width) = context_array_width {
let mut default = None;
let mut len = 0;
let mut index = 0;
for item in items {
let next_array_width: Option<&ShapeRef> = if array_width.dims() < 2 {
None
} else {
Some(array_width[1..].into())
};
let (next_array, next_width) = if is_array {
(next_array_width, context_width)
} else {
(None, next_array_width)
};
match item {
ir::ArrayLiteralItem::Value(expr, repeat) => {
let repeat = if let Some(repeat) = repeat {
let repeat =
eval_repeat(context, repeat).ok_or_else(|| ir_error!(token))?;
repeat.to_usize().unwrap_or(0)
} else {
1
};
let exprs = if let Some(x) =
eval_array_literal(context, next_array, next_width, expr)?
{
x
} else {
vec![ArrayLiteralExpression {
index: vec![],
select: vec![],
expr: expr.as_ref().clone(),
}]
};
for _ in 0..repeat {
let mut exprs = exprs.clone();
for expr in &mut exprs {
if is_array {
expr.index.insert(0, index);
} else {
expr.select.insert(0, index);
}
}
ret.append(&mut exprs);
index += 1;
}
len += repeat;
}
ir::ArrayLiteralItem::Defaul(expr) => {
let exprs = if let Some(x) =
eval_array_literal(context, next_array, next_width, expr)?
{
x
} else {
vec![ArrayLiteralExpression {
index: vec![],
select: vec![],
expr: expr.as_ref().clone(),
}]
};
if default.is_none() {
default = Some(exprs);
} else {
context.insert_error(AnalyzerError::multiple_default(
MultipleDefaultKind::ArrayLiteral,
"default",
&token,
));
return Err(ir_error!(token));
}
}
}
}
if let Some(Some(target_len)) = array_width.get(0) {
if let Some(x) = default {
let remaining = target_len.checked_sub(len);
if let Some(remaining) = remaining {
for _ in 0..remaining {
let mut exprs = x.clone();
for expr in &mut exprs {
if is_array {
expr.index.insert(0, index);
} else {
expr.select.insert(0, index);
}
}
ret.append(&mut exprs);
index += 1;
}
} else {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::ArrayDimension {
expected: *target_len,
actual: len,
},
&token,
));
return Err(ir_error!(token));
}
} else if *target_len != len {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::ArrayDimension {
expected: *target_len,
actual: len,
},
&token,
));
return Err(ir_error!(token));
}
} else {
return Err(ir_error!(token));
}
} else {
return Err(ir_error!(token));
}
Ok(Some(ret))
}
pub fn eval_repeat(context: &mut Context, expr: &mut ir::Expression) -> Option<Value> {
let token = expr.token_range();
let repeat = expr.eval_comptime(context, None);
if repeat.r#type.is_array() | repeat.r#type.is_type() {
context.insert_error(AnalyzerError::invalid_operand(
&repeat.r#type.to_string(),
&Op::Repeat.to_string(),
&token,
));
return None;
}
if !repeat.is_const {
context.insert_error(AnalyzerError::invalid_operand(
"non const value",
&Op::Repeat.to_string(),
&token,
));
return None;
}
match &repeat.value {
ValueVariant::Numeric(repeat) => Some(repeat.clone()),
ValueVariant::NumericArray(_) | ValueVariant::Type(_) => {
context.insert_error(AnalyzerError::invalid_operand(
&repeat.r#type.to_string(),
&Op::Repeat.to_string(),
&token,
));
None
}
ValueVariant::Unknown => None,
}
}
pub fn eval_size(
context: &mut Context,
expr: &Expression,
allow_inferable_size: bool,
) -> IrResult<(Comptime, Option<usize>)> {
let (comptime, expr) = eval_expr(context, None, expr, allow_inferable_size)?;
if comptime.r#type.is_type() {
let token = expr.token_range();
context.insert_error(AnalyzerError::invalid_size_type(&token));
return Err(ir_error!(token));
}
if let Ok(x) = comptime.get_value() {
let value = x.to_usize().unwrap_or(0);
let value = context.check_size(value, expr.token_range());
if value == Some(0) {
Ok((comptime, None))
} else {
Ok((comptime, value))
}
} else {
Ok((comptime, None))
}
}
pub fn check_assign_before_definition<T: Into<SymbolPathNamespace>>(
context: &mut Context,
identifier: T,
token: Token,
) {
if let Ok(symbol) = symbol_table::resolve(identifier)
&& let SymbolKind::Variable(x) = &symbol.found.kind
&& x.affiliation == Affiliation::Module
&& context
.find_path(&VarPath::new(symbol.found.token.text))
.is_none()
{
context.insert_error(AnalyzerError::referring_before_definition(
&token.text.to_string(),
&token.into(),
));
}
}
pub fn check_assign_clock_domain(
context: &mut Context,
dst: &mut ir::AssignDestination,
comptime: &ir::Comptime,
token: &TokenRange,
) {
if (dst.comptime.r#type.is_clock() || dst.comptime.r#type.is_reset())
&& context.is_affiliated(Affiliation::AlwaysFf)
{
context.insert_error(AnalyzerError::invalid_clock_assignment(
&dst.comptime.r#type.to_string(),
token,
));
}
if dst.comptime.clock_domain == ClockDomain::Implicit {
let inferred = if context.is_affiliated(Affiliation::AlwaysFf) {
context
.current_clock
.as_ref()
.and_then(|c| c.clock_domain.domain_id())
} else {
comptime.clock_domain.domain_id()
};
if let Some(id) = inferred {
dst.comptime.clock_domain = ClockDomain::Inferred(id);
if let Some((_, path_comptime)) = context.var_paths.get_mut(&dst.path) {
path_comptime.clock_domain = ClockDomain::Inferred(id);
}
}
}
let mut dst_comptime_for_cdc = dst.comptime.clone();
dst_comptime_for_cdc.token = dst.token;
check_clock_domain(context, &dst_comptime_for_cdc, comptime, &token.beg);
if context.is_affiliated(Affiliation::AlwaysFf)
&& let Some(clock) = context.current_clock.clone()
{
check_clock_domain(context, &dst_comptime_for_cdc, &clock, &token.beg);
}
for cond in context.condition_domains.clone() {
check_clock_domain(context, &dst_comptime_for_cdc, &cond, &token.beg);
}
}
pub fn eval_assign_statement(
context: &mut Context,
dst: &mut ir::AssignDestination,
expr: &mut (ir::Comptime, ir::Expression),
token: TokenRange,
) -> IrResult<Vec<ir::Statement>> {
let (comptime, expr) = expr;
check_assign_clock_domain(context, dst, comptime, &token);
let width = dst.total_width(context);
let mut ret = vec![];
let array_exprs = eval_array_literal(
context,
Some(&dst.comptime.r#type.array),
Some(dst.comptime.r#type.width()),
expr,
)?;
if let Some(exprs) = array_exprs {
for mut expr in exprs {
check_reset_non_elaborative(context, &mut expr.expr);
let index = expr.to_var_index();
let select = expr.to_var_select();
let mut dst = dst.clone();
dst.index.append(&index);
dst.select = select;
let statement = ir::Statement::Assign(ir::AssignStatement {
dst: vec![dst],
width,
expr: expr.expr,
token,
});
ret.push(statement);
}
} else {
check_reset_non_elaborative(context, expr);
let statement = ir::Statement::Assign(ir::AssignStatement {
dst: vec![dst.clone()],
width,
expr: expr.clone(),
token,
});
ret.push(statement);
}
Ok(ret)
}
pub fn assign_rhs_context_type(context: &mut Context, dst: &ir::AssignDestination) -> ir::Type {
let full = dst.comptime.r#type.clone();
if dst.select.is_empty() || dst.comptime.part_select.is_some() {
return full;
}
if !matches!(full.kind, ir::TypeKind::Logic | ir::TypeKind::Bit) {
return full;
}
let Some(w) = dst.total_width(context) else {
return full;
};
if w == 0 || full.total_width().is_none_or(|fw| w >= fw) {
return full;
}
let mut t = full;
t.set_concrete_width(Shape::new(vec![Some(w)]));
t
}
pub fn eval_array_range_assign(
context: &mut Context,
lhs: &VarPathSelect,
rhs: &Expression,
token: TokenRange,
) -> IrResult<Option<Vec<ir::Statement>>> {
let (_, select, _) = lhs.clone().into();
let Some((_, mut comptime)) = context.find_path(&lhs.0) else {
return Ok(None);
};
if let Some(part_select) = &comptime.part_select {
comptime.r#type = part_select.base.clone();
}
let (array_select, _) = select.split(comptime.r#type.array.dims());
if !array_select.is_range() {
return Ok(None);
}
let dsts = lhs.clone().to_assign_destinations(context, false);
if dsts.is_empty() {
let _ = eval_expr(context, None, rhs, false);
return Ok(Some(vec![]));
}
let n = dsts.len();
let elem_type = dsts[0].comptime.r#type.clone();
let mut sub_type = elem_type.clone();
let mut dims = vec![Some(n)];
dims.extend(elem_type.array.iter().copied());
sub_type.array = Shape::new(dims);
let (rhs_comptime, mut rhs_expr) = eval_expr(context, Some(sub_type), rhs, false)?;
let ir::Expression::ArrayLiteral(items, _) = &mut rhs_expr else {
context.insert_error(AnalyzerError::invalid_range_assign(&token));
return Ok(Some(vec![]));
};
let mut outer: Vec<ir::Expression> = Vec::new();
let mut default: Option<ir::Expression> = None;
let mut repeat_unresolved = false;
for item in items.iter_mut() {
match item {
ir::ArrayLiteralItem::Value(expr, repeat) => {
let count = if let Some(repeat) = repeat {
match eval_repeat(context, repeat).and_then(|v| v.to_usize()) {
Some(count) => count,
None => {
repeat_unresolved = true;
0
}
}
} else {
1
};
for _ in 0..count {
outer.push(expr.as_ref().clone());
}
}
ir::ArrayLiteralItem::Defaul(expr) => {
if default.is_some() {
context.insert_error(AnalyzerError::multiple_default(
MultipleDefaultKind::ArrayLiteral,
"default",
&token,
));
return Ok(Some(vec![]));
}
default = Some(expr.as_ref().clone());
}
}
}
if let Some(def) = default {
while outer.len() < n {
outer.push(def.clone());
}
}
if outer.len() != n && !repeat_unresolved {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::ArrayDimension {
expected: n,
actual: outer.len(),
},
&token,
));
return Ok(Some(vec![]));
}
let mut statements = vec![];
for (mut dst, sub_expr) in dsts.into_iter().zip(outer) {
let mut expr = (rhs_comptime.clone(), sub_expr);
statements.extend(eval_assign_statement(context, &mut dst, &mut expr, token)?);
}
Ok(Some(statements))
}
fn eval_array_literal_expressions(
context: &mut Context,
r#type: &ir::Type,
exprs: Vec<ArrayLiteralExpression>,
token: TokenRange,
) -> IrResult<Vec<Value>> {
let mut ret = vec![];
let mut value: Option<Value> = None;
let mut prev = None;
for expr in exprs {
if prev != Some(expr.index.clone())
&& let Some(x) = value
{
ret.push(x);
value = None;
}
let mut part_type = r#type.clone();
part_type.width_mut().drain(0..expr.select.len());
if let Some(mut part_value) = expr.expr.eval_value(context) {
let part_width = part_type.total_width().ok_or_else(|| ir_error!(token))?;
part_value.trunc(part_width);
value = if let Some(x) = value {
Some(x.concat(&part_value))
} else {
Some(part_value)
};
} else {
value = None;
}
prev = Some(expr.index);
}
if let Some(x) = value {
ret.push(x);
}
Ok(ret)
}
fn eval_array_literal_values(
context: &mut Context,
r#type: &ir::Type,
expr: &mut ir::Expression,
) -> IrResult<Option<Vec<Value>>> {
let token = expr.token_range();
let Some(exprs) = eval_array_literal(context, Some(&r#type.array), Some(r#type.width()), expr)?
else {
return Ok(None);
};
Ok(Some(eval_array_literal_expressions(
context, r#type, exprs, token,
)?))
}
fn insert_const_variable(
context: &mut Context,
dst: &ir::AssignDestination,
kind: VarKind,
r#type: &ir::Type,
comptime: Comptime,
values: Vec<Value>,
) {
let id = context.insert_var_path(dst.path.clone(), comptime);
let array_limit = context.config.evaluate_array_limit;
let variable = Variable::new(
id,
dst.path.clone(),
kind,
r#type.clone(),
values,
context.get_affiliation(),
&dst.token,
array_limit,
);
context.insert_variable(id, variable);
}
fn fit_array_elements(mut values: Vec<Value>, r#type: &ir::Type) -> Vec<Value> {
if let Some(total_width) = r#type.total_width() {
for value in &mut values {
if value.width() > total_width {
value.trunc(total_width);
} else if value.width() < total_width {
*value = value.expand(total_width, value.signed()).into_owned();
}
value.set_signed(r#type.signed);
}
}
values
}
pub fn eval_const_assign(
context: &mut Context,
kind: VarKind,
dst: &ir::AssignDestination,
expr: &mut (ir::Comptime, ir::Expression),
) -> IrResult<()> {
let (comptime, expr) = expr;
if comptime.value.is_unknown()
&& comptime.is_const
&& let Some(value) = expr.eval_value(context)
{
comptime.value = ValueVariant::Numeric(value);
}
let comptime = comptime.clone();
let path = &dst.path;
let r#type = &dst.comptime.r#type;
let token = expr.token_range();
match expr {
ir::Expression::ArrayLiteral(_, _)
if !matches!(comptime.value, ValueVariant::NumericArray(_)) =>
{
let Some(values) = eval_array_literal_values(context, r#type, expr)? else {
return Err(ir_error!(token));
};
insert_const_variable(context, dst, kind, r#type, comptime, values);
}
_ => {
match &comptime.value {
ValueVariant::Numeric(value) => {
let id = context.insert_var_path(path.clone(), comptime.clone());
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, ClockDomain::None, token);
comptime.is_const = true;
let path = x.path.clone();
comptime.part_select = Some(x);
context.insert_var_path_with_id(path, id, comptime);
}
let mut value = value.clone();
if !comptime.r#type.is_string() {
let total_width = comptime
.r#type
.total_width()
.ok_or_else(|| ir_error!(token))?;
if value.width() < total_width && value.width() != 0 {
value = value.expand(total_width, value.signed()).into_owned();
}
value.trunc(total_width);
value.set_signed(r#type.signed);
}
let array_limit = context.config.evaluate_array_limit;
let variable = Variable::new(
id,
path.clone(),
kind,
r#type.clone(),
vec![value],
context.get_affiliation(),
&dst.token,
array_limit,
);
context.insert_variable(id, variable);
}
ValueVariant::NumericArray(values) => {
let values = fit_array_elements(values.clone(), r#type);
insert_const_variable(context, dst, kind, r#type, comptime, values);
}
ValueVariant::Type(x) => {
let mut comptime = comptime.clone();
comptime.value = ValueVariant::Type(x.clone());
context.insert_var_path(path.clone(), comptime);
}
ValueVariant::Unknown => {
context.insert_var_path(path.clone(), comptime);
}
}
}
}
Ok(())
}
pub fn eval_variable(
context: &mut Context,
path: &VarPath,
kind: VarKind,
r#type: &ir::Type,
clock_domain: ClockDomain,
token: TokenRange,
) {
let comptime = Comptime::from_type(r#type.clone(), clock_domain, token);
let signed = comptime.r#type.signed;
let id = context.insert_var_path(path.clone(), comptime);
let values = if let Some(total_width) = r#type.total_width() {
vec![Value::new_x(total_width, signed)]
} else {
vec![]
};
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, 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.clone(),
kind,
r#type.clone(),
values,
context.get_affiliation(),
&token,
array_limit,
);
context.insert_variable(id, variable);
}
fn check_reset_non_elaborative(context: &mut Context, expr: &mut ir::Expression) {
let comptime = expr.eval_comptime(context, None);
if context.in_if_reset && !comptime.is_const {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::ResetValue,
&comptime.token,
));
}
}
pub fn eval_struct_member(
context: &mut Context,
member_symbol: &Symbol,
path: &GenericSymbolPath,
mut member_path: VarPath,
token: TokenRange,
) -> IrResult<ir::Factor> {
fn get_member_type(context: &mut Context, member_symbol: &Symbol) -> IrResult<ir::Type> {
let SymbolKind::StructMember(member) = &member_symbol.kind else {
unreachable!();
};
member.r#type.to_ir_type(context, TypePosition::Variable)
}
let mut parent_path = path.clone();
if let Some(x) = parent_path.paths.pop() {
member_path.add_prelude(&[x.base.text]);
}
if let Ok(symbol) = symbol_table::resolve(&parent_path) {
match &symbol.found.kind {
SymbolKind::Parameter(x) if !x.is_proto => {
if let Some(expr) = &x.value {
let path = VarPath::new(symbol.found.token.text);
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
let (_, mut expr) = eval_expr(context, Some(r#type.clone()), expr, false)?;
member_path.add_prelude(&path.0);
for x in r#type.expand_struct_union(&path, &[], None) {
if x.path == member_path {
let mut comptime = expr.eval_comptime(context, None).clone();
comptime.token = token;
if let ValueVariant::Numeric(ref full_value) = comptime.value.clone() {
let end: usize = x.part_select.iter().map(|ps| ps.pos).sum();
if let Some(width) =
x.part_select.last().and_then(|ps| ps.r#type.total_width())
{
let beg = end + width - 1;
comptime.value =
ValueVariant::Numeric(full_value.select(beg, end));
}
}
comptime.r#type = get_member_type(context, member_symbol)?;
return Ok(ir::Factor::Value(comptime));
}
}
}
Err(ir_error!(token))
}
SymbolKind::Parameter(x) if x.is_proto => {
let path = VarPath::new(symbol.found.token.text);
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
member_path.add_prelude(&path.0);
for x in r#type.expand_struct_union(&path, &[], None) {
if x.path == member_path {
let member_type = get_member_type(context, member_symbol)?;
let mut comptime =
Comptime::from_type(member_type, ClockDomain::None, token);
comptime.is_const = true;
return Ok(ir::Factor::Value(comptime));
}
}
Err(ir_error!(token))
}
SymbolKind::GenericParameter(x) => match &x.bound {
GenericBoundKind::Type => {
let mut comptime = Comptime::create_unknown(token);
comptime.is_const = true;
Ok(ir::Factor::Value(comptime))
}
GenericBoundKind::Proto(x) => {
let r#type = x.to_ir_type(context, TypePosition::Variable)?;
let mut comptime = Comptime::from_type(r#type, ClockDomain::None, token);
comptime.is_const = true;
Ok(ir::Factor::Value(comptime))
}
_ => Err(ir_error!(token)),
},
SymbolKind::StructMember(_) => {
eval_struct_member(context, member_symbol, &parent_path, member_path, token)
}
_ => Err(ir_error!(token)),
}
} else {
Err(ir_error!(token))
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TypePosition {
Variable,
Modport,
Cast,
Enum,
Generic,
TypeDef,
}
pub fn eval_type(
context: &mut Context,
path: &GenericSymbolPath,
pos: TypePosition,
) -> IrResult<ir::Type> {
if path.is_anonymous() {
let token: TokenRange = path.range;
context.insert_error(AnalyzerError::anonymous_identifier_usage(&token));
return Err(ir_error!(token));
}
let mut width = Shape::default();
let mut array = Shape::default();
let mut signed = false;
let mut is_positive = false;
let resolved = if matches!(pos, TypePosition::Variable) {
let resolved = context.resolve_path(path.clone());
if let Ok(symbol) = symbol_table::resolve(&resolved)
&& !symbol.found.is_variable_type()
{
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::SymbolKind {
name: symbol.found.token.to_string(),
expected: "enum, union, struct or typedef".to_string(),
actual: symbol.found.kind.to_kind_name(),
},
&path.range,
));
}
Some(resolved)
} else {
None
};
let kind = if let Some(x) = path.to_var_path()
&& let Some(x) = context.var_paths.get(&x)
{
match &x.1.value {
ValueVariant::Type(x) => {
let mut x = x.clone();
width.append(x.width_mut());
array.append(&mut x.array);
x.kind
}
ValueVariant::Numeric(x) => {
let value = x.to_usize().unwrap_or(0);
let value = context.check_size(value, path.paths[0].base.into());
width.push(value);
ir::TypeKind::Bit
}
_ => ir::TypeKind::Unknown,
}
} else {
let mut path = resolved.unwrap_or_else(|| context.resolve_path(path.clone()));
check_generic_refereence(context, &path);
let map = path.to_generic_maps();
if let Ok(symbol) = symbol_table::resolve(&path) {
if matches!(pos, TypePosition::Cast) && !symbol.found.is_casting_type() {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::SymbolKind {
name: symbol.found.token.to_string(),
expected: "enum, union, struct, typedef or integer constant".to_string(),
actual: symbol.found.kind.to_kind_name(),
},
&path.range,
));
}
match &symbol.found.kind {
SymbolKind::Struct(x) => {
context.push_generic_map(map.clone());
let members = context.block(|c| {
let mut members = vec![];
for x in &x.members {
let member = symbol_table::get(*x).unwrap();
let name = member.token.text;
if let SymbolKind::StructMember(x) = member.kind {
if symbol.found.token.text == x.r#type.token.beg.text {
continue;
}
let r#type = x.r#type.to_ir_type(c, TypePosition::Variable)?;
members.push(ir::TypeKindMember { name, r#type });
}
}
check_struct_union_members(c, &members, &symbol.found);
Ok(members)
});
context.pop_generic_map();
ir::TypeKind::Struct(Arc::new(ir::TypeKindStruct {
id: symbol.found.id,
members: members?,
}))
}
SymbolKind::Union(x) => {
context.push_generic_map(map.clone());
let members = context.block(|c| {
let mut members = vec![];
for x in &x.members {
let member = symbol_table::get(*x).unwrap();
let name = member.token.text;
if let SymbolKind::UnionMember(x) = member.kind {
if symbol.found.token.text == x.r#type.token.beg.text {
continue;
}
let r#type = x.r#type.to_ir_type(c, TypePosition::Variable)?;
members.push(ir::TypeKindMember { name, r#type });
}
}
check_struct_union_members(c, &members, &symbol.found);
Ok(members)
});
context.pop_generic_map();
ir::TypeKind::Union(Arc::new(ir::TypeKindUnion {
id: symbol.found.id,
members: members?,
}))
}
SymbolKind::Enum(x) => {
let r#type = if let Some(enum_type) = &x.r#type {
context.push_generic_map(map.clone());
let mut ret =
context.block(|c| enum_type.to_ir_type(c, TypePosition::Enum));
if let Ok(r#type) = ret.as_mut() {
if r#type.is_inferable_width() {
r#type.width_mut().replace(0, Some(x.width));
}
}
context.pop_generic_map();
ret?
} else {
{
let mut t = ir::Type::new(ir::TypeKind::Logic);
t.set_concrete_width(Shape::new(vec![Some(x.width)]));
t
}
};
ir::TypeKind::Enum(Arc::new(ir::TypeKindEnum {
id: symbol.found.id,
r#type: Box::new(r#type),
}))
}
SymbolKind::Modport(_) => {
path.paths.pop();
let token: TokenRange = symbol.found.token.into();
let sig =
Signature::from_path(context, path).ok_or_else(|| ir_error!(token))?;
ir::TypeKind::Modport(sig.into(), symbol.found.token.text)
}
SymbolKind::TypeDef(x) if !x.is_proto => {
if let Some(ty) = &x.r#type
&& context.push_typedef_visiting(symbol.found.id)
{
context.push_generic_map(map.clone());
let r#type = context.block(|c| ty.to_ir_type(c, TypePosition::TypeDef));
context.pop_generic_map();
context.pop_typedef_visiting();
let mut r#type = r#type?;
width.append(r#type.width_mut());
array.append(&mut r#type.array);
signed = r#type.signed;
r#type.kind
} else {
ir::TypeKind::Unknown
}
}
SymbolKind::Parameter(x) if x.is_proto => {
if x.r#type.kind.is_type() {
ir::TypeKind::Unknown
} else {
let token: TokenRange = symbol.found.token.into();
return Err(ir_error!(token));
}
}
SymbolKind::TypeDef(x) if x.is_proto => {
if let Some(x) = &x.r#type {
let mut r#type = x.to_ir_type(context, TypePosition::TypeDef)?;
width.append(r#type.width_mut());
array.append(&mut r#type.array);
signed = r#type.signed;
r#type.kind
} else {
ir::TypeKind::Unknown
}
}
SymbolKind::SystemVerilog => ir::TypeKind::SystemVerilog,
SymbolKind::Interface(_) => {
let token: TokenRange = symbol.found.token.into();
let sig =
Signature::from_path(context, path).ok_or_else(|| ir_error!(token))?;
ir::TypeKind::Interface(sig.into())
}
SymbolKind::GenericParameter(x) => match &x.bound {
GenericBoundKind::Proto(x) => {
let mut r#type = x.to_ir_type(context, pos)?;
width.append(r#type.width_mut());
array.append(&mut r#type.array);
signed = r#type.signed;
r#type.kind
}
GenericBoundKind::Type => {
if let Some(x) = &x.default_value {
return eval_type(context, x, pos);
} else {
ir::TypeKind::Unknown
}
}
_ => ir::TypeKind::Unknown,
},
SymbolKind::GenericConst(x) => match &x.bound {
GenericBoundKind::Proto(x) => {
let mut r#type = x.to_ir_type(context, pos)?;
width.append(r#type.width_mut());
array.append(&mut r#type.array);
signed = r#type.signed;
r#type.kind
}
GenericBoundKind::Type => {
let (comptime, _) = eval_generic_expr(context, &x.value)?;
if let ValueVariant::Type(mut x) = comptime.value {
width.append(x.width_mut());
array.append(&mut x.array);
x.kind
} else {
ir::TypeKind::Unknown
}
}
_ => ir::TypeKind::Unknown,
},
SymbolKind::Parameter(x) if !x.is_proto => {
if x.r#type.kind.is_type() {
if let Some(expr) = &x.value {
context.push_generic_map(map.clone());
let expr = context.block(|c| eval_expr(c, None, expr, false));
context.pop_generic_map();
let (comptime, _) = expr?;
if let ValueVariant::Type(mut r#type) = comptime.value {
width.append(r#type.width_mut());
array.append(&mut r#type.array);
signed = r#type.signed;
r#type.kind
} else {
ir::TypeKind::Unknown
}
} else {
ir::TypeKind::Unknown
}
} else {
let token: TokenRange = symbol.found.token.into();
return Err(ir_error!(token));
}
}
SymbolKind::TbComponent(x)
if matches!(
x.kind,
TbComponentKind::File
| TbComponentKind::External(_)
| TbComponentKind::Random
) =>
{
if !context.in_test_module {
let token: TokenRange = path.paths[0].base.into();
context.insert_error(AnalyzerError::invalid_tb_usage(&token));
}
if matches!(x.kind, TbComponentKind::Random)
&& let Some(arg) =
path.paths.last().and_then(|p| p.arguments.first()).cloned()
{
return eval_type(context, &arg, pos);
}
width.push(Some(32));
ir::TypeKind::Bit
}
_ => {
let token: TokenRange = symbol.found.token.into();
return Err(ir_error!(token));
}
}
} else if matches!(path.kind, GenericSymbolPathKind::TypeLiteral)
&& path.paths.len() == 1
&& path.paths[0].arguments.is_empty()
{
match path.paths[0].base.to_string().as_str() {
"p8" => {
width.push(Some(8));
is_positive = true;
ir::TypeKind::Bit
}
"p16" => {
width.push(Some(16));
is_positive = true;
ir::TypeKind::Bit
}
"p32" => {
width.push(Some(32));
is_positive = true;
ir::TypeKind::Bit
}
"p64" => {
width.push(Some(64));
is_positive = true;
ir::TypeKind::Bit
}
"u8" => {
width.push(Some(8));
ir::TypeKind::Bit
}
"u16" => {
width.push(Some(16));
ir::TypeKind::Bit
}
"u32" => {
width.push(Some(32));
ir::TypeKind::Bit
}
"u64" => {
width.push(Some(64));
ir::TypeKind::Bit
}
"i8" => {
width.push(Some(8));
signed = true;
ir::TypeKind::Bit
}
"i16" => {
width.push(Some(16));
signed = true;
ir::TypeKind::Bit
}
"i32" | "f32" => {
width.push(Some(32));
signed = true;
ir::TypeKind::Bit
}
"i64" | "f64" => {
width.push(Some(64));
signed = true;
ir::TypeKind::Bit
}
"bbool" => ir::TypeKind::Bit,
"lbool" => ir::TypeKind::Logic,
"string" => ir::TypeKind::String,
_ => ir::TypeKind::Unknown,
}
} else if let GenericSymbolPathKind::VariableType(widths) = &path.kind {
for w in widths {
width.push(Some(*w));
}
if path.paths.last().map(|p| p.base.to_string()).as_deref() == Some("logic") {
ir::TypeKind::Logic
} else {
ir::TypeKind::Bit
}
} else {
ir::TypeKind::Unknown
}
};
let width_expr = ir::WidthExpr::from_shape(&width);
let mut r#type = ir::Type::new(kind);
r#type.signed = signed;
r#type.is_positive = is_positive;
r#type.array = array;
if width_expr.len() == width.as_slice().len() {
r#type.set_parametric_width(width, width_expr);
} else {
r#type.set_concrete_width(width);
}
Ok(r#type)
}
fn check_struct_union_members(
context: &mut Context,
members: &[ir::TypeKindMember],
symbol: &Symbol,
) {
if context.in_generic {
return;
}
let is_indeterminate = |t: &ir::Type| t.is_systemverilog() || t.is_unknown();
let all_4state = members
.iter()
.all(|x| x.r#type.is_4state() || is_indeterminate(&x.r#type));
let all_2state = members
.iter()
.all(|x| x.r#type.is_2state() || is_indeterminate(&x.r#type));
if !(all_4state || all_2state) {
let token: TokenRange = symbol.token.into();
context.insert_error(AnalyzerError::mixed_struct_union_member(&token));
}
}
pub fn eval_clock(context: &mut Context, value: &AlwaysFfDeclaration) -> IrResult<ir::FfClock> {
let token: TokenRange = value.into();
if let Some(x) = &value.always_ff_declaration_opt {
let path = x
.always_ff_event_list
.always_ff_clock
.hierarchical_identifier
.as_ref();
let path: VarPathSelect = Conv::conv(context, path)?;
let (path, select, token) = path.into();
if let Some((id, comptime)) = context.find_path(&path) {
let (index, select) = select.split(comptime.r#type.array.dims());
let _ = index.eval_comptime(context, &comptime.r#type, true);
let index = index.to_index();
let dim = comptime.r#type.selected_dimension(&index, &select);
if !comptime.r#type.is_clock() || dim.0 != 0 || dim.1 != 0 || select.is_range() {
context.insert_error(AnalyzerError::invalid_clock(
&path.to_string(),
&comptime.token,
));
}
Ok(ir::FfClock {
id,
index,
select,
comptime,
})
} else {
Err(ir_error!(token))
}
} else if let Some((x, id)) = context.get_default_clock() {
let token = value.always_ff.always_ff_token.token;
symbol_table::add_reference(id, &token);
Ok(x)
} else {
context.insert_error(AnalyzerError::missing_clock_signal(&token));
Err(ir_error!(token))
}
}
pub fn eval_reset(
context: &mut Context,
value: &AlwaysFfDeclaration,
) -> IrResult<Option<ir::FfReset>> {
let token: TokenRange = value.into();
if !value.has_if_reset() {
if value.has_explicit_reset() {
context.insert_error(AnalyzerError::missing_if_reset(&token));
}
return Ok(None);
}
if let Some(x) = &value.always_ff_declaration_opt
&& let Some(x) = &x.always_ff_event_list.always_ff_event_list_opt
{
let path = x.always_ff_reset.hierarchical_identifier.as_ref();
let path: VarPathSelect = Conv::conv(context, path)?;
let (path, select, token) = path.into();
if let Some((id, comptime)) = context.find_path(&path) {
let (index, select) = select.split(comptime.r#type.array.dims());
let _ = index.eval_comptime(context, &comptime.r#type, true);
let index = index.to_index();
let dim = comptime.r#type.selected_dimension(&index, &select);
if !comptime.r#type.is_reset() || dim.0 != 0 || dim.1 != 0 || select.is_range() {
context.insert_error(AnalyzerError::invalid_reset(
&path.to_string(),
&comptime.token,
));
}
Ok(Some(ir::FfReset {
id,
index,
select,
comptime,
}))
} else {
Err(ir_error!(token))
}
} else if let Some((x, id)) = context.get_default_reset() {
let token = value.always_ff.always_ff_token.token;
symbol_table::add_reference(id, &token);
Ok(Some(x))
} else {
context.insert_error(AnalyzerError::missing_reset_signal(&token));
Err(ir_error!(token))
}
}
pub fn eval_width_select(
context: &mut Context,
path: &VarPath,
r#type: &ir::Type,
width_select: VarSelect,
) -> Option<VarSelect> {
if r#type.is_struct_union() && !r#type.width().is_empty() && !width_select.is_empty() {
let part_select = PartSelectPath {
base: r#type.clone(),
path: path.clone(),
part_select: vec![],
};
part_select.to_base_select(context, &width_select)
} else {
Some(width_select)
}
}
pub fn eval_generate_for_range(
context: &mut Context,
range: &Range,
rev: bool,
step: Option<(&AssignmentOperator, &Expression)>,
token: TokenRange,
) -> IrResult<Vec<usize>> {
let for_range = build_for_range_inner(context, range, rev, step, true)?;
for_range.eval_iter(context).ok_or_else(|| ir_error!(token))
}
pub fn build_for_range(
context: &mut Context,
range: &Range,
rev: bool,
step: Option<(&AssignmentOperator, &Expression)>,
) -> IrResult<ir::ForRange> {
build_for_range_inner(context, range, rev, step, false)
}
fn build_for_range_inner(
context: &mut Context,
range: &Range,
rev: bool,
step: Option<(&AssignmentOperator, &Expression)>,
require_const: bool,
) -> IrResult<ir::ForRange> {
let (beg, end, inclusive) = if require_const {
eval_range_const(context, range)?
} else {
eval_range(context, range)?
};
if let Some((op, expr)) = step {
let mut step_expr: ir::Expression = Conv::conv(context, expr)?;
let step_comptime = step_expr.eval_comptime(context, None);
let step_value = step_comptime.get_value()?;
let step_val = step_value.to_usize().unwrap_or(0);
let step_negative = step_value.is_semantically_not_positive() && step_val != 0;
let op: ir::Op = Conv::conv(context, op)?;
if matches!(op, ir::Op::Add) {
if step_val == 0 {
let token: TokenRange = expr.into();
context.insert_error(AnalyzerError::invalid_for_step(
InvalidForStepKind::ZeroStep,
&token,
));
return Err(ir_error!(token));
}
if step_negative {
let token: TokenRange = expr.into();
context.insert_error(AnalyzerError::invalid_for_step(
InvalidForStepKind::NeverAdvances,
&token,
));
return Err(ir_error!(token));
}
if rev {
Ok(ir::ForRange::Reverse {
start: beg,
end,
inclusive,
step: step_val,
})
} else {
Ok(ir::ForRange::Forward {
start: beg,
end,
inclusive,
step: step_val,
})
}
} else if rev {
let token: TokenRange = range.into();
context.insert_error(AnalyzerError::invalid_for_step(
InvalidForStepKind::NonAdditiveReverse,
&token,
));
Err(ir_error!(token))
} else {
let never_advances = match op {
ir::Op::Sub
| ir::Op::Div
| ir::Op::Rem
| ir::Op::BitAnd
| ir::Op::ArithShiftR
| ir::Op::LogicShiftR => true,
ir::Op::Mul => step_val <= 1,
ir::Op::BitOr | ir::Op::BitXor | ir::Op::ArithShiftL | ir::Op::LogicShiftL => {
step_val == 0
}
_ => false,
};
if never_advances {
let token: TokenRange = expr.into();
context.insert_error(AnalyzerError::invalid_for_step(
InvalidForStepKind::NeverAdvances,
&token,
));
return Err(ir_error!(token));
}
const INDUCTION_MAX: usize = i32::MAX as usize;
let end_val = end.eval_value(context).map(|end_val| {
if inclusive {
end_val.saturating_add(1)
} else {
end_val
}
});
let scannable = end_val.is_some() || !matches!(op, ir::Op::Add);
if let Some(start_val) = beg.eval_value(context)
&& scannable
{
let limit = context.config.evaluate_size_limit;
let mut i = start_val;
let mut n = 0usize;
while end_val.is_none_or(|end_val| i < end_val) && n <= limit {
match op.eval(i, step_val) {
Some(next) if next > INDUCTION_MAX => {
let token: TokenRange = expr.into();
context.insert_error(AnalyzerError::for_loop_overflow(&token));
return Err(ir_error!(token));
}
Some(next) if next > i => i = next,
_ if end_val.is_some() => {
let token: TokenRange = expr.into();
context.insert_error(AnalyzerError::invalid_for_step(
InvalidForStepKind::StopsAdvancing,
&token,
));
return Err(ir_error!(token));
}
_ => break,
}
n += 1;
}
}
Ok(ir::ForRange::Stepped {
start: beg,
end,
inclusive,
step: step_val,
op,
})
}
} else if rev {
Ok(ir::ForRange::Reverse {
start: beg,
end,
inclusive,
step: 1,
})
} else {
Ok(ir::ForRange::Forward {
start: beg,
end,
inclusive,
step: 1,
})
}
}
pub fn build_for_statement(
context: &mut Context,
value: &ForStatement,
r#type: &ir::Type,
clock_domain: ClockDomain,
for_range: ir::ForRange,
token: TokenRange,
) -> IrResult<ir::StatementBlock> {
let var_name = value.identifier.text();
let index = value.identifier.text();
let path = VarPath::new(index);
let kind = VarKind::Const;
let comptime = Comptime::from_type(r#type.clone(), clock_domain, token);
let loop_var_id = context.insert_var_path(path.clone(), comptime.clone());
let values = if let Some(total_width) = r#type.total_width() {
vec![Value::new_x(total_width, r#type.signed)]
} else {
vec![]
};
let array_limit = context.config.evaluate_array_limit;
let variable = Variable::new(
loop_var_id,
path,
kind,
comptime.r#type.clone(),
values,
context.get_affiliation(),
&token,
array_limit,
);
context.insert_variable(loop_var_id, variable);
let body: ir::StatementBlock = Conv::conv(context, value.statement_block.as_ref())?;
Ok(ir::StatementBlock(vec![ir::Statement::For(Box::new(
ir::ForStatement {
var_id: loop_var_id,
var_name,
var_type: r#type.clone(),
range: for_range,
body: body.0,
token,
},
))]))
}
pub fn eval_function_call(
context: &mut Context,
value: &IdentifierFactor,
token: TokenRange,
) -> IrResult<ir::Expression> {
if let Some(x) = &value.identifier_factor_opt
&& let IdentifierFactorOptGroup::FunctionCall(x) = x.identifier_factor_opt_group.as_ref()
{
if context.in_test_module
&& let Some(tb_stmt) = tb_method_call(
context,
value.expression_identifier.as_ref(),
&x.function_call,
token,
TbMethodCallPosition::Value,
)?
{
let ir::Statement::TbMethodCall(method_call) = tb_stmt else {
unreachable!()
};
if method_call.method.has_return_value() {
return hoist_component_method_call(context, method_call, token);
}
context.insert_error(AnalyzerError::invalid_factor(
None,
"testbench method without return value",
&token,
&[],
));
return Err(ir_error!(token));
}
let args = if let Some(x) = &x.function_call.function_call_opt {
argument_list(context, x.argument_list.as_ref())?
} else {
ir::Arguments::Null
};
let resolved_path = context.resolve_path(value.expression_identifier.as_ref().into());
let symbol = symbol_table::resolve(&resolved_path).map_err(|_| ir_error!(token))?;
match &symbol.found.kind {
SymbolKind::SystemFunction(_) => {
let name = symbol.found.token.text;
let args = args.to_system_function_args(context, &symbol.found);
check_initial_assign_system_function_args(context, &symbol.found, &args);
let ret = ir::SystemFunctionCall::new(context, name, args, token)?;
Ok(ir::Expression::Term(Box::new(
ir::Factor::SystemFunctionCall(ret),
)))
}
SymbolKind::Function(x) if x.is_proto => {
if context.in_generic {
let ret =
function_call(context, value.expression_identifier.as_ref(), args, token)?;
Ok(ir::Expression::Term(Box::new(ir::Factor::FunctionCall(
ret,
))))
} else {
Err(ir_error!(token))
}
}
SymbolKind::Function(_) | SymbolKind::ModportFunctionMember(_) => {
let ret =
function_call(context, value.expression_identifier.as_ref(), args, token)?;
Ok(ir::Expression::Term(Box::new(ir::Factor::FunctionCall(
ret,
))))
}
SymbolKind::SystemVerilog => {
let mut x = Comptime::create_unknown(token);
x.is_const = true;
Ok(ir::Expression::Term(Box::new(ir::Factor::Value(x))))
}
_ => {
let name = symbol.found.token.text.to_string();
let kind = symbol.found.kind.to_kind_name();
context.insert_error(AnalyzerError::call_non_function(&name, &kind, &token));
Err(ir_error!(token))
}
}
} else {
unreachable!();
}
}
pub fn eval_struct_constructor(
context: &mut Context,
value: &IdentifierFactor,
token: TokenRange,
) -> IrResult<ir::Expression> {
if let Some(x) = &value.identifier_factor_opt
&& let IdentifierFactorOptGroup::StructConstructor(x) =
x.identifier_factor_opt_group.as_ref()
{
let items: Vec<_> = x.struct_constructor.struct_constructor_list.as_ref().into();
let default = &x.struct_constructor.struct_constructor_opt;
let path: GenericSymbolPath = value.expression_identifier.as_ref().into();
let r#type = symbol::Type {
modifier: vec![],
kind: symbol::TypeKind::UserDefined(symbol::UserDefinedType { path, symbol: None }),
width: vec![],
array: vec![],
array_type: None,
is_const: false,
token,
};
let r#type = r#type.to_ir_type(context, TypePosition::Variable)?;
let members = match &r#type.kind {
ir::TypeKind::Struct(x) => &x.members,
ir::TypeKind::SystemVerilog => &vec![],
ir::TypeKind::Union(_) => {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::UnionConstructor,
&token,
));
return Err(ir_error!(token));
}
_ => {
context.insert_error(AnalyzerError::mismatch_type(
MismatchTypeKind::NonStructType {
actual: r#type.kind.to_string(),
},
&token,
));
return Err(ir_error!(token));
}
};
let mut exprs = HashMap::default();
for item in items {
let name = item.identifier.text();
exprs.insert(name, item.expression.as_ref().clone());
}
let mut ret = vec![];
for x in members {
let (_, expr) = if let Some(expr) = exprs.get(&x.name) {
eval_expr(context, Some(x.r#type.clone()), expr, false)?
} else if let Some(expr) = default {
eval_expr(context, Some(x.r#type.clone()), &expr.expression, false)?
} else {
context.insert_error(AnalyzerError::unknown_member(
"struct constructor",
&x.name.to_string(),
&token,
));
return Err(ir_error!(token));
};
ret.push((x.name, expr));
}
let comptime = Box::new(Comptime::create_unknown(token));
Ok(ir::Expression::StructConstructor(
Box::new(r#type),
ret,
comptime,
))
} else {
unreachable!();
}
}
enum HierReference {
Resolved {
inst_path: Vec<StrId>,
var_path: VarPath,
r#type: Box<ir::Type>,
part_select: Option<Box<PartSelectPath>>,
},
UnknownMember {
owner: String,
member: StrId,
},
NotDeclaredYet(StrId),
InstanceArray(StrId),
NotHier,
}
fn classify_hier_reference(context: &Context, path: &VarPath) -> HierReference {
let segs = &path.0;
if segs.len() < 2 {
return HierReference::NotHier;
}
if let Some(component) = context.inst_components.get(&segs[0]) {
let mut component = component.as_ref();
let mut inst_path = vec![segs[0]];
let mut i = 1;
loop {
let ir::Component::Module(module) = component else {
return HierReference::NotHier;
};
let var_path = VarPath::from_slice(&segs[i..]);
if let Some(variable) = module.variables.values().find(|v| v.path == var_path) {
return HierReference::Resolved {
inst_path,
var_path,
r#type: Box::new(variable.r#type.clone()),
part_select: None,
};
}
let holder = module
.variables
.values()
.filter(|v| v.path.0.len() < var_path.0.len() && var_path.0.starts_with(&v.path.0))
.max_by_key(|v| v.path.0.len());
if let Some(variable) = holder
&& let Some(part_select) = variable
.r#type
.expand_struct_union(&variable.path, &[], None)
.into_iter()
.find(|x| x.path == var_path)
{
return HierReference::Resolved {
inst_path,
var_path: variable.path.clone(),
r#type: Box::new(variable.r#type.clone()),
part_select: Some(Box::new(part_select)),
};
}
let child = module.declarations.iter().find_map(|x| {
let ir::Declaration::Inst(inst) = x else {
return None;
};
let consumed = ir::qualified_prefix_len(&inst.hierarchy, inst.name, &segs[i..])?;
Some((&inst.component, consumed))
});
let Some((child, consumed)) = child else {
let (owner_tail, member) = match holder {
Some(v) => (v.path.0.as_slice(), segs[i + v.path.0.len()]),
None => (&[][..], segs[i]),
};
let owner = inst_path
.iter()
.chain(owner_tail)
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(".");
return HierReference::UnknownMember { owner, member };
};
inst_path.extend_from_slice(&segs[i..i + consumed]);
component = child.as_ref();
i += consumed;
if i >= segs.len() {
let owner = inst_path
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(".");
return HierReference::UnknownMember {
owner,
member: *segs.last().unwrap(),
};
}
}
}
let Some(namespace) = context.current_namespace() else {
return HierReference::NotHier;
};
let Ok(symbol) = symbol_table::resolve((segs[0], &namespace)) else {
return HierReference::NotHier;
};
let SymbolKind::Instance(prop) = &symbol.found.kind else {
return HierReference::NotHier;
};
let Ok(type_symbol) =
symbol_table::resolve((&prop.type_name.generic_path(), &symbol.found.namespace))
else {
return HierReference::NotHier;
};
let is_module = match &type_symbol.found.kind {
SymbolKind::Module(_) | SymbolKind::AliasModule(_) => true,
SymbolKind::GenericInstance(x) => matches!(
symbol_table::get(x.base).map(|x| x.kind),
Some(SymbolKind::Module(_))
),
_ => false,
};
if !is_module {
return HierReference::NotHier;
}
if prop.array.is_empty() {
HierReference::NotDeclaredYet(segs[0])
} else {
HierReference::InstanceArray(segs[0])
}
}
pub fn eval_factor_path(
context: &mut Context,
symbol_path: GenericSymbolPath,
var_path: VarPathSelect,
allow_unknown_value: bool,
token: TokenRange,
) -> IrResult<ir::Factor> {
context.function_eval_depth += 1;
let ret = if context.function_eval_depth > context.config.function_instance_depth_limit {
if context.function_eval_overflow.is_none() {
context.function_eval_overflow = Some((token, context.function_eval_depth));
}
Err(ir_error!(token))
} else {
eval_factor_path_inner(context, symbol_path, var_path, allow_unknown_value, token)
};
context.function_eval_depth -= 1;
ret
}
fn eval_factor_path_inner(
context: &mut Context,
symbol_path: GenericSymbolPath,
var_path: VarPathSelect,
allow_unknown_value: bool,
token: TokenRange,
) -> IrResult<ir::Factor> {
let (path, select, _) = var_path.into();
let mut generic_path = context.resolve_path(symbol_path);
generic_path.unalias(None);
check_generic_refereence(context, &generic_path);
let found = if let Some(path) = generic_path.to_var_path()
&& let Some((var_id, comptime)) = context.find_path(&path)
{
Some((var_id, comptime))
} else {
context.find_path(&path)
};
let hier = if found.is_none() && context.in_test_module {
classify_hier_reference(context, &path)
} else {
HierReference::NotHier
};
if let Some((var_id, mut comptime)) = found {
if let Some(part_select) = &comptime.part_select {
comptime.r#type = part_select.base.clone();
}
let (array_select, width_select) = select.split(comptime.r#type.array.dims());
let _ = array_select.eval_comptime(context, &comptime.r#type, true);
let width_select = if let Some(part_select) = &comptime.part_select {
part_select.to_base_select(context, &width_select)
} else {
eval_width_select(context, &path, &comptime.r#type, width_select)
};
let width_select = width_select.ok_or_else(|| ir_error!(token))?;
comptime.is_global = false;
if array_select.is_range() {
Err(ir_error!(token))
} else {
let index = array_select.to_index();
comptime.r#type.array.drain(0..index.dimension());
comptime.is_const &= index.is_const() && width_select.is_const();
if (index.dimension() > 0 || !width_select.is_empty())
&& matches!(comptime.value, ValueVariant::NumericArray(_))
{
comptime.value = ValueVariant::Unknown;
}
comptime.token = token;
if comptime.r#type.is_type() {
Ok(ir::Factor::Value(comptime))
} else {
if !width_select.is_empty() || !index.is_const() {
comptime.evaluated = false;
}
Ok(ir::Factor::Variable(var_id, index, width_select, comptime))
}
}
} else if let HierReference::Resolved {
inst_path,
var_path,
r#type,
part_select,
} = hier
{
if !context.in_tb_block {
context.insert_error(AnalyzerError::invisible_identifier(
&path.0[1].to_string(),
&token,
));
return Err(ir_error!(token));
}
let mut comptime = Comptime::from_type(*r#type, ClockDomain::None, token);
let (array_select, width_select) = select.split(comptime.r#type.array.dims());
let _ = array_select.eval_comptime(context, &comptime.r#type, true);
let width_select = if let Some(part_select) = &part_select {
part_select.to_base_select(context, &width_select)
} else {
eval_width_select(context, &var_path, &comptime.r#type, width_select)
}
.ok_or_else(|| ir_error!(token))?;
if array_select.is_range() {
Err(ir_error!(token))
} else {
let index = array_select.to_index();
comptime.r#type.array.drain(0..index.dimension());
if !width_select.is_empty() {
comptime.r#type.flatten_struct_union_enum();
if let Some(width) = width_select.eval_comptime(context, &comptime.r#type, false) {
comptime.r#type.set_concrete_width(width);
}
}
comptime.token = token;
Ok(ir::Factor::HierVariable(Box::new(ir::HierVarRef {
inst_path,
var_path,
index,
select: width_select,
comptime,
})))
}
} else if !matches!(hier, HierReference::NotHier) {
match hier {
HierReference::UnknownMember { owner, member } => {
context.insert_error(AnalyzerError::unknown_member(
&owner,
&member.to_string(),
&token,
));
}
HierReference::NotDeclaredYet(name) => {
if context.in_tb_block {
context.insert_error(AnalyzerError::referring_before_definition(
&name.to_string(),
&token,
));
} else {
context.insert_error(AnalyzerError::invisible_identifier(
&path.0[1].to_string(),
&token,
));
}
}
HierReference::InstanceArray(name) => {
context.insert_error(AnalyzerError::invalid_factor(
Some(&name.to_string()),
"instance array",
&token,
&[],
));
}
HierReference::Resolved { .. } | HierReference::NotHier => unreachable!(),
}
Err(ir_error!(token))
} else if let Some(x) = generic_path.to_literal() {
let x = x.eval_comptime(token);
Ok(ir::Factor::Value(x))
} else if generic_path.is_anonymous() {
let mut comptime = Comptime::create_unknown(token);
comptime.is_const = true;
comptime.is_global = true;
Ok(ir::Factor::Anonymous(comptime))
} else if let Ok(symbol) = symbol_table::resolve(&generic_path) {
let is_inernal = context
.current_namespace()
.map(|x| symbol.found.namespace.included(&x))
.unwrap_or(false);
let factor = if is_inernal {
eval_factor_symbol(
context,
generic_path,
(*symbol).clone(),
allow_unknown_value,
token,
)?
} else {
let maps = generic_path.to_generic_maps();
eval_factor_symbol_external(
context,
generic_path,
(*symbol).clone(),
maps,
allow_unknown_value,
token,
)?
};
apply_symbol_select(context, factor, select, &symbol.found, token)
} else {
Err(ir_error!(token))
}
}
fn apply_symbol_select(
context: &mut Context,
factor: ir::Factor,
select: VarSelect,
symbol: &Symbol,
token: TokenRange,
) -> IrResult<ir::Factor> {
if select.is_empty() {
return Ok(factor);
}
let ir::Factor::Value(mut comptime) = factor else {
return Ok(factor);
};
let values = match &comptime.value {
ValueVariant::NumericArray(values) => values.clone(),
ValueVariant::Numeric(value) => vec![value.clone()],
_ => {
reduce_unevaluable_select(context, &mut comptime, select, token);
return Ok(ir::Factor::Value(comptime));
}
};
let array = comptime.r#type.array.clone();
let (array_select, width_select) = select.split(array.dims());
let _ = array_select.eval_comptime(context, &comptime.r#type, true);
if array_select.is_range() {
return Err(ir_error!(token));
}
let index = array_select.to_index();
let is_const_select = index.is_const() && width_select.is_const_with_range();
comptime.is_const &= is_const_select;
comptime.is_global &= comptime.is_const;
if is_const_select
&& let Some(comptime) = fold_symbol_select(
context,
&comptime,
&values,
&array,
&index,
&width_select,
token,
)
{
return Ok(ir::Factor::Value(comptime));
}
let id = materialize_const(context, &comptime, values, symbol, token)?;
comptime.r#type.array.drain(0..index.dimension());
comptime.value = ValueVariant::Unknown;
comptime.token = token;
comptime.evaluated = false;
Ok(ir::Factor::Variable(id, index, width_select, comptime))
}
fn reduce_unevaluable_select(
context: &mut Context,
comptime: &mut Comptime,
select: VarSelect,
token: TokenRange,
) {
if comptime.r#type.is_type() || comptime.r#type.is_systemverilog() {
return;
}
let (array_select, width_select) = select.split(comptime.r#type.array.dims());
let _ = array_select.eval_comptime(context, &comptime.r#type, true);
if array_select.is_range() {
return;
}
comptime.r#type.array.drain(0..array_select.dimension());
if !width_select.is_empty() {
comptime.r#type.flatten_struct_union_enum();
if let Some(width) = width_select.eval_comptime(context, &comptime.r#type, false) {
comptime.r#type.set_concrete_width(width);
}
}
comptime.token = token;
}
fn fold_symbol_select(
context: &mut Context,
comptime: &Comptime,
values: &[Value],
array: &Shape,
index: &VarIndex,
select: &VarSelect,
token: TokenRange,
) -> Option<Comptime> {
let mut comptime = comptime.clone();
comptime.r#type.array.drain(0..index.dimension());
comptime.token = token;
let indices = index.eval_value(context)?;
if !comptime.r#type.array.is_empty() {
let (beg, end) = array.calc_range(&indices)?;
comptime.value = ValueVariant::NumericArray(values.get(beg..=end)?.to_vec());
return Some(comptime);
}
let mut element = comptime.r#type.clone();
if !select.is_empty() {
element.flatten_struct_union_enum();
comptime.r#type = element.clone();
if let Some(width) = select.eval_comptime(context, &element, false) {
comptime.r#type.set_concrete_width(width);
}
}
let flat = array.calc_index(&indices)?;
let (beg, end) = select.eval_value(context, &element, false)?;
comptime.value = ValueVariant::Numeric(values.get(flat)?.select(beg, end));
Some(comptime)
}
fn materialize_const(
context: &mut Context,
comptime: &Comptime,
values: Vec<Value>,
symbol: &Symbol,
token: TokenRange,
) -> IrResult<ir::VarId> {
let r#type = comptime.r#type.clone();
let values = fit_array_elements(values, &r#type);
if values.len() != r#type.total_array().unwrap_or(1).max(1) {
return Err(ir_error!(token));
}
let name: String = format!("__const_{}_{}", symbol.namespace, symbol.token)
.chars()
.map(|x| if x.is_ascii_alphanumeric() { x } else { '_' })
.collect();
let affiliation = context.get_affiliation();
let mut path = VarPath::new(resource_table::insert_str(&name));
if let Some(id) = reusable_const(context, &path, affiliation, &r#type, &values) {
return Ok(id);
}
if context.find_path(&path).is_some() {
path = VarPath::new(resource_table::insert_str(&format!(
"{name}_{}",
symbol.id.0
)));
if let Some(id) = reusable_const(context, &path, affiliation, &r#type, &values) {
return Ok(id);
}
}
let mut comptime = comptime.clone();
comptime.token = token;
let id = context.insert_var_path(path.clone(), comptime);
let array_limit = context.config.evaluate_array_limit;
let variable = Variable::new(
id,
path,
VarKind::Const,
r#type,
values,
affiliation,
&token,
array_limit,
);
context.insert_variable(id, variable);
Ok(id)
}
fn reusable_const(
context: &Context,
path: &VarPath,
affiliation: Affiliation,
r#type: &ir::Type,
values: &[Value],
) -> Option<ir::VarId> {
let (id, _) = context.find_path(path)?;
let variable = context.variables.get(&id)?;
let reusable = variable.kind == VarKind::Const
&& variable.affiliation == affiliation
&& variable.r#type == *r#type
&& variable.value == values;
reusable.then_some(id)
}
pub fn eval_factor_symbol_external(
context: &mut Context,
path: GenericSymbolPath,
symbol: ResolveResult,
maps: Vec<GenericMap>,
allow_unknown_value: bool,
token: TokenRange,
) -> IrResult<ir::Factor> {
let mut external_context = Context::default();
external_context.inherit(context);
external_context.push_generic_map(maps);
let ret =
external_context.block(|c| eval_factor_symbol(c, path, symbol, allow_unknown_value, token));
external_context.pop_generic_map();
context.inherit(&mut external_context);
ret
}
pub fn eval_factor_symbol(
context: &mut Context,
path: GenericSymbolPath,
symbol: ResolveResult,
allow_unknown_value: bool,
token: TokenRange,
) -> IrResult<ir::Factor> {
match &symbol.found.kind {
SymbolKind::Parameter(x) if !x.is_proto => {
if let Some(namespace) = context.current_namespace()
&& symbol.found.namespace.included(&namespace)
{
context.insert_error(AnalyzerError::referring_before_definition(
&symbol.found.token.to_string(),
&token,
));
}
if let Some(expr) = &x.value {
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
let (mut comptime, _) =
context.block(|c| eval_expr(c, Some(r#type), expr, false))?;
if let Some(width) = comptime.r#type.total_width() {
comptime.value.expand_value(width);
}
comptime.token = token;
return Ok(ir::Factor::Value(comptime));
}
}
SymbolKind::GenericParameter(x) => {
let default_value = if let Some(x) = &x.default_value
&& let Some(x) = x.to_literal()
{
let x = x.eval_comptime(token);
Some(x)
} else if allow_unknown_value {
None
} else {
return Err(ir_error!(token));
};
if let Ok(proto) = x.bound.resolve_proto_bound(&symbol.found.namespace) {
match proto {
ProtoBound::ProtoModule(_)
| ProtoBound::ProtoInterface(_)
| ProtoBound::ProtoPackage(_)
if context.allow_component_as_factor =>
{
context.allow_component_as_factor = false;
return Ok(ir::Factor::from_component_symbol(&symbol.found, token));
}
ProtoBound::FactorType(x)
| ProtoBound::Enum((_, x))
| ProtoBound::Struct((_, x))
| ProtoBound::Union((_, x)) => {
let r#type = x.to_ir_type(context, TypePosition::Generic)?;
let mut x = Comptime::from_type(r#type, ClockDomain::None, token);
if let Some(val) = default_value {
x.value = val.value;
}
x.is_const = true;
x.is_global = true;
return Ok(ir::Factor::Value(x));
}
_ => {}
}
} else if matches!(x.bound, GenericBoundKind::Type) {
let mut x = Comptime::create_unknown(token);
if let Some(val) = default_value {
x.value = val.value;
}
x.is_const = true;
x.is_global = true;
x.r#type.kind = ir::TypeKind::Type;
return Ok(ir::Factor::Value(x));
} else {
context.insert_error(AnalyzerError::invalid_factor(
Some(&symbol.found.token.to_string()),
&symbol.found.kind.to_kind_name(),
&token,
&[],
));
}
}
SymbolKind::GenericConst(x) => {
if let Ok(proto) = x.bound.resolve_proto_bound(&symbol.found.namespace) {
match proto {
ProtoBound::ProtoModule(_)
| ProtoBound::ProtoInterface(_)
| ProtoBound::ProtoPackage(_)
if context.allow_component_as_factor =>
{
context.allow_component_as_factor = false;
return Ok(ir::Factor::from_component_symbol(&symbol.found, token));
}
ProtoBound::FactorType(t)
| ProtoBound::Enum((_, t))
| ProtoBound::Struct((_, t))
| ProtoBound::Union((_, t)) => {
let r#type = t.to_ir_type(context, TypePosition::Generic)?;
let mut comptime = if context.in_generic {
Comptime::from_type(r#type.clone(), ClockDomain::None, token)
} else {
let (mut comptime, _) = eval_generic_expr(context, &x.value)?;
comptime.r#type = r#type;
comptime
};
comptime.is_const = true;
comptime.is_global = true;
comptime.token = token;
return Ok(ir::Factor::Value(comptime));
}
_ => {}
}
} else if matches!(x.bound, GenericBoundKind::Type) {
let (mut comptime, _) = eval_generic_expr(context, &x.value)?;
comptime.is_const = true;
comptime.is_global = true;
comptime.token = token;
return Ok(ir::Factor::Value(comptime));
}
}
SymbolKind::Parameter(x) if x.is_proto && allow_unknown_value => {
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
let mut x = Comptime::from_type(r#type, ClockDomain::None, token);
x.is_const = true;
x.is_global = true;
return Ok(ir::Factor::Value(x));
}
SymbolKind::TypeDef(x) if x.is_proto => {
if allow_unknown_value {
let mut x = Comptime::create_unknown(token);
x.is_const = true;
x.is_global = true;
return Ok(ir::Factor::Value(x));
}
}
SymbolKind::EnumMember(x) => {
let enum_symbol = symbol.found.get_parent().unwrap();
let SymbolKind::Enum(r#enum) = enum_symbol.kind else {
unreachable!();
};
let factor = match &x.value {
EnumMemberValue::Unresolved => {
context.insert_error(AnalyzerError::referring_before_definition(
&symbol.found.token.to_string(),
&token,
));
None
}
EnumMemberValue::ImplicitValue(x) => {
let value = Value::new_biguint(x.clone(), r#enum.width, false);
Some(ir::Factor::create_value(value, token))
}
EnumMemberValue::ExplicitValue(x, _) => {
let (mut x, _) = eval_expr(context, None, x, false)?;
x.token = token;
if r#enum.width > 0
&& let Ok(value) = x.get_value()
{
let mut value = value.clone();
if value.width() < r#enum.width {
value = value.expand(r#enum.width, value.signed()).into_owned();
}
value.trunc(r#enum.width);
value.set_signed(false);
Some(ir::Factor::create_value(value, token))
} else {
Some(ir::Factor::Value(x))
}
}
EnumMemberValue::UnevaluableValue => None,
};
if let Some(mut factor) = factor {
let enum_type = factor.comptime().r#type.clone();
let type_kind = ir::TypeKind::Enum(Arc::new(ir::TypeKindEnum {
id: enum_symbol.id,
r#type: Box::new(enum_type),
}));
let factor_comptime = factor.comptime_mut();
factor_comptime.r#type.kind = type_kind;
factor_comptime.r#type.set_concrete_width(Shape::default());
return Ok(factor);
}
}
SymbolKind::StructMember(_) => {
let module_variable = symbol.full_path.iter().any(|x| {
let symbol = symbol_table::get(*x).unwrap();
if let SymbolKind::Variable(x) = symbol.kind {
x.affiliation == Affiliation::Module
} else {
false
}
});
if module_variable {
context.insert_error(AnalyzerError::referring_before_definition(
&symbol.found.token.to_string(),
&token,
));
}
return eval_struct_member(context, &symbol.found, &path, VarPath::default(), token);
}
SymbolKind::SystemVerilog => {
let r#type = ir::Type::new(ir::TypeKind::SystemVerilog);
let mut x = Comptime::from_type(r#type, ClockDomain::None, token);
x.is_const = true;
x.is_global = true;
return Ok(ir::Factor::Value(x));
}
SymbolKind::Module(_)
| SymbolKind::AliasModule(_)
| SymbolKind::Interface(_)
| SymbolKind::AliasInterface(_)
| SymbolKind::Package(_)
| SymbolKind::AliasPackage(_) => {
if context.allow_component_as_factor {
context.allow_component_as_factor = false;
return Ok(ir::Factor::from_component_symbol(&symbol.found, token));
} else {
context.insert_error(AnalyzerError::invalid_factor(
Some(&symbol.found.token.to_string()),
&symbol.found.kind.to_kind_name(),
&token,
&[],
));
}
}
SymbolKind::Instance(x) if context.allow_component_as_factor => {
context.allow_component_as_factor = false;
if let Ok(component) =
symbol_table::resolve_generic_structural(&x.type_name, &symbol.found.namespace)
{
let sig = Arc::new(Signature::new(component.found.id));
let kind = if symbol.found.is_module(true) {
ir::TypeKind::Instance(sig.clone(), ir::InstanceKind::Module)
} else if symbol.found.is_interface(true) {
ir::TypeKind::Instance(sig.clone(), ir::InstanceKind::Interface)
} else {
ir::TypeKind::Instance(sig.clone(), ir::InstanceKind::SystemVerilog)
};
let r#type = ir::Type::new(kind);
let comptime = Comptime::from_type(r#type, ClockDomain::None, token);
return Ok(ir::Factor::Value(comptime));
}
}
SymbolKind::Function(x) if !x.is_proto => {
context.insert_error(AnalyzerError::invalid_factor(
Some(&symbol.found.token.to_string()),
&symbol.found.kind.to_kind_name(),
&token,
&[],
));
}
SymbolKind::SystemFunction(_) => {
context.insert_error(AnalyzerError::invalid_factor(
Some(&symbol.found.token.to_string()),
&symbol.found.kind.to_kind_name(),
&token,
&[],
));
}
SymbolKind::EnumMemberMangled => {
context.insert_error(AnalyzerError::undefined_identifier(
&symbol.found.token.to_string(),
&symbol.found.token.into(),
));
}
SymbolKind::Variable(x) => {
if x.affiliation == Affiliation::Module {
context.insert_error(AnalyzerError::referring_before_definition(
&symbol.found.token.to_string(),
&token,
));
}
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
let x = Comptime::from_type(r#type, x.clock_domain, token);
return Ok(ir::Factor::Value(x));
}
SymbolKind::Port(x) => {
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
let x = Comptime::from_type(r#type, x.clock_domain, token);
return Ok(ir::Factor::Value(x));
}
SymbolKind::ModportVariableMember(x) => {
let variable = symbol_table::get(x.variable).unwrap();
if let SymbolKind::Variable(x) = &variable.kind {
let r#type = x.r#type.to_ir_type(context, TypePosition::Variable)?;
let x = Comptime::from_type(r#type, x.clock_domain, token);
return Ok(ir::Factor::Value(x));
}
}
SymbolKind::Enum(_)
| SymbolKind::Struct(_)
| SymbolKind::Union(_)
| SymbolKind::TypeDef(_) => {
let r#type = symbol::Type {
modifier: vec![],
kind: symbol::TypeKind::UserDefined(symbol::UserDefinedType { path, symbol: None }),
width: vec![],
array: vec![],
array_type: None,
is_const: true,
token,
};
let r#type = r#type.to_ir_type(context, TypePosition::Variable)?;
let x = Comptime {
value: ValueVariant::Type(r#type),
r#type: ir::Type::new(ir::TypeKind::Type),
is_const: true,
is_global: true,
token,
..Default::default()
};
return Ok(ir::Factor::Value(x));
}
SymbolKind::ProjectProperty(x) => {
let value = match &x.value {
veryl_metadata::ProjectProperty::Int(x) => Value::new(*x as u64, 64, true),
veryl_metadata::ProjectProperty::Bool(x) => {
if *x {
Value::new(1, 1, false)
} else {
Value::new(0, 1, false)
}
}
};
let comptime = Comptime::create_value(value, symbol.found.token.into());
return Ok(ir::Factor::Value(comptime));
}
_ => (),
}
Err(ir_error!(token))
}
pub fn case_patterns(
context: &mut Context,
cond: &CaseCondition,
) -> IrResult<Vec<ir::CasePattern>> {
let mut ret = Vec::with_capacity(1 + cond.case_condition_list.len());
ret.push(range_item_pattern(context, &cond.range_item)?);
for x in &cond.case_condition_list {
ret.push(range_item_pattern(context, &x.range_item)?);
}
Ok(ret)
}
fn range_item_pattern(context: &mut Context, range_item: &RangeItem) -> IrResult<ir::CasePattern> {
let mut lo: ir::Expression = Conv::conv(context, range_item.range.expression.as_ref())?;
let comptime = lo.eval_comptime(context, None);
if !comptime.is_const {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::CaseCondition,
&range_item.into(),
));
}
let Some(opt) = &range_item.range.range_opt else {
return Ok(ir::CasePattern::Eq(Box::new(lo)));
};
let mut hi: ir::Expression = Conv::conv(context, opt.expression.as_ref())?;
let comptime = hi.eval_comptime(context, None);
if !comptime.is_const {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::CaseCondition,
&range_item.into(),
));
}
let inclusive = matches!(opt.range_operator.as_ref(), RangeOperator::DotDotEqu(_));
Ok(ir::CasePattern::Range {
lo: Box::new(lo),
hi: Box::new(hi),
inclusive,
})
}
pub fn case_condition(
context: &mut Context,
tgt: &ir::Expression,
cond: &CaseCondition,
) -> IrResult<ir::Expression> {
let mut ret = range_item(context, tgt, &cond.range_item)?;
for x in &cond.case_condition_list {
let item = range_item(context, tgt, &x.range_item)?;
let comptime = Box::new(Comptime::create_unknown(item.token_range()));
ret = ir::Expression::Binary(Box::new(ret), Op::LogicOr, Box::new(item), comptime);
}
Ok(ret)
}
pub fn range_list(
context: &mut Context,
tgt: &ir::Expression,
list: &RangeList,
) -> IrResult<ir::Expression> {
let mut ret = range_item(context, tgt, &list.range_item)?;
for x in &list.range_list_list {
let item = range_item(context, tgt, &x.range_item)?;
let comptime = Box::new(Comptime::create_unknown(item.token_range()));
ret = ir::Expression::Binary(Box::new(ret), Op::LogicOr, Box::new(item), comptime);
}
Ok(ret)
}
fn range_item(
context: &mut Context,
tgt: &ir::Expression,
range_item: &RangeItem,
) -> IrResult<ir::Expression> {
let mut exp: ir::Expression = Conv::conv(context, range_item.range.expression.as_ref())?;
let comptime = exp.eval_comptime(context, None);
let lo_value = comptime.get_value().ok().and_then(|v| v.to_usize());
if !comptime.is_const {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::CaseCondition,
&range_item.into(),
));
}
let ret = if let Some(x) = &range_item.range.range_opt {
let mut exp0: ir::Expression = Conv::conv(context, x.expression.as_ref())?;
let token: TokenRange = range_item.into();
let comptime = exp0.eval_comptime(context, None);
if !comptime.is_const {
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::CaseCondition,
&token,
));
}
if matches!(x.range_operator.as_ref(), RangeOperator::DotDot(_))
&& let (Some(lo), Some(hi)) = (
lo_value,
comptime.get_value().ok().and_then(|v| v.to_usize()),
)
&& lo >= hi
{
context.insert_error(AnalyzerError::invalid_range(
"the lower bound of an exclusive range is not less than the upper bound",
&token,
));
}
let comptime = Box::new(Comptime::create_unknown(token));
match x.range_operator.as_ref() {
RangeOperator::DotDot(_) => {
let cond0 = ir::Expression::Binary(
Box::new(exp.clone()),
Op::LessEq,
Box::new(tgt.clone()),
comptime.clone(),
);
let cond1 = ir::Expression::Binary(
Box::new(tgt.clone()),
Op::Less,
Box::new(exp0.clone()),
comptime.clone(),
);
ir::Expression::Binary(Box::new(cond0), Op::LogicAnd, Box::new(cond1), comptime)
}
RangeOperator::DotDotEqu(_) => {
let cond0 = ir::Expression::Binary(
Box::new(exp.clone()),
Op::LessEq,
Box::new(tgt.clone()),
comptime.clone(),
);
let cond1 = ir::Expression::Binary(
Box::new(tgt.clone()),
Op::LessEq,
Box::new(exp0.clone()),
comptime.clone(),
);
ir::Expression::Binary(Box::new(cond0), Op::LogicAnd, Box::new(cond1), comptime)
}
}
} else {
let token: TokenRange = range_item.into();
let comptime = Box::new(Comptime::create_unknown(token));
ir::Expression::Binary(
Box::new(tgt.clone()),
Op::EqWildcard,
Box::new(exp),
comptime,
)
};
Ok(ret)
}
pub fn switch_condition(context: &mut Context, cond: &SwitchCondition) -> IrResult<ir::Expression> {
let mut ret: ir::Expression = Conv::conv(context, cond.expression.as_ref())?;
for x in &cond.switch_condition_list {
let exp: ir::Expression = Conv::conv(context, x.expression.as_ref())?;
let comptime = Box::new(Comptime::create_unknown(exp.token_range()));
ret = ir::Expression::Binary(Box::new(ret), Op::LogicOr, Box::new(exp), comptime);
}
ret.eval_comptime(context, None);
Ok(ret)
}
pub fn argument_list(context: &mut Context, value: &ArgumentList) -> IrResult<Arguments> {
let mut positional = vec![];
let mut named = vec![];
let mut seen_named = HashSet::default();
let x: Vec<_> = value.into();
for arg in x {
if let Some(x) = &arg.argument_item_opt {
if let Some(name) = arg.argument_expression.expression.unwrap_identifier() {
let name_token = name.identifier().token;
let name = name_token.text;
let token: TokenRange = x.expression.as_ref().into();
if !seen_named.insert(name) {
context.insert_error(AnalyzerError::duplicate_argument(
&name_token.to_string(),
&token,
));
}
let expr = Conv::conv(context, x.expression.as_ref())?;
let dst: Vec<VarPathSelect> = Conv::conv(context, x.expression.as_ref())?;
named.push((name, (expr, dst, token)));
} else {
}
} else {
let token: TokenRange = arg.argument_expression.expression.as_ref().into();
let expr = Conv::conv(context, arg.argument_expression.expression.as_ref())?;
let dst: Vec<VarPathSelect> =
Conv::conv(context, arg.argument_expression.expression.as_ref())?;
positional.push((expr, dst, token));
}
}
if !positional.is_empty() && !named.is_empty() {
context.insert_error(AnalyzerError::mixed_function_argument(&value.into()));
}
let ret = if !positional.is_empty() && !named.is_empty() {
Arguments::Mixed(positional, named)
} else if !named.is_empty() {
Arguments::Named(named)
} else if !positional.is_empty() {
Arguments::Positional(positional)
} else {
Arguments::Null
};
Ok(ret)
}
pub fn get_component(
context: &mut Context,
sig: &Signature,
token: TokenRange,
) -> IrResult<Arc<ir::Component>> {
let mut sig = sig.clone();
sig.normalize();
let sig = &sig;
if let Some((component, in_generic)) = context.get_instance_history(sig) {
if in_generic && !context.in_generic {
context.remove_instance_history(sig);
get_component(context, sig, token)
} else {
Ok(component)
}
} else {
let symbol = symbol_table::get(sig.symbol).unwrap();
if let SymbolKind::SystemVerilog = symbol.kind {
let component = ir::SystemVerilog {
name: symbol.token.text,
connects: vec![],
};
return Ok(Arc::new(ir::Component::SystemVerilog(component)));
}
let err = context.push_instance_history(sig.clone());
if let Err(x) = err {
match x {
InstanceHistoryError::ExceedDepthLimit(x) => {
context.insert_error(AnalyzerError::exceed_limit(
ExceedLimitKind::HierarchyDepth,
x,
&token,
));
}
InstanceHistoryError::ExceedTotalLimit(x) => {
context.insert_error(AnalyzerError::exceed_limit(
ExceedLimitKind::TotalInstance,
x,
&token,
));
}
InstanceHistoryError::InfiniteRecursion => {
context.insert_error(AnalyzerError::infinite_recursion(&token));
}
}
return Err(ir_error!(token));
}
context.push_generic_map(sig.to_generic_map());
let ret = context.block(|c| match &symbol.kind {
SymbolKind::Module(x) if !x.is_proto => {
let definition =
definition_table::get(x.definition).ok_or_else(|| ir_error!(token))?;
let Definition::Module(x) = definition.as_ref() else {
unreachable!()
};
let header_only = c.in_generic;
let component: IrResult<ir::Module> = Conv::conv(c, (x, header_only));
match component {
Ok(mut component) => {
component.signature = sig.clone();
if !c.config.retain_component_body {
component.functions.clear();
component.declarations.clear();
}
let component = Arc::new(ir::Component::Module(component));
c.set_instance_history(sig, component.clone());
c.pop_instance_history();
Ok(component)
}
Err(x) => {
c.pop_instance_history();
Err(x)
}
}
}
SymbolKind::Interface(x) if !x.is_proto => {
let definition = x
.definition
.and_then(definition_table::get)
.ok_or_else(|| ir_error!(token))?;
let Definition::Interface(x) = definition.as_ref() else {
unreachable!()
};
let component: IrResult<ir::Interface> = Conv::conv(c, x);
match component {
Ok(component) => {
let component = Arc::new(ir::Component::Interface(component));
c.set_instance_history(sig, component.clone());
c.pop_instance_history();
Ok(component)
}
Err(x) => {
c.pop_instance_history();
Err(x)
}
}
}
SymbolKind::Module(x) if x.is_proto => {
let definition =
definition_table::get(x.definition).ok_or_else(|| ir_error!(token))?;
let Definition::ProtoModule(x) = definition.as_ref() else {
unreachable!()
};
let component: IrResult<ir::Module> = Conv::conv(c, x);
match component {
Ok(mut component) => {
component.signature = sig.clone();
if !c.config.retain_component_body {
component.functions.clear();
component.declarations.clear();
}
let component = Arc::new(ir::Component::Module(component));
c.set_instance_history(sig, component.clone());
c.pop_instance_history();
Ok(component)
}
Err(x) => {
c.pop_instance_history();
Err(x)
}
}
}
_ => Err(ir_error!(token)),
});
context.pop_generic_map();
ret
}
}
fn resolve_array_value(
context: &mut Context,
r#type: &ir::Type,
expr: &ir::Expression,
) -> Option<Vec<Value>> {
if r#type.array.is_empty() {
return None;
}
match expr {
ir::Expression::ArrayLiteral(_, _) => {
let mut expr = expr.clone();
eval_array_literal_values(context, r#type, &mut expr)
.ok()
.flatten()
}
ir::Expression::Term(factor) => {
let ir::Factor::Variable(id, index, select, _) = factor.as_ref() else {
return None;
};
if !select.is_empty() {
return None;
}
if index.dimension() == 0 {
return context.variables.get(id).map(|v| v.value.clone());
}
if !index.is_const() {
return None;
}
let idx = index.eval_value(context)?;
let var = context.variables.get(id)?;
let shape = &var.r#type.array;
if idx
.iter()
.enumerate()
.any(|(i, &v)| matches!(shape.get(i), Some(&Some(dim)) if v >= dim))
{
return None;
}
let (beg, end) = shape.calc_range(&idx)?;
let inner_total = end - beg + 1;
let total = var.r#type.total_array().unwrap_or(var.value.len());
if var.value.len() == total {
var.value.get(beg..=end).map(<[Value]>::to_vec)
} else if var.value.len() == 1 {
Some(vec![var.value[0].clone(); inner_total])
} else {
None
}
}
_ => None,
}
}
pub fn get_overridden_params(
context: &mut Context,
arg: &ComponentInstantiation,
) -> IrResult<HashMap<VarPath, (Comptime, ir::Expression)>> {
let mut ret = HashMap::default();
let token: TokenRange = arg.scoped_identifier.as_ref().into();
let symbol =
symbol_table::resolve(arg.scoped_identifier.as_ref()).map_err(|_| ir_error!(token))?;
let component_namespace = symbol.found.inner_namespace();
let params = if let Some(ref x) = arg.component_instantiation_opt1 {
if let Some(x) = &x.inst_parameter.inst_parameter_opt {
x.inst_parameter_list.as_ref().into()
} else {
Vec::new()
}
} else {
Vec::new()
};
for param in params {
let name = param.identifier.text();
let Ok(target) = symbol_table::resolve((param.identifier.as_ref(), &component_namespace))
.map(|x| Rc::clone(&x.found))
else {
continue;
};
let target_type = if let Some(x) = target.kind.get_type() {
let x = x.to_ir_type(context, TypePosition::Variable);
if let Ok(x) = x {
Some(x)
} else {
continue;
}
} else {
None
};
let mut expr = if let Some(x) = ¶m.inst_parameter_item_opt {
eval_expr(context, target_type.clone(), &x.expression, false)?
} else {
let src: Expression = param.identifier.as_ref().into();
eval_expr(context, target_type.clone(), &src, false)?
};
if expr.0.value.is_unknown()
&& let Some(r#type) = &target_type
&& let Some(values) = resolve_array_value(context, r#type, &expr.1)
{
expr.0.value = ValueVariant::NumericArray(values);
}
if expr.0.value.is_unknown()
&& expr.0.is_const
&& let Some(value) = expr.1.eval_value(context)
{
expr.0.value = ValueVariant::Numeric(value);
}
let is_type_param = matches!(
&target.kind,
SymbolKind::Parameter(x) if !x.is_proto && matches!(x.r#type.kind, TypeKind::Type)
);
if !is_type_param && !expr.0.is_const {
let token: TokenRange = param.identifier.as_ref().into();
context.insert_error(AnalyzerError::unevaluable_value(
UnevaluableValueKind::ParameterValue,
&token,
));
}
let path = VarPath::new(name);
ret.insert(path, expr);
}
Ok(ret)
}
pub fn get_return_str() -> StrId {
resource_table::insert_str("return")
}
pub struct PortConnects {
pub connects: Vec<(VarPath, Vec<VarPathSelect>, ir::Expression)>,
pub interface_binding: Option<VarPathSelect>,
}
pub fn get_port_connects(
context: &mut Context,
component: &ir::Module,
port: &InstPortItem,
port_path: &VarPath,
port_type: &ir::Type,
token: TokenRange,
) -> IrResult<PortConnects> {
let mut ret = vec![];
let mut interface_binding = None;
if let ir::TypeKind::Modport(_, _) = &port_type.kind {
if let Some(x) = &port.inst_port_item_opt {
let _ = eval_expr(context, Some(port_type.clone()), &x.expression, false)?;
}
let (dst_path, dst_select) = if let Some(x) = &port.inst_port_item_opt {
let dst: Vec<VarPathSelect> = Conv::conv(context, x.expression.as_ref())?;
let dst = dst.first().ok_or_else(|| ir_error!(token))?;
(dst.0.clone(), dst.1.clone())
} else {
(port_path.clone(), VarSelect::default())
};
interface_binding = Some(VarPathSelect(dst_path.clone(), dst_select.clone(), token));
let members = port_type.expand_modport(context, &dst_path, token)?;
for member in members {
if member.1.is_input() | member.1.is_output() {
let member_path = member.0.clone();
let expr = if let Some((var_id, mut comptime)) = context.find_path(&member_path) {
comptime.token = token;
let (array_select, width_select) =
dst_select.clone().split(comptime.r#type.array.dims());
comptime.r#type.array.drain(0..array_select.dimension());
ir::Expression::Term(Box::new(ir::Factor::Variable(
var_id,
array_select.to_index(),
width_select,
comptime,
)))
} else {
let comptime = Comptime::create_unknown(token);
ir::Expression::Term(Box::new(ir::Factor::Unknown(comptime)))
};
let dst = vec![VarPathSelect(
member_path.clone(),
dst_select.clone(),
token,
)];
let mut port_path = port_path.clone();
let mut member_name = member_path.0.clone();
member_name.remove(0);
port_path.append(&member_name);
ret.push((port_path.clone(), dst, expr));
}
}
} else if let Some(x) = &port.inst_port_item_opt {
let mut allow_anonymous = true;
if let Some(id) = component.ports.get(port_path)
&& let Some(variable) = component.variables.get(id)
&& variable.kind == VarKind::Input
{
allow_anonymous = false;
}
let (comptime, expr) = eval_expr(
context,
Some(port_type.clone()),
&x.expression,
allow_anonymous,
)?;
check_compatibility(context, port_type, &comptime, &token);
let dst: Vec<VarPathSelect> = Conv::conv(context, x.expression.as_ref())?;
ret.push((port_path.clone(), dst, expr));
} else if let Some((var_id, mut comptime)) = context.find_path(port_path) {
check_compatibility(context, port_type, &comptime, &token);
comptime.token = token;
let expr = ir::Expression::Term(Box::new(ir::Factor::Variable(
var_id,
VarIndex::default(),
VarSelect::default(),
comptime,
)));
let dst = vec![VarPathSelect(
port_path.clone(),
VarSelect::default(),
token,
)];
ret.push((port_path.clone(), dst, expr));
}
Ok(PortConnects {
connects: ret,
interface_binding,
})
}
pub fn expand_input_connect(
context: &mut Context,
variable: &ir::Variable,
dst: &[VarPathSelect],
expr: ir::Expression,
) -> Vec<ir::Expression> {
if variable.kind != VarKind::Input
|| variable.r#type.array.is_empty()
|| dst.len() != 1
|| !dst[0].is_array_range(context)
{
return vec![expr];
}
let exprs = dst[0].clone().to_expressions(context);
if exprs.len() == variable.r#type.total_array().unwrap_or(1) {
exprs
} else {
vec![expr]
}
}
pub fn insert_port_connect(
context: &mut Context,
variable: &ir::Variable,
dst: Vec<VarPathSelect>,
exprs: Vec<ir::Expression>,
inputs: &mut Vec<ir::InstInput>,
outputs: &mut Vec<ir::InstOutput>,
) {
match variable.kind {
VarKind::Input => {
inputs.push(ir::InstInput {
id: variable.id,
exprs,
});
}
VarKind::Output => {
let expr = &exprs[0];
if !expr.is_assignable() {
context.insert_error(AnalyzerError::unassignable_output(&expr.token_range()));
}
let dst = var_path_to_assign_destination(context, dst, false);
outputs.push(ir::InstOutput {
id: variable.id,
dst,
});
}
_ => (),
}
}
pub fn expand_connect(
context: &mut Context,
lhs: VarPathSelect,
rhs: VarPathSelect,
token: TokenRange,
) -> IrResult<Vec<ir::Statement>> {
let mut ret = vec![];
let (lhs_path, lhs_select, lhs_token) = lhs.into();
let (rhs_path, rhs_select, rhs_token) = rhs.into();
if let Some((_, lhs_comptime)) = context.find_path(&lhs_path)
&& let Some((_, rhs_comptime)) = context.find_path(&rhs_path)
{
let mut lhs_base = lhs_path.clone();
let mut rhs_base = rhs_path.clone();
if lhs_base.0.len() != 1 {
lhs_base.pop();
}
if rhs_base.0.len() != 1 {
rhs_base.pop();
}
let lhs_members = lhs_comptime
.r#type
.expand_modport(context, &lhs_base, lhs_token)?;
let rhs_members = rhs_comptime
.r#type
.expand_modport(context, &rhs_base, rhs_token)?;
let rhs_members: HashMap<_, _> = rhs_members
.into_iter()
.map(|(path, dir)| {
let mut key = path.clone();
key.remove_prelude(&rhs_base.0);
(key, (path, dir))
})
.collect();
for lhs in &lhs_members {
let mut key = lhs.0.clone();
key.remove_prelude(&lhs_base.0);
if let Some(rhs) = rhs_members.get(&key) {
let lhs_path = lhs.0.clone();
let rhs_path = rhs.0.clone();
let lhs_direction = lhs.1;
let rhs_direction = rhs.1;
let lhs = VarPathSelect(lhs_path, lhs_select.clone(), lhs_token);
let rhs = VarPathSelect(rhs_path, rhs_select.clone(), rhs_token);
let (dst, src) = if lhs_direction.is_output() && rhs_direction.is_input() {
(lhs, rhs)
} else if rhs_direction.is_output() && lhs_direction.is_input() {
(rhs, lhs)
} else {
return Ok(ret);
};
if let Some((_, mut dst_comptime)) = context.find_path(&dst.0)
&& let Some((_, mut src_comptime)) = context.find_path(&src.0)
{
dst_comptime.token = dst.2;
src_comptime.token = src.2;
check_clock_domain(context, &dst_comptime, &src_comptime, &token.beg);
}
if let Some(src) = src.to_expression(context)
&& let Some(dst) = dst.to_assign_destination(context, false)
{
let width = dst.total_width(context);
let statement = ir::Statement::Assign(ir::AssignStatement {
dst: vec![dst],
width,
expr: src,
token,
});
ret.push(statement);
} else {
}
}
}
}
Ok(ret)
}
pub fn expand_connect_const(
context: &mut Context,
lhs: VarPathSelect,
comptime: Comptime,
token: TokenRange,
) -> IrResult<Vec<ir::Statement>> {
let mut ret = vec![];
let (lhs_path, lhs_select, lhs_token) = lhs.into();
if let Some((_, lhs_comptime)) = context.find_path(&lhs_path) {
let mut lhs_base = lhs_path.clone();
if lhs_base.0.len() != 1 {
lhs_base.pop();
}
let lhs_members = lhs_comptime
.r#type
.expand_modport(context, &lhs_base, lhs_token)?;
for lhs in lhs_members {
if lhs.1.is_output() {
let dst = VarPathSelect(lhs.0, lhs_select.clone(), lhs_token);
let mut comptime = comptime.clone();
comptime.token = token;
let src = ir::Factor::Value(comptime);
let src = ir::Expression::Term(Box::new(src));
if let Some(dst) = dst.to_assign_destination(context, false) {
let width = dst.total_width(context);
let statement = ir::Statement::Assign(ir::AssignStatement {
dst: vec![dst],
width,
expr: src,
token,
});
ret.push(statement);
} else {
}
}
}
}
Ok(ret)
}
pub fn var_path_to_assign_destination(
context: &mut Context,
path: Vec<VarPathSelect>,
ignore_error: bool,
) -> Vec<ir::AssignDestination> {
path.into_iter()
.flat_map(|x| x.to_assign_destinations(context, ignore_error))
.collect()
}
fn get_function(context: &mut Context, path: &FuncPath, token: TokenRange) -> IrResult<FuncProto> {
fn conv_function(
context: &mut Context,
definition: DefinitionId,
path: &FuncPath,
) -> IrResult<()> {
let definition = definition_table::get(definition).unwrap();
match definition.as_ref() {
Definition::Function(x) => Conv::conv(context, (x, Some(path))),
Definition::ProtoFunction(x) => Conv::conv(context, (x, Some(path))),
_ => unreachable!(),
}
}
if !context.func_paths.contains_key(path) {
let symbol = symbol_table::get(path.sig.symbol).unwrap();
let (definition, is_global) = match &symbol.kind {
SymbolKind::Function(x) => (x.definition.unwrap(), x.is_global()),
SymbolKind::ModportFunctionMember(x) => {
let symbol = symbol_table::get(x.function).unwrap();
let SymbolKind::Function(x) = symbol.kind else {
unreachable!();
};
(x.definition.unwrap(), false)
}
_ => return Err(ir_error!(token)),
};
let array = if let Some((_, comptime)) = context.find_path(&path.path) {
comptime.r#type.array
} else {
Shape::default()
};
let is_local_func = context
.current_namespace()
.map(|namespace| symbol.namespace.included(&namespace))
.unwrap_or(false);
if is_local_func {
let ret = conv_function(context, definition, path);
ret?;
} else {
let generic_arg_paths = if is_global {
path.sig
.generic_parameters
.iter()
.filter_map(|(_, x)| x.to_var_path())
.collect()
} else {
vec![]
};
let mut local_context = Context::default();
local_context.var_id = context.var_id;
local_context.inherit(context);
local_context.extract_var_paths(context, &path.path, &array);
for path in &generic_arg_paths {
if let Some((var_id, comptime)) = context.find_path(path)
&& let Some(var) = context.variables.get(&var_id)
{
local_context
.var_paths
.insert(path.clone(), (var_id, comptime));
local_context.variables.insert(var_id, var.clone());
}
}
let ret = conv_function(&mut local_context, definition, path);
for path in &generic_arg_paths {
if let Some((var_id, _)) = local_context.var_paths.remove(path) {
local_context.variables.remove(&var_id);
}
}
context.extract_function(&mut local_context, &path.path, &array);
context.inherit(&mut local_context);
context.var_id = local_context.var_id;
ret?;
}
}
let Some(id) = context.func_paths.get(path) else {
return Err(ir_error!(token));
};
context
.functions
.get(id)
.map(|func| func.to_proto())
.ok_or_else(|| ir_error!(token))
}
pub fn function_call(
context: &mut Context,
path: &ExpressionIdentifier,
args: Arguments,
token: TokenRange,
) -> IrResult<ir::FunctionCall> {
let generic_path: GenericSymbolPath = path.into();
check_generic_refereence(context, &generic_path);
let mut sig =
Signature::from_path(context, generic_path.clone()).ok_or_else(|| ir_error!(token))?;
sig.normalize();
if context.function_call_stack.contains(&sig) {
context.insert_error(AnalyzerError::infinite_recursion(&token));
return Err(ir_error!(token));
}
let path: VarPathSelect = Conv::conv(context, path)?;
let (mut base_path, select, _) = path.into();
let index = select.to_index();
let index = index.eval_value(context);
base_path.pop();
let path = ir::FuncPath {
path: base_path.clone(),
sig: sig.clone(),
};
let map = symbol_table::function_call_generic_maps(&sig, &generic_path);
context.push_generic_map(map);
context.function_call_stack.push(sig.clone());
let ret = context.block(|c| {
let func = get_function(c, &path, token)?;
let (mut inputs, outputs) = args.to_function_args(c, &func, token)?;
let symbol = symbol_table::get(sig.symbol);
let mut effect = symbol
.as_ref()
.and_then(|symbol| match &symbol.kind {
SymbolKind::Function(func) => Some(RuntimeFunctionEffect {
external_write: func.has_side_effect_in(&c.config.defines),
written_outputs: func
.written_output_paths(&c.config.defines)
.into_iter()
.filter_map(|path| path.to_var_path())
.collect(),
}),
_ => None,
})
.unwrap_or_default();
if let Some(runtime_effect) = c.function_effect(&path) {
effect.external_write |= runtime_effect.external_write;
effect
.written_outputs
.extend(runtime_effect.written_outputs);
}
c.record_function_call_effect(&effect, &outputs);
if c.is_affiliated(Affiliation::AlwaysFf)
&& effect.external_write
&& let Some(symbol) = &symbol
{
let definition = TokenRange::from(&symbol.token);
let external_write = symbol_table::find_external_write(symbol, &sig, c);
let (calls, external_writes) = external_write
.map(|trace| (trace.calls, vec![trace.write]))
.unwrap_or_default();
c.insert_error(AnalyzerError::side_effect_function_call_in_always_ff(
&symbol.token.text.to_string(),
&token,
&definition,
&calls,
&external_writes,
));
}
if c.is_affiliated(Affiliation::AlwaysFf) {
for (formal, dsts) in &outputs {
let Some(formal_name) = formal.0.first() else {
continue;
};
if effect.written_paths_for(formal).next().is_none() {
continue;
}
let output = resource_table::get_str_value(*formal_name).unwrap_or_default();
let output_port = symbol.as_ref().and_then(|symbol| match &symbol.kind {
SymbolKind::Function(function) => function.ports.iter().find(|port| {
port.token.token.text == *formal_name
&& symbol_table::get(port.symbol).is_some_and(|symbol| {
matches!(symbol.kind, SymbolKind::Port(ref port) if port.direction == symbol::Direction::Output)
})
}),
_ => None,
});
let Some(output_port) = output_port else {
continue;
};
let declaration = TokenRange::from(&output_port.token.token);
for dst in dsts {
if c.get_variable_info(dst.id)
.is_some_and(|variable| variable.affiliation != Affiliation::AlwaysFf)
{
c.insert_error(AnalyzerError::function_output_in_always_ff(
&func.name.to_string(),
&output,
&dst.path.to_string(),
&dst.token,
Some(&declaration),
));
}
}
}
}
let mut comptime = func.r#type.clone();
comptime.token = token;
for expr in inputs.values_mut() {
let x = expr.eval_comptime(c, None);
check_clock_domain(c, &comptime, x, &token.beg);
comptime.clock_domain = comptime.clock_domain.merge(&x.clock_domain);
}
for dsts in outputs.values() {
for dst in dsts {
let mut dst_comptime = dst.comptime.clone();
dst_comptime.token = dst.token;
check_clock_domain(c, &dst_comptime, &comptime, &token.beg);
}
}
Ok(ir::FunctionCall {
id: func.id,
index,
comptime,
inputs,
outputs,
})
});
context.pop_generic_map();
context.function_call_stack.pop();
ret
}
pub fn check_compatibility(
context: &mut Context,
dst: &ir::Type,
src: &ir::Comptime,
token: &TokenRange,
) {
if dst.is_positive
&& let Ok(value) = src.get_value()
&& !(src.r#type.is_positive && value.is_xz())
&& (!value.is_positive() || value.is_semantically_not_positive())
{
context.insert_error(AnalyzerError::non_positive_value(
"non-positive",
&format_positive_type_name(dst).unwrap(),
token,
));
}
if (dst.is_clock() || dst.is_reset()) && context.in_inst_port {
check_implicit_clock_conversion(context, dst, src, token);
return;
}
if !dst.compatible(src, context.in_generic) {
let src_type = src.r#type.to_string();
let dst_type = dst.to_string();
context.insert_error(AnalyzerError::mismatch_assignment(
&src_type,
&dst_type,
token,
&[],
));
}
}
pub fn check_implicit_clock_conversion(
context: &mut Context,
dst: &ir::Type,
src: &Comptime,
token: &TokenRange,
) {
let dst_is_clock = dst.is_clock();
let dst_is_reset = dst.is_reset();
if !dst_is_clock && !dst_is_reset {
return;
}
if src.r#type.is_unknown() || src.r#type.is_systemverilog() || src.is_const {
return;
}
let ok = if dst_is_clock {
src.r#type.is_clock()
} else {
src.r#type.is_reset()
};
if !ok {
context.insert_error(AnalyzerError::implicit_clock_conversion(
&src.r#type.to_string(),
&dst.to_string(),
token,
&[],
));
}
}
pub fn check_module_with_unevaluable_generic_parameters(ident: &Identifier) -> bool {
if let Ok(symbol) = symbol_table::resolve(ident)
&& let SymbolKind::Module(x) = &symbol.found.kind
{
let mut ret = false;
for x in &x.generic_parameters {
let param = symbol_table::get(*x).unwrap();
if let SymbolKind::GenericParameter(x) = param.kind {
let has_default = x.default_value.is_some();
ret |= match &x.bound {
GenericBoundKind::Type => false,
GenericBoundKind::Inst(_) => false,
GenericBoundKind::Proto(x) => {
if x.kind.is_fixed() && !has_default {
true
} else if let TypeKind::UserDefined(x) = &x.kind
&& let Ok(symbol) = symbol_table::resolve(&x.path)
&& (matches!(symbol.found.kind, SymbolKind::Package(ref x) if x.is_proto)
|| matches!(&symbol.found.kind, SymbolKind::AliasPackage(x) if x.is_proto))
&& !has_default
{
true
} else {
false
}
}
};
} else {
unreachable!();
}
}
ret
} else {
false
}
}
#[cfg(test)]
pub fn parse_expression(s: &str) -> Expression {
use veryl_parser::parser::Parser;
use veryl_parser::veryl_walker::VerylWalker;
let src = format!(
r#"
module A {{
let a: bit = {s};
}}
"#
);
let parser = Parser::parse(&src, &"").unwrap();
struct Extractor(Option<Expression>);
impl VerylWalker for Extractor {
fn expression(&mut self, arg: &Expression) {
self.0 = Some(arg.clone());
}
}
let mut extractor = Extractor(None);
extractor.veryl(&parser.veryl);
extractor.0.unwrap()
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TbMethodCallPosition {
Statement,
Value,
}
fn conv_external_method_call(
context: &mut Context,
component: resource_table::StrId,
path: &GenericSymbolPath,
args: Option<ir::Arguments>,
inst_name: resource_table::StrId,
position: TbMethodCallPosition,
token: TokenRange,
) -> IrResult<(TbMethod, Option<u32>)> {
if path.paths.len() != 2 {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::MethodCallForm,
Some(component),
&token,
));
return Err(ir_error!(token));
}
if path.paths.iter().any(|x| !x.arguments.is_empty()) {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::MethodNoGenericArgs,
Some(component),
&token,
));
return Err(ir_error!(token));
}
let args: Vec<SystemFunctionInput> = match args {
Some(ir::Arguments::Positional(ref positional)) => positional
.iter()
.map(|arg| SystemFunctionInput(arg.0.clone()))
.collect(),
None | Some(ir::Arguments::Null) => Vec::new(),
Some(ir::Arguments::Named(_)) | Some(ir::Arguments::Mixed(..)) => {
context.insert_error(AnalyzerError::invalid_factor(
None,
"component method with named arguments",
&token,
&[],
));
return Err(ir_error!(token));
}
};
let method = resource_table::canonical_str_id(path.paths[path.paths.len() - 1].base.text);
let mut ret_width = None;
if let Some(manifest) = crate::component_manifest_table::get(component)
&& !manifest.methods.is_empty()
{
let name = method.to_string();
match manifest.method(&name) {
None => {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::UnknownMethod {
method: name.clone(),
},
Some(component),
&token,
));
}
Some(m) if m.args.len() != args.len() => {
context.insert_error(AnalyzerError::mismatch_function_arity(
&name,
m.args.len(),
args.len(),
&token,
));
}
Some(m) => {
match position {
TbMethodCallPosition::Statement if m.ret.is_some() => {
context.insert_error(AnalyzerError::unused_return(&name, &token));
}
TbMethodCallPosition::Value if m.ret.is_none() => {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::MethodReturnsNoValue {
method: name.clone(),
},
Some(component),
&token,
));
}
_ => {}
}
let inst_params = context
.tb_component_params
.get(&inst_name)
.cloned()
.unwrap_or_default();
let resolve_width =
|context: &mut Context, expr: &veryl_metadata::WidthExpr, what: &str| {
match veryl_metadata::eval_width_expr(expr, &inst_params) {
Some(w) if w > 0 => Some(w as u32),
_ => {
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::UnresolvableWidth {
expr: expr.to_string(),
what: what.to_string(),
},
Some(component),
&token,
));
None
}
}
};
if let Some(expr) = &m.ret_width.clone() {
ret_width = resolve_width(context, expr, &format!("method `{name}`'s return"));
const METHOD_RET_MAX_WIDTH: u32 = 512;
if let Some(w) = ret_width
&& w > METHOD_RET_MAX_WIDTH
{
context.insert_error(AnalyzerError::component_interface_mismatch(
ComponentInterfaceMismatchKind::MethodReturnTooWide {
method: name.clone(),
declared: w,
max: METHOD_RET_MAX_WIDTH,
},
Some(component),
&token,
));
}
}
}
}
}
Ok((TbMethod::Component { method, args }, ret_width))
}
pub fn tb_method_call(
context: &mut Context,
expr_id: &ExpressionIdentifier,
func_call: &FunctionCall,
token: TokenRange,
position: TbMethodCallPosition,
) -> IrResult<Option<ir::Statement>> {
let path: GenericSymbolPath = expr_id.into();
if path.paths.len() < 2 {
return Ok(None);
}
let inst_name = path.paths[0].base.text;
let inst_path = SymbolPath::new(&[inst_name]);
let Some((inst_scope, inst_define_context)) = crate::scope::token_scope(path.paths[0].base.id)
else {
return Ok(None);
};
let inst_symbol = match symbol_table::resolve(SymbolPathNamespace::from_scope(
inst_path,
inst_scope,
inst_define_context,
)) {
Ok(s) => s,
Err(_) => {
return Ok(None);
}
};
let type_path = match &inst_symbol.found.kind {
SymbolKind::Instance(x) => &x.type_name,
SymbolKind::Variable(x) => {
if let TypeKind::UserDefined(ref ud) = x.r#type.kind {
&ud.path
} else {
return Ok(None);
}
}
_ => {
return Ok(None);
}
};
let type_symbol =
match symbol_table::resolve_generic_structural(type_path, &inst_symbol.found.namespace) {
Ok(s) => s,
Err(_) => {
return Ok(None);
}
};
let tb_kind = match &type_symbol.found.kind {
SymbolKind::TbComponent(x) => &x.kind,
_ => {
return Ok(None);
}
};
let method_name = resource_table::get_str_value(path.paths[path.paths.len() - 1].base.text);
let method_name = method_name.as_deref().unwrap_or("");
let args = if let Some(ref arg_list) = func_call.function_call_opt {
Some(argument_list(context, arg_list.argument_list.as_ref())?)
} else {
None
};
let mut ret_width = None;
let mut ret_signed = false;
let method = match (tb_kind, method_name) {
(TbComponentKind::ClockGen, "next") => {
let count = if let Some(ir::Arguments::Positional(ref positional)) = args
&& !positional.is_empty()
{
let mut count = positional[0].0.clone();
count.eval_comptime(context, None);
Some(count)
} else {
None
};
let period = context
.tb_clock_period
.get(&inst_name)
.cloned()
.map(Box::new);
TbMethod::ClockNext { count, period }
}
(TbComponentKind::ResetGen, "assert") => {
let clock = context
.tb_reset_clock
.get(&inst_name)
.copied()
.ok_or_else(|| ir_error!(token))?;
let duration = if let Some(ir::Arguments::Positional(ref positional)) = args
&& let Some(arg) = positional.first()
{
let mut duration = arg.0.clone();
duration.eval_comptime(context, None);
Some(duration)
} else {
context.tb_reset_cycles.get(&inst_name).cloned()
};
TbMethod::ResetAssert { clock, duration }
}
(TbComponentKind::File, "open") | (TbComponentKind::File, "append") => {
let name = if let Some(ir::Arguments::Positional(ref positional)) = args
&& let Some(arg) = positional.first()
{
SystemFunctionInput(arg.0.clone())
} else {
let actual = if let Some(ir::Arguments::Positional(ref positional)) = args {
positional.len()
} else {
0
};
context.insert_error(AnalyzerError::mismatch_function_arity(
method_name,
1,
actual,
&token,
));
return Err(ir_error!(token));
};
TbMethod::FileOpen {
name,
append: method_name == "append",
}
}
(TbComponentKind::File, "write") => {
let args = if let Some(ir::Arguments::Positional(ref positional)) = args {
positional
.iter()
.map(|arg| SystemFunctionInput(arg.0.clone()))
.collect()
} else {
Vec::new()
};
TbMethod::FileWrite { args }
}
(TbComponentKind::File, "close") => TbMethod::FileClose,
(TbComponentKind::File, "flush") => TbMethod::FileFlush,
(TbComponentKind::Random, "seed") => {
let value = random_arg(context, &args, 0, method_name, 1, &token)?;
TbMethod::RandomSeed { value }
}
(TbComponentKind::Random, "get") => {
let (width, signed) = resolve_random_elem_type(context, type_path);
ret_width = Some(width);
ret_signed = signed;
TbMethod::RandomGet { width, signed }
}
(TbComponentKind::Random, "get_range") => {
let min = random_arg(context, &args, 0, method_name, 2, &token)?;
let max = random_arg(context, &args, 1, method_name, 2, &token)?;
let (width, signed) = resolve_random_elem_type(context, type_path);
ret_width = Some(width);
ret_signed = signed;
TbMethod::RandomGetRange {
min,
max,
width,
signed,
}
}
(TbComponentKind::Random, "get_seed") => {
ret_width = Some(64);
TbMethod::RandomGetSeed
}
(TbComponentKind::External(component), _) => {
let (method, rw) = conv_external_method_call(
context, *component, &path, args, inst_name, position, token,
)?;
ret_width = rw;
method
}
_ => return Err(ir_error!(token)),
};
Ok(Some(ir::Statement::TbMethodCall(TbMethodCall {
inst: inst_name,
method,
ret: None,
ret_strict: false,
ret_width,
ret_signed,
})))
}
fn random_arg(
context: &mut Context,
args: &Option<ir::Arguments>,
i: usize,
method_name: &str,
arity: usize,
token: &TokenRange,
) -> IrResult<ir::Expression> {
if let Some(ir::Arguments::Positional(positional)) = args
&& let Some(arg) = positional.get(i)
{
let mut expr = arg.0.clone();
expr.eval_comptime(context, None);
Ok(expr)
} else {
let actual = if let Some(ir::Arguments::Positional(positional)) = args {
positional.len()
} else {
0
};
context.insert_error(AnalyzerError::mismatch_function_arity(
method_name,
arity,
actual,
token,
));
Err(ir_error!(*token))
}
}
fn resolve_random_elem_type(context: &mut Context, type_path: &GenericSymbolPath) -> (u32, bool) {
let resolved = context.resolve_path(type_path.clone());
if let Some(arg) = resolved.paths.last().and_then(|p| p.arguments.first())
&& let Ok(ty) = eval_type(context, &arg.clone(), TypePosition::Variable)
&& let Some(width) = ty.total_width()
{
(width as u32, ty.signed)
} else {
(32, false)
}
}
pub fn hoist_component_method_call(
context: &mut Context,
mut method_call: TbMethodCall,
token: TokenRange,
) -> IrResult<ir::Expression> {
if context.tb_hoist.is_none() {
context.insert_error(AnalyzerError::invalid_factor(
None,
"component method call outside testbench statements",
&token,
&[],
));
return Err(ir_error!(token));
}
let width = method_call.ret_width.unwrap_or(64) as usize;
let signed = method_call.ret_signed;
let name = format!("__tb_method_ret_{}", context.tb_hoist_count);
context.tb_hoist_count += 1;
let path = VarPath::new(resource_table::insert_str(&name));
let mut r#type = ir::Type::new(ir::TypeKind::Bit);
r#type.set_concrete_width(Shape::new(vec![Some(width)]));
r#type.signed = signed;
let comptime = Comptime::from_type(r#type.clone(), ClockDomain::None, token);
let id = context.insert_var_path(path.clone(), comptime.clone());
let array_limit = context.config.evaluate_array_limit;
let variable = Variable::new(
id,
path.clone(),
VarKind::Variable,
r#type,
vec![Value::new_x(width, signed)],
context.get_affiliation(),
&token,
array_limit,
);
context.insert_variable(id, variable);
method_call.ret = Some(Box::new(ir::AssignDestination {
id,
path,
index: VarIndex::default(),
select: VarSelect::default(),
comptime: comptime.clone(),
token,
}));
method_call.ret_strict = true;
context
.tb_hoist
.as_mut()
.unwrap()
.push(ir::Statement::TbMethodCall(method_call));
Ok(ir::Expression::Term(Box::new(ir::Factor::Variable(
id,
VarIndex::default(),
VarSelect::default(),
comptime,
))))
}
pub fn single_function_call_factor(
expr: &Expression,
) -> Option<(&ExpressionIdentifier, &FunctionCall)> {
let if_expr = &*expr.if_expression;
if !if_expr.if_expression_list.is_empty() {
return None;
}
let e01 = &*if_expr.expression01;
if !e01.expression01_list.is_empty() {
return None;
}
let e02 = &*e01.expression02;
if !e02.expression02_list.is_empty() || e02.expression02_opt.is_some() {
return None;
}
if let Factor::IdentifierFactor(x) = e02.factor.as_ref()
&& let Some(opt) = &x.identifier_factor.identifier_factor_opt
&& let IdentifierFactorOptGroup::FunctionCall(call) =
opt.identifier_factor_opt_group.as_ref()
{
return Some((
x.identifier_factor.expression_identifier.as_ref(),
call.function_call.as_ref(),
));
}
None
}
#[cfg(test)]
pub fn parse_number(s: &str) -> Number {
use veryl_parser::parser::Parser;
use veryl_parser::veryl_walker::VerylWalker;
let src = format!(
r#"
module A {{
let a: bit = {s};
}}
"#
);
let parser = Parser::parse(&src, &"").unwrap();
struct Extractor(Option<Number>);
impl VerylWalker for Extractor {
fn number(&mut self, arg: &Number) {
self.0 = Some(arg.clone());
}
}
let mut extractor = Extractor(None);
extractor.veryl(&parser.veryl);
extractor.0.unwrap()
}