mirror of
https://github.com/diodeinc/pcb.git
synced 2026-09-14 20:17:19 +08:00
14ebd87fd8
Delete the custom JSON-to-Starlark converter that truncated integers to i32 and allocate with Starlark's heap.alloc instead. Amp-Thread-ID: https://ampcode.com/threads/T-01a05503-2085-769a-86c0-7eebfe657db2
2174 lines
64 KiB
Rust
2174 lines
64 KiB
Rust
use crate::common::{InMemoryFileProvider, eval_zen, stdlib_test_files, test_resolution};
|
|
use pcb_zen_core::lang::error::CategorizedDiagnostic;
|
|
use pcb_zen_core::lang::io_direction::IoDirection;
|
|
use pcb_zen_core::lang::net::FrozenNetValue;
|
|
use pcb_zen_core::{DiagnosticsPass, SortPass};
|
|
use starlark::collections::SmallMap;
|
|
use starlark::errors::EvalSeverity;
|
|
use starlark::values::ValueLike;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
fn redundancy_advice<'a>(
|
|
diagnostics: &'a pcb_zen_core::Diagnostics,
|
|
body_substring: &str,
|
|
) -> Vec<&'a pcb_zen_core::Diagnostic> {
|
|
diagnostics
|
|
.iter()
|
|
.filter(|diag| {
|
|
diag.body.contains(body_substring)
|
|
&& diag
|
|
.downcast_error_ref::<CategorizedDiagnostic>()
|
|
.is_some_and(|c| c.kind == "style.redundant_name")
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
snapshot_eval!(config_default_implies_optional_in_signature, {
|
|
"test.zen" => r#"
|
|
# No explicit optional, but default is provided.
|
|
led_color = config(str, default = "green")
|
|
"#
|
|
});
|
|
|
|
#[test]
|
|
fn json_config_preserves_numeric_inputs() {
|
|
for (typ, raw) in [
|
|
("int", "0"),
|
|
("int", "2147483647"),
|
|
("int", "2147483648"),
|
|
("int", "-2147483648"),
|
|
("int", "-2147483649"),
|
|
("int", "9007199254740991"),
|
|
("int", "-9007199254740991"),
|
|
("int", "9223372036854775807"),
|
|
("int", "-9223372036854775808"),
|
|
("int", "18446744073709551615"),
|
|
("float", "-1.25"),
|
|
(
|
|
"dict",
|
|
r#"{"nested": {"values": [0, -2147483649, 18446744073709551615, -1.25, null, true, false, "μ"]}}"#,
|
|
),
|
|
] {
|
|
let mut files = stdlib_test_files();
|
|
files.insert("test.zen".to_string(), format!("value = config({typ})"));
|
|
let file_provider = Arc::new(InMemoryFileProvider::new(files));
|
|
let mut ctx = pcb_zen_core::EvalContext::new(file_provider, test_resolution())
|
|
.set_source_path(PathBuf::from("test.zen"))
|
|
.set_inject_prelude(false);
|
|
let input: serde_json::Value = serde_json::from_str(raw).unwrap();
|
|
ctx.set_json_inputs(SmallMap::from_iter([("value".to_string(), input.clone())]));
|
|
|
|
let result = ctx.eval();
|
|
assert!(
|
|
result.is_success(),
|
|
"{typ} input {raw} failed: {:?}",
|
|
result.diagnostics
|
|
);
|
|
let output = result.output.unwrap();
|
|
let value = output.star_module.get("value").unwrap();
|
|
assert_eq!(value.value().get_type(), typ, "input {raw}");
|
|
let actual: serde_json::Value =
|
|
serde_json::from_str(&value.value().to_json().unwrap()).unwrap();
|
|
assert_eq!(actual, input, "{typ} input {raw} changed during evaluation");
|
|
}
|
|
}
|
|
|
|
snapshot_eval!(io_template_infers_signature_and_default, {
|
|
"test.zen" => r#"
|
|
Power = builtin.net_type("Power", voltage=Voltage)
|
|
|
|
VDD = io(Power("VDD", voltage="3.3V"))
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(io_template_enforces_voltage_compatibility, {
|
|
"Module.zen" => r#"
|
|
Power = builtin.net_type("Power", voltage=Voltage)
|
|
|
|
VDD = io(Power("VDD", voltage="1.8V to 3.6V"))
|
|
|
|
Component(
|
|
name = "U1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"VDD": "1"},
|
|
pins = {"VDD": VDD},
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
vdd = Mod.Power("VIN", voltage="5V")
|
|
Mod(name = "child", VDD = vdd)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(io_template_rejects_plain_net_input, {
|
|
"Module.zen" => r#"
|
|
Power = builtin.net_type("Power", voltage=Voltage)
|
|
|
|
VDD = io(Power())
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
vdd = Net("VDD")
|
|
Mod(name = "child", VDD = vdd)
|
|
"#
|
|
});
|
|
|
|
#[test]
|
|
fn io_rejects_template_positional_with_default() {
|
|
let result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
Power = builtin.net_type("Power", voltage=Voltage)
|
|
|
|
VDD = io(
|
|
Power("VDD", voltage="3.3V"),
|
|
default=Power("ALT", voltage="3.3V"),
|
|
)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!result.is_success(),
|
|
"expected eval failure, got diagnostics: {:?}",
|
|
result.diagnostics
|
|
);
|
|
assert!(
|
|
result.diagnostics.iter().any(|diag| diag.body.contains(
|
|
"io() cannot accept both a template positional argument and `default=`; remove `default=`"
|
|
)),
|
|
"expected ambiguous io() template/default diagnostic, got: {:?}",
|
|
result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn io_bound_template_skips_implicit_checks() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
Power = builtin.net_type("Power", voltage=Voltage)
|
|
|
|
VIN = Power(voltage="1.8V to 3.6V")
|
|
VDD = io(VIN)
|
|
|
|
Component(
|
|
name = "U1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"VDD": "1"},
|
|
pins = {"VDD": VDD},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
Mod(name = "child", VDD = Mod.Power("SUPPLY", voltage="5V"))
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"bound template should not fail io() resolution: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
eval_result
|
|
.diagnostics
|
|
.warnings()
|
|
.iter()
|
|
.all(|diag| !diag.body.contains("template voltage")),
|
|
"did not expect implicit template-voltage warning, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn io_derived_template_skips_implicit_checks() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
Power = builtin.net_type("Power", voltage=Voltage)
|
|
|
|
VIN = io(Power(voltage="1.8V to 3.6V"))
|
|
EN = io(Net(VIN))
|
|
|
|
Component(
|
|
name = "U1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"VIN": "1", "EN": "2"},
|
|
pins = {"VIN": VIN, "EN": EN},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
vin = Mod.Power("VIN", voltage="3.3V")
|
|
en = Mod.Power("ENABLE", voltage="5V")
|
|
Mod(name = "child", VIN = vin, EN = en)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"derived template should not fail io() resolution: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
eval_result
|
|
.diagnostics
|
|
.warnings()
|
|
.iter()
|
|
.all(|diag| !diag.body.contains("template voltage")),
|
|
"did not expect implicit template-voltage warning, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn io_template_implicit_check_warning_preserves_child_instantiation() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
Power = builtin.net_type("Power", voltage=Voltage)
|
|
|
|
VDD = io(Power("VDD", voltage="1.8V to 3.6V"))
|
|
|
|
Component(
|
|
name = "U1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"VDD": "1"},
|
|
pins = {"VDD": VDD},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
vdd = Mod.Power("VIN", voltage="5V")
|
|
Mod(name = "child", VDD = vdd)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
eval_result.output.is_some(),
|
|
"implicit-check failures should not abort eval output: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"implicit-check failures should now warn without becoming errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
!eval_result.diagnostics.warnings().is_empty(),
|
|
"expected warning diagnostic for implicit-check failure"
|
|
);
|
|
assert!(
|
|
format!("{:?}", eval_result.diagnostics)
|
|
.contains("Input 'VDD' voltage 5V is not within template voltage"),
|
|
"expected implicit-check warning, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let output = eval_result.output.expect("expected eval output");
|
|
let module_tree = output.module_tree();
|
|
let child_module = module_tree
|
|
.values()
|
|
.find(|module| module.path().to_string() == "child")
|
|
.expect("expected instantiated child module");
|
|
let component = child_module
|
|
.components()
|
|
.find(|component| component.name() == "U1")
|
|
.expect("expected child component despite implicit-check error");
|
|
let net = component
|
|
.connections()
|
|
.get("VDD")
|
|
.and_then(|value| value.downcast_ref::<FrozenNetValue>())
|
|
.expect("expected connected VDD net");
|
|
|
|
assert_eq!(net.name(), "VIN");
|
|
}
|
|
|
|
#[test]
|
|
fn io_generated_net_warning_uses_io_declaration_span() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"power_pin.kicad_sym".to_string(),
|
|
r#"(kicad_symbol_lib
|
|
(version 20211014)
|
|
(generator "test")
|
|
(symbol "PowerPin"
|
|
(property "Reference" "U")
|
|
(symbol "PowerPin_0_1"
|
|
(pin power_in line
|
|
(at 0 0 0)
|
|
(length 2.54)
|
|
(name "VCC")
|
|
(number "1")
|
|
)
|
|
)
|
|
)
|
|
)"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
symbol = Symbol(library = "power_pin.kicad_sym")
|
|
|
|
VDD = io(Net())
|
|
|
|
Component(
|
|
name = "U1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
symbol = symbol,
|
|
pins = {
|
|
"VCC": VDD,
|
|
},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let warnings = eval_result.diagnostics.warnings();
|
|
let warning = warnings
|
|
.iter()
|
|
.find(|diag| {
|
|
diag.downcast_error_ref::<CategorizedDiagnostic>()
|
|
.is_some_and(|c| c.kind == "pin.power_net")
|
|
})
|
|
.expect("expected pin.power_net warning");
|
|
|
|
assert_eq!(warning.path, "test.zen");
|
|
assert!(
|
|
warning.span.is_some(),
|
|
"expected pin.power_net warning to have an io() declaration span, got: {:?}",
|
|
warning
|
|
);
|
|
}
|
|
|
|
snapshot_eval!(config_optional_false_missing_emits_error_diagnostic, {
|
|
"Module.zen" => r#"
|
|
led_color = config(str, default = "green", optional = False)
|
|
|
|
Component(
|
|
name = "D1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"A": "1", "K": "2"},
|
|
pins = {"A": Net("VCC"), "K": Net("GND")},
|
|
properties = {"color": led_color},
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
Mod(name = "U1")
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(io_config, {
|
|
"Module.zen" => r#"
|
|
pwr = io(Net)
|
|
baud = config(int)
|
|
|
|
Component(
|
|
name = "comp0",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"V": "1"},
|
|
pins = {"V": pwr},
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
Mod(
|
|
name = "U1",
|
|
pwr = Net("VCC"),
|
|
baud = 9600,
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(missing_required_io_config, {
|
|
"Module.zen" => r#"
|
|
pwr = io(Net)
|
|
baud = config(int)
|
|
|
|
Component(
|
|
name = "comp0",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"V": "1"},
|
|
pins = {"V": pwr},
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
Mod(
|
|
name = "U1",
|
|
# intentionally omit `pwr` and `baud` - should trigger an error
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(optional_io_config, {
|
|
"Module.zen" => r#"
|
|
pwr = io(Net, optional = True)
|
|
baud = config(int, optional = True)
|
|
|
|
# The io() should be default-initialized, and the config() should be None.
|
|
check(pwr != None, "pwr should not be None when omitted")
|
|
check(baud == None, "baud should be None when omitted")
|
|
|
|
Component(
|
|
name = "comp0",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"V": "1"},
|
|
pins = {"V": Net("INTERNAL_V")},
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
Mod(
|
|
name = "U1",
|
|
# omit both inputs - allowed because they are optional
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_io, {
|
|
"Module.zen" => r#"
|
|
Power = interface(vcc = Net)
|
|
PdmMic = interface(power = Power, data = Net, select = Net, clock = Net)
|
|
|
|
pdm = io(PdmMic)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
pdm = Mod.PdmMic("PDM")
|
|
Mod(name = "U1", pdm = pdm)
|
|
"#
|
|
});
|
|
|
|
#[test]
|
|
fn config_named_checks() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
def nonnegative(value):
|
|
check(value >= 0, "value must be nonnegative")
|
|
|
|
value = config(int, checks = nonnegative)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
Mod(name = "U1", value = 1)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn io_named_checks() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
def present(net):
|
|
check(net != None, "net must be present")
|
|
|
|
pwr = io(Net, checks = present)
|
|
|
|
Component(
|
|
name = "comp0",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"V": "1"},
|
|
pins = {"V": pwr},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
Mod(name = "U1", pwr = Net("VCC"))
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn config_positional_none_checks_is_ignored() {
|
|
let eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
value = config(int, None)
|
|
check(value == 0, "config() should still resolve with positional None checks")
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn io_positional_none_checks_is_ignored() {
|
|
let eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
VIN = io(Net, None, optional = True)
|
|
check(VIN != None, "io() should still resolve with positional None checks")
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn config_name_infers_from_assignment() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
description = config(str)
|
|
skip_bom = config(bool, default = True)
|
|
|
|
Component(
|
|
name = "R1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"P": "1"},
|
|
pins = {"P": Net("SIG")},
|
|
description = description,
|
|
skip_bom = skip_bom,
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
Mod(name = "U1", description = "Acme")
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let output = eval_result.output.expect("expected eval output");
|
|
let module_tree = output.module_tree();
|
|
let child_module = module_tree
|
|
.values()
|
|
.find(|module| module.path().to_string() == "U1")
|
|
.expect("expected instantiated child module");
|
|
let component = child_module
|
|
.components()
|
|
.find(|component| component.name() == "R1")
|
|
.expect("expected child component");
|
|
|
|
assert_eq!(component.description(), Some("Acme"));
|
|
assert!(
|
|
component.skip_bom(),
|
|
"defaulted inferred config should still apply"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn inferred_config_values_work_in_component_kwargs() {
|
|
let eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
description = config(str, default = "Acme")
|
|
skip_bom = config(bool, default = True)
|
|
|
|
Component(
|
|
name = "R1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"P": "1"},
|
|
pins = {"P": Net("SIG")},
|
|
description = description,
|
|
skip_bom = skip_bom,
|
|
)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unused_nameless_config_is_ignored() {
|
|
let eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
config(int)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"unused nameless config() should be ignored, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn io_name_infers_from_assignment() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
SIG = io(Net)
|
|
|
|
Component(
|
|
name = "R1",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"P": "1"},
|
|
pins = {"P": SIG},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
Mod(name = "U1", SIG = Net("INPUT"))
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let output = eval_result.output.expect("expected eval output");
|
|
let module_tree = output.module_tree();
|
|
let child_module = module_tree
|
|
.values()
|
|
.find(|module| module.path().to_string() == "U1")
|
|
.expect("expected instantiated child module");
|
|
let component = child_module
|
|
.components()
|
|
.find(|component| component.name() == "R1")
|
|
.expect("expected child component");
|
|
let net = component
|
|
.connections()
|
|
.get("P")
|
|
.and_then(|value| value.downcast_ref::<FrozenNetValue>())
|
|
.expect("expected connected net");
|
|
|
|
assert_eq!(net.name(), "INPUT");
|
|
}
|
|
|
|
#[test]
|
|
fn unused_nameless_io_is_ignored() {
|
|
let eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
io(Net)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"unused nameless io() should be ignored, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn net_cast_from_inferred_net_preserves_runtime_name_without_setting_original_name() {
|
|
let eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
AUTO = Net()
|
|
COPY = Net(AUTO)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let output = eval_result.output.expect("expected eval output");
|
|
let copy = output.star_module.get("COPY").expect("expected COPY");
|
|
let net = copy
|
|
.value()
|
|
.downcast_ref::<FrozenNetValue>()
|
|
.expect("expected COPY to be a net");
|
|
|
|
assert_eq!(net.name(), "AUTO");
|
|
assert_eq!(
|
|
net.original_name_opt(),
|
|
None,
|
|
"casting an inferred net should not invent an explicit original name"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn redundant_io_and_config_names_emit_advice() {
|
|
let mut eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
VIN = io("VIN", Net)
|
|
manufacturer = config("manufacturer", str, default = "Acme")
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
pcb_zen_core::SortPass.apply(&mut eval_result.diagnostics);
|
|
|
|
let config_advice = redundancy_advice(
|
|
&eval_result.diagnostics,
|
|
"config() name 'manufacturer' is redundant",
|
|
);
|
|
let io_advice = redundancy_advice(&eval_result.diagnostics, "io() name 'VIN' is redundant");
|
|
|
|
assert_eq!(
|
|
config_advice.len(),
|
|
1,
|
|
"unexpected diagnostics: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert_eq!(
|
|
io_advice.len(),
|
|
1,
|
|
"unexpected diagnostics: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert_eq!(config_advice[0].path, "Module.zen");
|
|
assert_eq!(io_advice[0].path, "Module.zen");
|
|
assert!(
|
|
config_advice[0].span.is_some() && io_advice[0].span.is_some(),
|
|
"expected source spans for advice"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_names_emit_style_advice_without_assignment() {
|
|
let mut eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
load("@stdlib/interfaces.zen", "I2c")
|
|
|
|
io("signal", Net)
|
|
config("ClockRate", int, default = 100)
|
|
Net("vcc")
|
|
I2c("bus")
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
pcb_zen_core::SortPass.apply(&mut eval_result.diagnostics);
|
|
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("io() name 'signal' should be UPPERCASE: 'SIGNAL'")),
|
|
"expected standalone io() explicit-name advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("config() name 'ClockRate' should be snake_case: 'clock_rate'")),
|
|
"expected standalone config() explicit-name advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("Net() name 'vcc' should be UPPERCASE: 'VCC'")),
|
|
"expected standalone Net() explicit-name advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("interface() name 'bus' should be UPPERCASE: 'BUS'")),
|
|
"expected standalone interface() explicit-name advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn assignment_and_explicit_name_checks_are_independent() {
|
|
let mut eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
pwr = io("signal", Net)
|
|
BaudRate = config("ClockRate", int, default = 100)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
pcb_zen_core::SortPass.apply(&mut eval_result.diagnostics);
|
|
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("io() parameter 'pwr' should be UPPERCASE: 'PWR'")),
|
|
"expected assignment-name io() advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("io() name 'signal' should be UPPERCASE: 'SIGNAL'")),
|
|
"expected explicit-name io() advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("config() parameter 'BaudRate' should be snake_case: 'baud_rate'")),
|
|
"expected assignment-name config() advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("config() name 'ClockRate' should be snake_case: 'clock_rate'")),
|
|
"expected explicit-name config() advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_param_names_do_not_warn_when_assignment_differs() {
|
|
let mut eval_result = eval_zen(vec![(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
rail = io("VCC", Power)
|
|
package_name = config("package", str, default = "0603")
|
|
PowerIf = interface(vcc = Net, gnd = Net)
|
|
net_alias = Net("VIN")
|
|
bus = PowerIf("BUS")
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
pcb_zen_core::SortPass.apply(&mut eval_result.diagnostics);
|
|
|
|
let redundancy_advice = eval_result.diagnostics.iter().filter(|diag| {
|
|
diag.downcast_error_ref::<CategorizedDiagnostic>()
|
|
.is_some_and(|c| c.kind == "style.redundant_name")
|
|
});
|
|
|
|
assert_eq!(
|
|
redundancy_advice.count(),
|
|
0,
|
|
"did not expect redundancy advice, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unused_io_warns_only_for_unconnected_ports() {
|
|
let eval_result = eval_zen(vec![
|
|
(
|
|
"Leaf.zen".to_string(),
|
|
r#"
|
|
VIN = io(Net)
|
|
|
|
Component(
|
|
name = "LOAD",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"P": "1"},
|
|
skip_bom = True,
|
|
pins = {"P": VIN},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"Wrapper.zen".to_string(),
|
|
r#"
|
|
Leaf = Module("Leaf.zen")
|
|
|
|
Bus = interface(DATA = Net, CTRL = Net)
|
|
|
|
VIN = io(Net)
|
|
SPARE = io(Net)
|
|
BUS = io(Bus)
|
|
UNUSED_BUS = io(Bus)
|
|
|
|
Component(
|
|
name = "TAP",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"P": "1"},
|
|
skip_bom = True,
|
|
pins = {"P": BUS.DATA},
|
|
)
|
|
|
|
Leaf(name = "LEAF", VIN = VIN)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Wrapper = Module("Wrapper.zen")
|
|
|
|
bus = Wrapper.Bus("BUS")
|
|
unused_bus = Wrapper.Bus("UNUSED")
|
|
|
|
Wrapper(
|
|
name = "WRAP",
|
|
VIN = Net("VIN"),
|
|
SPARE = Net("SPARE"),
|
|
BUS = bus,
|
|
UNUSED_BUS = unused_bus,
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let eval_output = eval_result.output.expect("expected eval output");
|
|
let sch_result = eval_output.to_schematic_with_diagnostics();
|
|
|
|
assert!(
|
|
!sch_result.diagnostics.has_errors(),
|
|
"schematic conversion produced unexpected errors: {:?}",
|
|
sch_result.diagnostics
|
|
);
|
|
|
|
let unused_io_bodies: Vec<String> = sch_result
|
|
.diagnostics
|
|
.iter()
|
|
.filter(|diag| {
|
|
diag.downcast_error_ref::<CategorizedDiagnostic>()
|
|
.map(|categorized| categorized.kind == "module.io.unused")
|
|
.unwrap_or(false)
|
|
})
|
|
.map(|diag| diag.body.clone())
|
|
.collect();
|
|
let unused_io_paths: Vec<String> = sch_result
|
|
.diagnostics
|
|
.iter()
|
|
.filter(|diag| {
|
|
diag.downcast_error_ref::<CategorizedDiagnostic>()
|
|
.map(|categorized| categorized.kind == "module.io.unused")
|
|
.unwrap_or(false)
|
|
})
|
|
.map(|diag| diag.path.clone())
|
|
.collect();
|
|
|
|
assert_eq!(
|
|
unused_io_bodies.len(),
|
|
2,
|
|
"unexpected warnings: {unused_io_bodies:?}"
|
|
);
|
|
assert!(
|
|
unused_io_bodies
|
|
.iter()
|
|
.any(|body| body.contains("SPARE") && body.contains("WRAP")),
|
|
"missing SPARE warning: {unused_io_bodies:?}"
|
|
);
|
|
assert!(
|
|
unused_io_bodies
|
|
.iter()
|
|
.any(|body| body.contains("UNUSED_BUS") && body.contains("WRAP")),
|
|
"missing UNUSED_BUS warning: {unused_io_bodies:?}"
|
|
);
|
|
assert!(
|
|
unused_io_bodies.iter().all(|body| !body.contains("VIN")),
|
|
"forwarded VIN should not warn: {unused_io_bodies:?}"
|
|
);
|
|
assert!(
|
|
unused_io_bodies
|
|
.iter()
|
|
.all(|body| !body.starts_with("io() 'BUS'")),
|
|
"partially used BUS interface should not warn: {unused_io_bodies:?}"
|
|
);
|
|
assert!(
|
|
unused_io_paths
|
|
.iter()
|
|
.all(|path| path.ends_with("Wrapper.zen") && !path.contains("/stdlib/io.zen")),
|
|
"unused io warnings should point at Wrapper.zen, got: {unused_io_paths:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn errors_for_unspecified_non_generic_bom_component() {
|
|
let eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
vcc = Net("VCC")
|
|
gnd = Net("GND")
|
|
|
|
Component(
|
|
name = "U1",
|
|
prefix = "U",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"VDD": "1", "GND": "2"},
|
|
pins = {"VDD": vcc, "GND": gnd},
|
|
)
|
|
|
|
Component(
|
|
name = "R1",
|
|
prefix = "R",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
pins = {"1": vcc, "2": gnd},
|
|
type = "resistor",
|
|
properties = {"resistance": "10k", "package": "0402"},
|
|
)
|
|
|
|
Component(
|
|
name = "U2",
|
|
prefix = "U",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"VDD": "1", "GND": "2"},
|
|
pins = {"VDD": vcc, "GND": gnd},
|
|
part = builtin.Part(mpn = "PART-123", manufacturer = "ACME"),
|
|
)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let eval_output = eval_result.output.expect("expected eval output");
|
|
let mut diagnostics = eval_output.to_schematic_with_diagnostics().diagnostics;
|
|
SortPass.apply(&mut diagnostics);
|
|
|
|
let output = diagnostics
|
|
.iter()
|
|
.filter(|diag| {
|
|
diag.downcast_error_ref::<CategorizedDiagnostic>()
|
|
.is_some_and(|c| c.kind == "bom.unspecified")
|
|
})
|
|
.map(|diag| format!("{:?}: {}: {}", diag.severity, diag.path, diag.body))
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
|
|
insta::with_settings!({ prepend_module_to_snapshot => false }, {
|
|
insta::assert_snapshot!("input__errors_for_unspecified_non_generic_bom_component", output);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn errors_for_components_without_api_bom_matching() {
|
|
let eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
vcc = Net("VCC")
|
|
gnd = Net("GND")
|
|
|
|
Component(
|
|
name = "TH1",
|
|
prefix = "TH",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
pins = {"1": vcc, "2": gnd},
|
|
type = "thermistor",
|
|
)
|
|
|
|
Component(
|
|
name = "J1",
|
|
prefix = "J",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
pins = {"1": vcc, "2": gnd},
|
|
type = "connector",
|
|
properties = {"connector_type": "pin header"},
|
|
)
|
|
|
|
Component(
|
|
name = "J2",
|
|
prefix = "J",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
pins = {"1": vcc, "2": gnd},
|
|
type = "connector",
|
|
properties = {"connector_type": "terminal block"},
|
|
)
|
|
|
|
Component(
|
|
name = "X1",
|
|
prefix = "X",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
pins = {"1": vcc, "2": gnd},
|
|
type = "connector",
|
|
)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let eval_output = eval_result.output.expect("expected eval output");
|
|
let mut diagnostics = eval_output.to_schematic_with_diagnostics().diagnostics;
|
|
SortPass.apply(&mut diagnostics);
|
|
|
|
let unspecified = diagnostics
|
|
.iter()
|
|
.filter(|diag| {
|
|
diag.downcast_error_ref::<CategorizedDiagnostic>()
|
|
.is_some_and(|c| c.kind == "bom.unspecified")
|
|
})
|
|
.map(|diag| (diag.severity, diag.body.as_str()))
|
|
.collect::<Vec<_>>();
|
|
|
|
assert!(
|
|
unspecified
|
|
.iter()
|
|
.any(|(severity, body)| matches!(severity, EvalSeverity::Error)
|
|
&& body.contains("Component 'TH1'")),
|
|
"expected thermistor to error, got: {unspecified:?}"
|
|
);
|
|
assert!(
|
|
unspecified
|
|
.iter()
|
|
.any(|(severity, body)| matches!(severity, EvalSeverity::Error)
|
|
&& body.contains("Component 'X1'")),
|
|
"expected unmatched connector to error, got: {unspecified:?}"
|
|
);
|
|
assert!(
|
|
unspecified
|
|
.iter()
|
|
.any(|(severity, body)| matches!(severity, EvalSeverity::Error)
|
|
&& body.contains("Component 'J2'")),
|
|
"expected terminal block connector to error, got: {unspecified:?}"
|
|
);
|
|
assert!(
|
|
unspecified
|
|
.iter()
|
|
.all(|(_, body)| !body.contains("Component 'J1'")),
|
|
"expected pin header connector to be API-match eligible, got: {unspecified:?}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn errors_for_legacy_mpn_without_manufacturer_as_underspecified() {
|
|
let eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
vcc = Net("VCC")
|
|
gnd = Net("GND")
|
|
|
|
Component(
|
|
name = "R1",
|
|
prefix = "R",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
pins = {"1": vcc, "2": gnd},
|
|
mpn = "RC0603FR-071KL",
|
|
)
|
|
|
|
Component(
|
|
name = "R2",
|
|
prefix = "R",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
pins = {"1": vcc, "2": gnd},
|
|
manufacturer = "Yageo",
|
|
)
|
|
|
|
Component(
|
|
name = "R3",
|
|
prefix = "R",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
pins = {"1": vcc, "2": gnd},
|
|
type = "resistor",
|
|
mpn = "RC0603FR-071KL",
|
|
)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let eval_output = eval_result.output.expect("expected eval output");
|
|
let mut diagnostics = eval_output.to_schematic_with_diagnostics().diagnostics;
|
|
SortPass.apply(&mut diagnostics);
|
|
|
|
let categorized = diagnostics
|
|
.iter()
|
|
.filter_map(|diag| {
|
|
diag.downcast_error_ref::<CategorizedDiagnostic>()
|
|
.map(|c| (diag.severity, c.kind.as_str(), diag.body.as_str()))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
assert!(
|
|
categorized.iter().any(|(severity, kind, body)| {
|
|
matches!(severity, EvalSeverity::Error)
|
|
&& *kind == "bom.underspecified"
|
|
&& body.contains("Component 'R1'")
|
|
&& body.contains("missing manufacturer")
|
|
}),
|
|
"expected mpn-only component to error as bom.underspecified, got: {categorized:?}"
|
|
);
|
|
assert!(
|
|
categorized.iter().any(|(severity, kind, body)| {
|
|
matches!(severity, EvalSeverity::Error)
|
|
&& *kind == "bom.underspecified"
|
|
&& body.contains("Component 'R3'")
|
|
&& body.contains("missing manufacturer")
|
|
}),
|
|
"expected mpn-only API-matchable generic to error as bom.underspecified, got: {categorized:?}"
|
|
);
|
|
assert!(
|
|
categorized.iter().any(|(severity, kind, body)| {
|
|
matches!(severity, EvalSeverity::Error)
|
|
&& *kind == "bom.unspecified"
|
|
&& body.contains("Component 'R2'")
|
|
}),
|
|
"expected manufacturer-only component to error as bom.unspecified, got: {categorized:?}"
|
|
);
|
|
}
|
|
|
|
snapshot_eval!(io_interface_incompatible, {
|
|
"Module.zen" => r#"
|
|
signal = io(Net)
|
|
"#,
|
|
"parent.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
SingleNet = interface(signal = Net)
|
|
sig_if = SingleNet("SIG")
|
|
|
|
Mod(name="U1", signal=sig_if) # Should fail - interface not accepted for Net io
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_str, {
|
|
"test.zen" => r#"
|
|
value = config(str)
|
|
|
|
# Use the string config
|
|
Component(
|
|
name = "test_comp",
|
|
footprint = "test_footprint",
|
|
pin_defs = {"in": "1", "out": "2"},
|
|
pins = {
|
|
"in": Net("1"),
|
|
"out": Net("2")
|
|
},
|
|
properties = {
|
|
"value": value
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_types, {
|
|
"test.zen" => r#"
|
|
# Test various config() and io() declarations for signature generation
|
|
|
|
# Basic types
|
|
str_config = config(str)
|
|
int_config = config(int)
|
|
float_config = config(float)
|
|
bool_config = config(bool)
|
|
|
|
# Optional configs with defaults
|
|
opt_str = config(str, optional=True, default="default_value")
|
|
opt_int = config(int, optional=True, default=42)
|
|
opt_float = config(float, optional=True, default=3.14)
|
|
opt_bool = config(bool, optional=True, default=True)
|
|
|
|
# Optional without defaults
|
|
opt_no_default = config(str, optional=True)
|
|
|
|
# IO declarations
|
|
net_io = io(Net)
|
|
opt_net_io = io(Net, optional=True)
|
|
|
|
# Interface types
|
|
Power = interface(vcc = Net, gnd = Net)
|
|
power_io = io(Power)
|
|
opt_power_io = io(Power, optional=True)
|
|
|
|
# Nested interface
|
|
DataBus = interface(
|
|
data = Net,
|
|
clock = Net,
|
|
enable = Net
|
|
)
|
|
bus_io = io(DataBus)
|
|
|
|
# Complex nested interface
|
|
System = interface(
|
|
power = Power,
|
|
bus = DataBus,
|
|
reset = Net
|
|
)
|
|
system_io = io(System)
|
|
|
|
# Add a simple component to make the module valid
|
|
Component(
|
|
name = "test",
|
|
type = "test_component",
|
|
pin_defs = {"1": "1"},
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pins = {"1": Net("TEST")},
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(implicit_enum_conversion, {
|
|
"Module.zen" => r#"
|
|
Direction = enum("NORTH", "SOUTH")
|
|
|
|
heading = config(Direction)
|
|
|
|
Component(
|
|
name = "comp0",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = { "V": "1" },
|
|
pins = { "V": Net("VCC") },
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
Mod(
|
|
name = "child",
|
|
heading = "NORTH",
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_net_incompatible, {
|
|
"Module.zen" => r#"
|
|
SingleNet = interface(signal = Net)
|
|
|
|
signal_if = SingleNet(name="sig")
|
|
|
|
Component(
|
|
name = "test_comp",
|
|
footprint = "test_footprint",
|
|
pin_defs = {"in": "1", "out": "2"},
|
|
pins = {
|
|
"in": signal_if, # This should fail - interfaces not accepted for pins
|
|
"out": Net()
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_net_template_basic, {
|
|
"Module.zen" => r#"
|
|
MyInterface = interface(test = Net("MYTEST"))
|
|
instance = MyInterface("PREFIX")
|
|
|
|
Component(
|
|
name = "R1",
|
|
type = "resistor",
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
footprint = File("@kicad-footprints/Capacitor_SMD.pretty/C_0805_2012Metric.kicad_mod"),
|
|
pins = {"1": instance.test, "2": Net("GND")},
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_multiple_net_templates, {
|
|
"test.zen" => r#"
|
|
Power = interface(
|
|
vcc = Net("3V3"),
|
|
gnd = Net("GND"),
|
|
enable = Net("EN")
|
|
)
|
|
|
|
pwr1 = Power("MCU")
|
|
pwr2 = Power("SENSOR")
|
|
|
|
Component(
|
|
name = "U1",
|
|
type = "mcu",
|
|
pin_defs = {"VCC": "1", "GND": "2", "EN": "3"},
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pins = {
|
|
"VCC": pwr1.vcc,
|
|
"GND": pwr1.gnd,
|
|
"EN": pwr1.enable,
|
|
}
|
|
)
|
|
|
|
Component(
|
|
name = "U2",
|
|
type = "sensor",
|
|
pin_defs = {"VDD": "1", "VSS": "2", "ENABLE": "3"},
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pins = {
|
|
"VDD": pwr2.vcc,
|
|
"VSS": pwr2.gnd,
|
|
"ENABLE": pwr2.enable,
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_nested_template, {
|
|
"test.zen" => r#"
|
|
# Nested interface templates
|
|
PowerNets = interface(
|
|
vcc = Net("VCC"),
|
|
gnd = Net("GND")
|
|
)
|
|
|
|
# Create a pre-configured power instance
|
|
usb_power = PowerNets("USB")
|
|
|
|
# Use as template in another interface
|
|
Device = interface(
|
|
power = usb_power,
|
|
data_p = Net("D+"),
|
|
data_n = Net("D-")
|
|
)
|
|
|
|
# Create device instance
|
|
dev = Device("PORT1")
|
|
|
|
# Wire up components
|
|
Component(
|
|
name = "J1",
|
|
type = "usb_connector",
|
|
pin_defs = {"VBUS": "1", "D+": "2", "D-": "3", "GND": "4"},
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pins = {
|
|
"VBUS": dev.power.vcc,
|
|
"D+": dev.data_p,
|
|
"D-": dev.data_n,
|
|
"GND": dev.power.gnd,
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_mixed_templates_and_types, {
|
|
"test.zen" => r#"
|
|
# Mix of templates and regular types
|
|
MixedInterface = interface(
|
|
# Template nets without properties
|
|
power = Net("VDD"),
|
|
ground = Net("VSS"),
|
|
# Regular net type
|
|
signal = Net,
|
|
# Nested interface template
|
|
control = interface(
|
|
enable = Net("EN"),
|
|
reset = Net("RST")
|
|
)()
|
|
)
|
|
|
|
# Create instance
|
|
mixed = MixedInterface("CHIP")
|
|
|
|
# Use all the nets
|
|
Component(
|
|
name = "IC1",
|
|
type = "asic",
|
|
pin_defs = {
|
|
"VDD": "1",
|
|
"VSS": "2",
|
|
"SIG": "3",
|
|
"EN": "4",
|
|
"RST": "5"
|
|
},
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pins = {
|
|
"VDD": mixed.power,
|
|
"VSS": mixed.ground,
|
|
"SIG": mixed.signal,
|
|
"EN": mixed.control.enable,
|
|
"RST": mixed.control.reset,
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_record_type_rejected, {
|
|
"Module.zen" => r#"
|
|
UnitType = record(
|
|
value = field(float),
|
|
unit = field(str),
|
|
)
|
|
|
|
voltage = config(UnitType, default = UnitType(value = 0.0, unit = "V"))
|
|
"#,
|
|
});
|
|
|
|
snapshot_eval!(io_config_with_help_text, {
|
|
"Module.zen" => r#"
|
|
# Test io() and config() with help parameter
|
|
|
|
# IO with help text
|
|
power = io(Net, help = "Main power supply net")
|
|
data = io(Net, optional = True, help = "Optional data line")
|
|
|
|
# Config with help text
|
|
baud_rate = config(int, default = 9600, help = "Serial communication baud rate")
|
|
device_name = config(str, help = "Human-readable device identifier")
|
|
|
|
# Optional config with help
|
|
debug_mode = config(bool, optional = True, help = "Enable debug logging")
|
|
|
|
voltage = config(float, default = 3.3, help = "Operating voltage in volts")
|
|
|
|
# Add a component to make the module valid
|
|
Component(
|
|
name = "test",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"PWR": "1", "GND": "2"},
|
|
pins = {"PWR": power, "GND": Net("GND")},
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
# Create module instance with some parameters
|
|
Mod(
|
|
name = "U1",
|
|
power = Net("VCC"),
|
|
baud_rate = 115200,
|
|
device_name = "TestDevice",
|
|
voltage = 5.0,
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(cfg_enum_value, {
|
|
"Module.zen" => r#"
|
|
# Test io() with enum value
|
|
|
|
EnumType = enum("A", "B", "C")
|
|
|
|
cfg = config(EnumType, default = "A")
|
|
print(cfg)
|
|
"#,
|
|
"top.zen" => r#"
|
|
MyModule = Module("./Module.zen")
|
|
|
|
# Create module instance with some parameters
|
|
MyModule(
|
|
name = "U1",
|
|
cfg = MyModule.EnumType("B"),
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_int_to_float_conversion, {
|
|
"Module.zen" => r#"
|
|
# Test automatic int to float conversion
|
|
voltage = config(float)
|
|
current = config(float, default = 1) # int default should convert to float
|
|
power = config(float, optional = True)
|
|
|
|
# Verify the values are floats
|
|
builtin.add_property("voltage_value", voltage)
|
|
builtin.add_property("voltage_type", type(voltage))
|
|
builtin.add_property("current_value", current)
|
|
builtin.add_property("current_type", type(current))
|
|
|
|
# Test arithmetic to ensure they behave as floats
|
|
builtin.add_property("voltage_divided", voltage / 2)
|
|
builtin.add_property("current_multiplied", current * 1.5)
|
|
|
|
# Optional power should be None when not provided
|
|
builtin.add_property("power_is_none", power == None)
|
|
"#,
|
|
"top.zen" => r#"
|
|
MyModule = Module("./Module.zen")
|
|
|
|
# Provide integer values that should be converted to floats
|
|
m = MyModule(
|
|
name = "test",
|
|
voltage = 5, # int 5 should become float 5.0
|
|
current = 2, # int 2 should become float 2.0
|
|
# power is not provided, should be None
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_mixed_numeric_types, {
|
|
"Module.zen" => r#"
|
|
# Test that float values remain floats and int values convert to float
|
|
voltage1 = config(float)
|
|
voltage2 = config(float)
|
|
voltage3 = config(float, default = 0) # int default
|
|
|
|
# Verify all are floats
|
|
builtin.add_property("v1_value", voltage1)
|
|
builtin.add_property("v1_type", type(voltage1))
|
|
builtin.add_property("v2_value", voltage2)
|
|
builtin.add_property("v2_type", type(voltage2))
|
|
builtin.add_property("v3_value", voltage3)
|
|
builtin.add_property("v3_type", type(voltage3))
|
|
|
|
# Test that float arithmetic works correctly
|
|
builtin.add_property("sum", voltage1 + voltage2 + voltage3)
|
|
"#,
|
|
"top.zen" => r#"
|
|
MyModule = Module("./Module.zen")
|
|
|
|
m = MyModule(
|
|
name = "test",
|
|
voltage1 = 3.14, # Already a float
|
|
voltage2 = 10, # Int that should convert to float
|
|
# voltage3 uses default int 0 that should convert to float
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(io_invalid_type, {
|
|
"test.zen" => r#"
|
|
# io() should only accept NetType or InterfaceFactory, not primitive types
|
|
value = io(int)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_string_to_physical_value, {
|
|
"child.zen" => r#"
|
|
voltage = config(Voltage)
|
|
resistance = config(Voltage / builtin.Current)
|
|
current = config(builtin.Current)
|
|
|
|
print("voltage:", voltage)
|
|
print("resistance:", resistance)
|
|
print("current:", current)
|
|
"#,
|
|
"test.zen" => r#"
|
|
Child = Module("child.zen")
|
|
|
|
# Provide mixed scalar/string values that should be converted
|
|
# through the PhysicalValue constructor path.
|
|
Child(name = "test", voltage = "3.3V", resistance = 10000, current = 0.02)
|
|
|
|
print("String to PhysicalValue conversion: success")
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_string_to_physical_value_with_bounds, {
|
|
"child.zen" => r#"
|
|
voltage = config(Voltage)
|
|
|
|
print("voltage:", voltage)
|
|
"#,
|
|
"test.zen" => r#"
|
|
Child = Module("child.zen")
|
|
|
|
Child(name = "test", voltage = "3.0V to 3.6V")
|
|
|
|
print("String to PhysicalValue (bounds) conversion: success")
|
|
"#
|
|
});
|
|
|
|
#[test]
|
|
fn config_accepts_composed_physical_types() {
|
|
let result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
load("@stdlib/units.zen", "Current", "Length", "Mass", "Resistance", "Time", "Voltage")
|
|
|
|
slew_rate = config(Voltage / Time, default="5V/us")
|
|
frequency = config(1 / Time, default="1MHz")
|
|
power = config(Voltage * Current, default="2W")
|
|
speed = config(Length / Time, default="2m/s")
|
|
mass = config(Mass, default="500g")
|
|
resistance = config(Resistance, default="1m")
|
|
|
|
expected_slew_rate = Voltage("5V") / Time("1us")
|
|
expected_voltage = Mass * Length * Length / (Current * Time * Time * Time)
|
|
check(Voltage.unit == expected_voltage.unit, "Voltage should derive from the five SI bases")
|
|
check(slew_rate.unit == "V/s", "slew rate should retain V/s dimensions")
|
|
check(slew_rate.value == 5000000, "slew rate should apply denominator prefix, got " + str(slew_rate.value))
|
|
check(expected_slew_rate.unit == slew_rate.unit, "value and type algebra should agree")
|
|
check(expected_slew_rate.value == slew_rate.value, "value and type algebra should agree")
|
|
check(frequency.unit == "Hz", "reciprocal time should be frequency")
|
|
check(power.unit == "W", "voltage times current should be power")
|
|
check(speed.unit == "m/s", "length over time should retain SI dimensions")
|
|
check(speed.value == 2, "metre should parse contextually as length")
|
|
check(mass.unit == "kg", "mass should be stored in kilograms")
|
|
check(mass.value == 0.5, "gram prefixes should scale relative to kilograms")
|
|
check(resistance.value == 0.001, "legacy bare m should remain milli-ohms for resistance")
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
Mod(
|
|
name = "U1",
|
|
slew_rate = "5V/us",
|
|
frequency = "1MHz",
|
|
power = "2W",
|
|
speed = "2m/s",
|
|
mass = "500g",
|
|
resistance = "1m",
|
|
)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
!result.diagnostics.has_errors(),
|
|
"composed physical config types should evaluate: {:?}",
|
|
result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn io_direction_appears_in_signature() {
|
|
let eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
VIN = io(Net, direction = "input")
|
|
VOUT = io(Net, direction = "output")
|
|
BIDIR = io(Net)
|
|
|
|
Component(
|
|
name = "test",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"IN": "1", "OUT": "2", "IO": "3"},
|
|
pins = {"IN": VIN, "OUT": VOUT, "IO": BIDIR},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let eval_output = eval_result.output.expect("expected eval output");
|
|
let signature = eval_output.signature;
|
|
|
|
let vin = signature
|
|
.iter()
|
|
.find(|param| param.name == "VIN")
|
|
.expect("expected VIN in signature");
|
|
assert_eq!(vin.direction, Some(IoDirection::Input));
|
|
|
|
let vout = signature
|
|
.iter()
|
|
.find(|param| param.name == "VOUT")
|
|
.expect("expected VOUT in signature");
|
|
assert_eq!(vout.direction, Some(IoDirection::Output));
|
|
|
|
let bidir = signature
|
|
.iter()
|
|
.find(|param| param.name == "BIDIR")
|
|
.expect("expected BIDIR in signature");
|
|
assert_eq!(bidir.direction, None);
|
|
}
|
|
|
|
#[test]
|
|
fn io_direction_rejects_invalid_values() {
|
|
let eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
VIN = io(Net, direction = "in")
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
eval_result.output.is_none(),
|
|
"expected evaluation to fail for invalid direction"
|
|
);
|
|
assert!(
|
|
eval_result.diagnostics.iter().any(|diag| diag
|
|
.body
|
|
.contains("io() direction must be \"input\" or \"output\"")),
|
|
"expected invalid direction diagnostic, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn input_output_set_direction_metadata() {
|
|
let eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
VIN = input(Net)
|
|
VOUT = output(Net, help = "Output net")
|
|
|
|
Component(
|
|
name = "test",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"IN": "1", "OUT": "2"},
|
|
pins = {"IN": VIN, "OUT": VOUT},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
|
|
let eval_output = eval_result.output.expect("expected eval output");
|
|
let signature = eval_output.signature;
|
|
|
|
let vin = signature
|
|
.iter()
|
|
.find(|param| param.name == "VIN")
|
|
.expect("expected VIN in signature");
|
|
assert_eq!(vin.direction, Some(IoDirection::Input));
|
|
|
|
let vout = signature
|
|
.iter()
|
|
.find(|param| param.name == "VOUT")
|
|
.expect("expected VOUT in signature");
|
|
assert_eq!(vout.direction, Some(IoDirection::Output));
|
|
assert_eq!(vout.help.as_deref(), Some("Output net"));
|
|
}
|
|
|
|
#[test]
|
|
fn builtin_io_available_directly() {
|
|
let eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
VIN = builtin.io("VIN", Net, direction = "input")
|
|
VOUT = builtin.io("VOUT", Net)
|
|
|
|
Component(
|
|
name = "test",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"IN": "1", "OUT": "2"},
|
|
pins = {"IN": VIN, "OUT": VOUT},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn builtin_io_skips_ast_style_lints() {
|
|
let mut eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
signal = builtin.io("signal", Net)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
SortPass.apply(&mut eval_result.diagnostics);
|
|
|
|
assert!(
|
|
redundancy_advice(&eval_result.diagnostics, "io() name 'signal' is redundant").is_empty(),
|
|
"did not expect redundant-name advice for dotted builtin.io(), got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
let io_advice_count = eval_result
|
|
.diagnostics
|
|
.iter()
|
|
.filter(|diag| diag.body.contains("io() parameter 'signal'"))
|
|
.count();
|
|
assert_eq!(
|
|
io_advice_count, 0,
|
|
"did not expect naming advice for dotted builtin.io(), got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn stdlib_interface_io_only_lints_root_name() {
|
|
let mut eval_result = eval_zen(vec![(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
load("@stdlib/interfaces.zen", "Usb2")
|
|
|
|
usb = io(Usb2)
|
|
"#
|
|
.to_string(),
|
|
)]);
|
|
SortPass.apply(&mut eval_result.diagnostics);
|
|
|
|
let root_advice: Vec<_> = eval_result
|
|
.diagnostics
|
|
.iter()
|
|
.filter(|diag| {
|
|
diag.body
|
|
.contains("io() parameter 'usb' should be UPPERCASE: 'USB'")
|
|
})
|
|
.collect();
|
|
let generated_net_advice: Vec<_> = eval_result
|
|
.diagnostics
|
|
.iter()
|
|
.filter(|diag| diag.body.contains("Net name 'usb_"))
|
|
.collect();
|
|
|
|
assert_eq!(
|
|
root_advice.len(),
|
|
1,
|
|
"expected one root io() naming advice for direct io(Usb2), got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
assert!(
|
|
generated_net_advice.is_empty(),
|
|
"did not expect generated child-net naming advice for stdlib interfaces, got: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn prelude_injects_io_helpers_from_stdlib() {
|
|
let mut files = stdlib_test_files();
|
|
files.insert(
|
|
"test.zen".to_string(),
|
|
r#"
|
|
VIN = input(Power)
|
|
VOUT = output(Net)
|
|
GND = io(Ground)
|
|
|
|
Component(
|
|
name = "test",
|
|
footprint = File("@kicad-footprints/Resistor_SMD.pretty/R_0402_1005Metric.kicad_mod"),
|
|
pin_defs = {"IN": "1", "OUT": "2", "G": "3"},
|
|
pins = {"IN": VIN, "OUT": VOUT, "G": GND},
|
|
)
|
|
"#
|
|
.to_string(),
|
|
);
|
|
|
|
let file_provider: Arc<dyn pcb_zen_core::FileProvider> =
|
|
Arc::new(InMemoryFileProvider::new(files));
|
|
let eval_result = pcb_zen_core::EvalContext::new(file_provider, test_resolution())
|
|
.set_source_path(PathBuf::from("test.zen"))
|
|
.eval();
|
|
|
|
assert!(
|
|
!eval_result.diagnostics.has_errors(),
|
|
"eval produced unexpected errors: {:?}",
|
|
eval_result.diagnostics
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn config_allowed_physical_metadata() {
|
|
let result = eval_zen(vec![
|
|
(
|
|
"Module.zen".to_string(),
|
|
r#"
|
|
Capacitance = builtin.Current * builtin.Time / Voltage
|
|
|
|
capacitance = config(
|
|
"capacitance",
|
|
Capacitance,
|
|
allowed = ["100mF", "220mF"],
|
|
default = "100mF",
|
|
)
|
|
|
|
print("capacitance:", capacitance)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
(
|
|
"top.zen".to_string(),
|
|
r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
Mod(name = "U1", capacitance = "0.1F")
|
|
"#
|
|
.to_string(),
|
|
),
|
|
]);
|
|
|
|
assert!(
|
|
result.is_success(),
|
|
"expected eval success, got diagnostics: {:?}",
|
|
result.diagnostics
|
|
);
|
|
|
|
let output = result.output.expect("expected eval output");
|
|
let module_tree = output.module_tree();
|
|
let child_module = module_tree
|
|
.values()
|
|
.find(|module| module.path().name() == "U1")
|
|
.expect("expected instantiated child module");
|
|
let param = child_module
|
|
.signature()
|
|
.iter()
|
|
.find(|param| param.name == "capacitance")
|
|
.expect("expected capacitance parameter in child signature");
|
|
|
|
assert_eq!(
|
|
param.allowed_values.as_ref().map(Vec::len),
|
|
Some(2),
|
|
"expected two allowed values in signature metadata"
|
|
);
|
|
let default_display = param
|
|
.default_value
|
|
.as_ref()
|
|
.expect("expected normalized default value")
|
|
.to_value()
|
|
.to_repr();
|
|
let allowed_display: Vec<String> = param
|
|
.allowed_values
|
|
.as_ref()
|
|
.expect("expected normalized allowed values")
|
|
.iter()
|
|
.map(|value| value.to_value().to_repr())
|
|
.collect();
|
|
let actual_display = param
|
|
.actual_value
|
|
.as_ref()
|
|
.expect("expected resolved config value")
|
|
.to_value()
|
|
.to_repr();
|
|
|
|
assert!(
|
|
allowed_display
|
|
.iter()
|
|
.any(|value| value == &default_display),
|
|
"default should be one of the normalized allowed values: {:?}",
|
|
allowed_display
|
|
);
|
|
assert!(
|
|
allowed_display.iter().any(|value| value == &actual_display),
|
|
"resolved value should match one of the normalized allowed values: {:?}",
|
|
allowed_display
|
|
);
|
|
}
|
|
|
|
snapshot_eval!(config_allowed_invalid_value, {
|
|
"Module.zen" => r#"
|
|
package = config(
|
|
str,
|
|
allowed = {"0402": 1, "0603": 2},
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
Mod(name = "U1", package = "0805")
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_allowed_invalid_default, {
|
|
"Module.zen" => r#"
|
|
output_voltage = config(
|
|
"output_voltage",
|
|
Voltage,
|
|
allowed = ["1.0V"],
|
|
default = "0.9V",
|
|
)
|
|
"#,
|
|
"top.zen" => r#"
|
|
Mod = Module("Module.zen")
|
|
|
|
Mod(name = "U1", output_voltage = "1.0V")
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_allowed_unsupported_type, {
|
|
"test.zen" => r#"
|
|
value = config(
|
|
"value",
|
|
list,
|
|
allowed = [[]],
|
|
)
|
|
"#
|
|
});
|