@@ -1351,6 +1351,172 @@ fn wide_128_ff_validate_pool_agnostic() {
13511351 );
13521352}
13531353
1354+ #[test]
1355+ fn probe_r4_wide_dynsel_store() {
1356+ // ROUND 4 (Vortex/SIMT): dynamic bit-select store into a WIDE (>64-bit)
1357+ // packed-row target `logic<N, W>[idx] = row` — aot_c_validate dual-runs
1358+ // cc vs Cranelift each cycle. Non-power-of-2 element width (ew=31) and a
1359+ // window that fits exactly ((ne-1)*ew + win == dst_width).
1360+ if !crate::backend::aot_c::cc_available() {
1361+ return;
1362+ }
1363+ let code = r#"
1364+ module Top (
1365+ idx: input logic<2>,
1366+ v: input logic<31>,
1367+ base: input logic<124>,
1368+ o: output logic<124>,
1369+ ) {
1370+ var t: logic<4, 31>;
1371+ always_comb {
1372+ t = base;
1373+ t[idx] = v;
1374+ }
1375+ assign o = t;
1376+ }
1377+ "#;
1378+ let config = aot_native_validate_config();
1379+ let ir = analyze(code, &config);
1380+ assert!(
1381+ ir.whole_comb.is_some(),
1382+ "wide dynsel-store comb must be AOT-C-native"
1383+ );
1384+ let mut sim = Simulator::new(ir, None);
1385+ for idx in 0u64..4 {
1386+ sim.set("base", Value::new(0, 124, false));
1387+ sim.set("idx", Value::new(idx, 2, false));
1388+ sim.set("v", Value::new(0x7F, 31, false));
1389+ sim.step(&Event::Clock(VarId::SYNTHETIC));
1390+ // element `idx` = 0x7F (7 bits), rest 0.
1391+ let o = sim.get("o").unwrap();
1392+ let expect = Value::from_u128(0x7Fu128 << (31 * idx), 0, 124, false);
1393+ assert_eq!(o, expect, "idx={idx}");
1394+ }
1395+ }
1396+
1397+ #[test]
1398+ fn probe_r4_wide_assign_dynamic() {
1399+ // ROUND 4: dynamic-INDEX store into a 65..128-bit unpacked-array element
1400+ // `logic<70> [N]; arr[idx] = v`. aot_c_validate dual-runs cc vs Cranelift.
1401+ if !crate::backend::aot_c::cc_available() {
1402+ return;
1403+ }
1404+ let code = r#"
1405+ module Top (
1406+ clk: input clock,
1407+ idx: input logic<2>,
1408+ v: input logic<70>,
1409+ o: output logic<70>,
1410+ ) {
1411+ var arr: logic<70> [4];
1412+ always_ff {
1413+ for i in 0..4 {
1414+ arr[i] = 70'h0;
1415+ }
1416+ arr[idx] = v;
1417+ }
1418+ assign o = arr[3];
1419+ }
1420+ "#;
1421+ let config = aot_native_validate_config();
1422+ let ir = analyze(code, &config);
1423+ assert!(
1424+ !ir.whole_events.is_empty(),
1425+ "wide AssignDynamic event must be AOT-C-native"
1426+ );
1427+ let mut sim = Simulator::new(ir, None);
1428+ let clk = sim.get_clock("clk").unwrap();
1429+ let bigv: u128 = (1u128 << 69) | (1u128 << 40) | 0xABCD;
1430+ for idx in 0u64..4 {
1431+ sim.set("idx", Value::new(idx, 2, false));
1432+ sim.set("v", Value::from_u128(bigv, 0, 70, false));
1433+ sim.step(&clk);
1434+ let o = sim.get("o").unwrap();
1435+ let expect = if idx == 3 {
1436+ Value::from_u128(bigv, 0, 70, false)
1437+ } else {
1438+ Value::new(0, 70, false)
1439+ };
1440+ assert_eq!(o, expect, "idx={idx}");
1441+ }
1442+ }
1443+
1444+ #[test]
1445+ fn probe_r4_shift_left_128() {
1446+ // ROUND 4: 65..128-bit unsigned shift-LEFT with a narrow (≤64-bit) left
1447+ // operand — `(a << s)` promoted to __uint128_t in a 128-bit context (a
1448+ // u64 `<<` would truncate). aot_c_validate dual-runs cc vs Cranelift.
1449+ if !crate::backend::aot_c::cc_available() {
1450+ return;
1451+ }
1452+ let code = r#"
1453+ module Top (
1454+ a: input logic<32>,
1455+ s: input logic<7>,
1456+ o: output logic<128>,
1457+ ) {
1458+ assign o = (a << s) + 128'h1;
1459+ }
1460+ "#;
1461+ let config = aot_native_validate_config();
1462+ let ir = analyze(code, &config);
1463+ assert!(
1464+ ir.whole_comb.is_some(),
1465+ "128-bit shift comb must be AOT-C-native"
1466+ );
1467+ let mut sim = Simulator::new(ir, None);
1468+ for s in [0u64, 1, 31, 63, 64, 96, 100, 127] {
1469+ sim.set("a", Value::new(0xDEAD_BEEF, 32, false));
1470+ sim.set("s", Value::new(s, 7, false));
1471+ sim.step(&Event::Clock(VarId::SYNTHETIC));
1472+ let o = sim.get("o").unwrap();
1473+ let expect_val: u128 = (0xDEAD_BEEFu128 << s).wrapping_add(1);
1474+ // mask to 128 bits (u128 already), the shift drops bits >= 128.
1475+ let expect = Value::from_u128(expect_val, 0, 128, false);
1476+ assert_eq!(o, expect, "s={s}");
1477+ }
1478+ }
1479+
1480+ #[test]
1481+ fn probe_r4_wide_src_rhs_select() {
1482+ // ROUND 4: windowed copy `dst = wide_src[hi:lo]` — a static rhs_select of
1483+ // a >128-bit source into both wide (176-bit) and narrow (48-bit) targets.
1484+ // aot_c_validate dual-runs cc vs Cranelift.
1485+ if !crate::backend::aot_c::cc_available() {
1486+ return;
1487+ }
1488+ let code = r#"
1489+ module Top (
1490+ src: input logic<472>,
1491+ wide: output logic<176>,
1492+ narr: output logic<48>,
1493+ ) {
1494+ assign wide = src[351:176];
1495+ assign narr = src[119:72];
1496+ }
1497+ "#;
1498+ let config = aot_native_validate_config();
1499+ let ir = analyze(code, &config);
1500+ assert!(
1501+ ir.whole_comb.is_some(),
1502+ "wide-src rhs_select comb must be AOT-C-native"
1503+ );
1504+ let mut sim = Simulator::new(ir, None);
1505+ // Distinctive payload: low word pattern + a set bit near the top window.
1506+ let mut src = Value::new(0, 472, false);
1507+ // set bits so both windows carry data: bit 72.. and 176..
1508+ src = Value::from_u128((0x1234_5678u128) << 72, 0, 472, false);
1509+ sim.set("src", src);
1510+ sim.step(&Event::Clock(VarId::SYNTHETIC));
1511+ // narr = src[119:72] = 0x1234_5678 low 48 bits.
1512+ assert_eq!(
1513+ sim.get("narr").unwrap(),
1514+ Value::new((0x1234_5678u64) & ((1u64 << 48) - 1), 48, false)
1515+ );
1516+ // wide = src[351:176] — the payload only reaches bit ~104, so window is 0.
1517+ assert_eq!(sim.get("wide").unwrap(), Value::new(0, 176, false));
1518+ }
1519+
13541520#[test]
13551521fn wide_256_reduce_and_nested_unary() {
13561522 // Wide unary REDUCTIONS (&a / |a / ^a → 1-bit) exercise
0 commit comments