Skip to content

Commit ade3e5f

Browse files
authored
Merge pull request #3145 from veryl-lang/pr/01-analyzer-assign-rhs-width
fix(analyzer): size assignment RHS against the LHS bit-select width
2 parents ee52384 + 2fcaf4f commit ade3e5f

4 files changed

Lines changed: 70 additions & 23 deletions

File tree

crates/analyzer/src/conv/declaration.rs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ use crate::conv::checker::inst::check_inst;
1515
use crate::conv::checker::modport::{check_modport, check_modport_default, check_modport_in_port};
1616
use crate::conv::checker::port::{check_direction, check_port_default_value, check_port_direction};
1717
use crate::conv::utils::{
18-
TypePosition, check_assign_clock_domain, eval_array_range_assign, eval_assign_statement,
19-
eval_clock, eval_const_assign, eval_expr, eval_factor_symbol, eval_factor_symbol_external,
20-
eval_generate_for_range, eval_reset, eval_size, eval_type, eval_variable, expand_connect,
21-
expand_connect_const, get_component, get_overridden_params, get_port_connects, get_return_str,
22-
insert_port_connect, try_infer_decl_type, try_infer_var_assign, var_path_to_assign_destination,
18+
TypePosition, assign_rhs_context_type, check_assign_clock_domain, eval_array_range_assign,
19+
eval_assign_statement, eval_clock, eval_const_assign, eval_expr, eval_factor_symbol,
20+
eval_factor_symbol_external, eval_generate_for_range, eval_reset, eval_size, eval_type,
21+
eval_variable, expand_connect, expand_connect_const, get_component, get_overridden_params,
22+
get_port_connects, get_return_str, insert_port_connect, try_infer_decl_type,
23+
try_infer_var_assign, var_path_to_assign_destination,
2324
};
2425
use crate::conv::{Affiliation, Context, Conv};
2526
use crate::definition_table::{self, Definition};
@@ -948,12 +949,8 @@ impl Conv<&AssignDeclaration> for ir::Declaration {
948949
let mut expr = if let Some(inferred) = inferred {
949950
inferred
950951
} else {
951-
eval_expr(
952-
context,
953-
Some(dst.comptime.r#type.clone()),
954-
&value.expression,
955-
false,
956-
)?
952+
let ctx_type = assign_rhs_context_type(context, &dst);
953+
eval_expr(context, Some(ctx_type), &value.expression, false)?
957954
};
958955

959956
let statements = eval_assign_statement(context, &mut dst, &mut expr, token)?;

crates/analyzer/src/conv/statement.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::analyzer_error::{ComponentInterfaceMismatchKind, MismatchTypeKind};
22
use crate::conv::utils::{
3-
TbMethodCallPosition, TypePosition, argument_list, build_for_range, build_for_statement,
4-
case_patterns, check_assign_clock_domain, eval_array_range_assign, eval_assign_statement,
5-
eval_expr, eval_variable, expand_connect, expand_connect_const, function_call, get_return_str,
6-
hoist_component_method_call, single_function_call_factor, switch_condition, tb_method_call,
7-
try_infer_decl_type, try_infer_var_assign,
3+
TbMethodCallPosition, TypePosition, argument_list, assign_rhs_context_type, build_for_range,
4+
build_for_statement, case_patterns, check_assign_clock_domain, eval_array_range_assign,
5+
eval_assign_statement, eval_expr, eval_variable, expand_connect, expand_connect_const,
6+
function_call, get_return_str, hoist_component_method_call, single_function_call_factor,
7+
switch_condition, tb_method_call, try_infer_decl_type, try_infer_var_assign,
88
};
99
use crate::conv::{Context, Conv};
1010
use crate::ir::{
@@ -357,12 +357,8 @@ impl Conv<&IdentifierStatement> for ir::StatementBlock {
357357
let mut expr = if let Some(inferred) = inferred {
358358
inferred
359359
} else {
360-
eval_expr(
361-
context,
362-
Some(dst.comptime.r#type.clone()),
363-
&x.assignment.expression,
364-
false,
365-
)?
360+
let ctx_type = assign_rhs_context_type(context, &dst);
361+
eval_expr(context, Some(ctx_type), &x.assignment.expression, false)?
366362
};
367363

368364
let statements =
@@ -392,9 +388,10 @@ impl Conv<&IdentifierStatement> for ir::StatementBlock {
392388
if let Some(dst) = dst.to_assign_destination(context, false)
393389
&& let Some(src) = src.to_expression(context)
394390
{
391+
let ctx_type = assign_rhs_context_type(context, &dst);
395392
let (_, expr) = eval_expr(
396393
context,
397-
Some(dst.comptime.r#type.clone()),
394+
Some(ctx_type),
398395
&x.assignment.expression,
399396
false,
400397
)?;

crates/analyzer/src/conv/utils.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,29 @@ pub fn eval_assign_statement(
650650
Ok(ret)
651651
}
652652

653+
/// IEEE 1800 sizes an assignment RHS against the LHS *select*, not the whole
654+
/// variable. Only observable when the RHS is no wider than the select, since
655+
/// the context is the max of the two: with an 8-bit `y`, `x[7:0] = (y << 4)
656+
/// >> 4` is 0x0d at 8 bits and 0xcd at a 32-bit `x`'s width.
657+
pub fn assign_rhs_context_type(context: &mut Context, dst: &ir::AssignDestination) -> ir::Type {
658+
let full = dst.comptime.r#type.clone();
659+
if dst.select.is_empty() || dst.comptime.part_select.is_some() {
660+
return full;
661+
}
662+
if !matches!(full.kind, ir::TypeKind::Logic | ir::TypeKind::Bit) {
663+
return full;
664+
}
665+
let Some(w) = dst.total_width(context) else {
666+
return full;
667+
};
668+
if w == 0 || full.total_width().is_none_or(|fw| w >= fw) {
669+
return full;
670+
}
671+
let mut t = full;
672+
t.set_concrete_width(Shape::new(vec![Some(w)]));
673+
t
674+
}
675+
653676
/// Expand an array-range assignment LHS (`o[0+:N] = '{...}`) into one assignment
654677
/// per covered element (recursing inner dims for multi-dim arrays). Returns `None`
655678
/// to defer to the scalar path when the LHS isn't a range or the RHS isn't an array

crates/simulator/src/tests/simulation.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20297,3 +20297,33 @@ fn package_const_select_multi_dim_width() {
2029720297
}
2029820298
}
2029920299
}
20300+
20301+
#[test]
20302+
fn assign_rhs_sized_by_lhs_bit_select_width() {
20303+
// Regression: sized against the 32-bit variable rather than the 8-bit
20304+
// select, the shift pair was lossless and stored 0xcd. 0x0d is what the
20305+
// emitted SV produces under both iverilog and Verilator.
20306+
let code = r#"
20307+
module Top (
20308+
y: input logic<8>,
20309+
o: output logic<32>,
20310+
) {
20311+
always_comb {
20312+
o = 0;
20313+
o[7:0] = (y << 4) >> 4;
20314+
}
20315+
}
20316+
"#;
20317+
20318+
for config in Config::all() {
20319+
let ir = analyze(code, &config);
20320+
let mut sim = Simulator::new(ir, None);
20321+
sim.set("y", Value::new(0xcd, 8, false));
20322+
sim.step(&Event::Clock(VarId::SYNTHETIC));
20323+
assert_eq!(
20324+
sim.get("o").unwrap(),
20325+
Value::new(0x0d, 32, false),
20326+
"config={config:?}"
20327+
);
20328+
}
20329+
}

0 commit comments

Comments
 (0)