mirror of
https://github.com/diodeinc/pcb.git
synced 2026-09-14 20:17:19 +08:00
e852477546
* Initial release * cargo-dist * build and test CI * update ubuntu workflow * fmt * ubuntu: install deps * clippy: 0.1.88 fixes * format * install clippy * Update README.md * Don't run cargo-dist on PRs
583 lines
16 KiB
Rust
583 lines
16 KiB
Rust
#[macro_use]
|
|
mod common;
|
|
|
|
snapshot_eval!(io_config, {
|
|
"Module.star" => r#"
|
|
pwr = io("pwr", Net)
|
|
baud = config("baud", int)
|
|
|
|
Component(
|
|
name = "comp0",
|
|
footprint = "TEST:0402",
|
|
pin_defs = {"V": "1"},
|
|
pins = {"V": pwr},
|
|
)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
Module(
|
|
name = "U1",
|
|
pwr = Net("VCC"),
|
|
baud = 9600,
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(missing_required_io_config, {
|
|
"Module.star" => r#"
|
|
pwr = io("pwr", Net)
|
|
baud = config("baud", int)
|
|
|
|
Component(
|
|
name = "comp0",
|
|
footprint = "TEST:0402",
|
|
pin_defs = {"V": "1"},
|
|
pins = {"V": pwr},
|
|
)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
Module(
|
|
name = "U1",
|
|
# intentionally omit `pwr` and `baud` - should trigger an error
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(optional_io_config, {
|
|
"Module.star" => r#"
|
|
pwr = io("pwr", Net, optional = True)
|
|
baud = config("baud", 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 = "TEST:0402",
|
|
pin_defs = {"V": "1"},
|
|
pins = {"V": Net("")},
|
|
)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
Module(
|
|
name = "U1",
|
|
# omit both inputs - allowed because they are optional
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_io, {
|
|
"Module.star" => r#"
|
|
Power = interface(vcc = Net)
|
|
PdmMic = interface(power = Power, data = Net, select = Net, clock = Net)
|
|
|
|
pdm = io("pdm", PdmMic)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
pdm = Module.PdmMic("PDM")
|
|
Module(name = "U1", pdm = pdm)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(io_interface_incompatible, {
|
|
"Module.star" => r#"
|
|
signal = io("signal", Net)
|
|
"#,
|
|
"parent.star" => r#"
|
|
load(".", "Module")
|
|
|
|
SingleNet = interface(signal = Net)
|
|
sig_if = SingleNet("SIG")
|
|
|
|
Module(name="U1", signal=sig_if) # Should fail - interface not accepted for Net io
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_str, {
|
|
"test.star" => r#"
|
|
value = config("value", 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.star" => r#"
|
|
# Test various config() and io() declarations for signature generation
|
|
|
|
# Basic types
|
|
str_config = config("str_config", str)
|
|
int_config = config("int_config", int)
|
|
float_config = config("float_config", float)
|
|
bool_config = config("bool_config", bool)
|
|
|
|
# Optional configs with defaults
|
|
opt_str = config("opt_str", str, optional=True, default="default_value")
|
|
opt_int = config("opt_int", int, optional=True, default=42)
|
|
opt_float = config("opt_float", float, optional=True, default=3.14)
|
|
opt_bool = config("opt_bool", bool, optional=True, default=True)
|
|
|
|
# Optional without defaults
|
|
opt_no_default = config("opt_no_default", str, optional=True)
|
|
|
|
# IO declarations
|
|
net_io = io("net_io", Net)
|
|
opt_net_io = io("opt_net_io", Net, optional=True)
|
|
|
|
# Interface types
|
|
Power = interface(vcc = Net, gnd = Net)
|
|
power_io = io("power_io", Power)
|
|
opt_power_io = io("opt_power_io", Power, optional=True)
|
|
|
|
# Nested interface
|
|
DataBus = interface(
|
|
data = Net,
|
|
clock = Net,
|
|
enable = Net
|
|
)
|
|
bus_io = io("bus_io", DataBus)
|
|
|
|
# Complex nested interface
|
|
System = interface(
|
|
power = Power,
|
|
bus = DataBus,
|
|
reset = Net
|
|
)
|
|
system_io = io("system_io", System)
|
|
|
|
# Add a simple component to make the module valid
|
|
Component(
|
|
name = "test",
|
|
type = "test_component",
|
|
pin_defs = {"1": "1"},
|
|
footprint = "TEST:FP",
|
|
pins = {"1": Net("TEST")},
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(implicit_enum_conversion, {
|
|
"Module.star" => r#"
|
|
Direction = enum("NORTH", "SOUTH")
|
|
|
|
heading = config("heading", Direction)
|
|
|
|
Component(
|
|
name = "comp0",
|
|
footprint = "TEST:0402",
|
|
pin_defs = { "V": "1" },
|
|
pins = { "V": Net("VCC") },
|
|
)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
Module(
|
|
name = "child",
|
|
heading = "NORTH",
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_net_incompatible, {
|
|
"Module.star" => 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.star" => r#"
|
|
MyInterface = interface(test = Net("MYTEST", prop=True, value="123"))
|
|
instance = MyInterface("PREFIX")
|
|
|
|
Component(
|
|
name = "R1",
|
|
type = "resistor",
|
|
pin_defs = {"1": "1", "2": "2"},
|
|
footprint = "SMD:0805",
|
|
pins = {"1": instance.test, "2": Net("GND")},
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_multiple_net_templates, {
|
|
"test.star" => r#"
|
|
Power = interface(
|
|
vcc = Net("3V3", voltage="3.3V", type="power"),
|
|
gnd = Net("GND", type="ground"),
|
|
enable = Net("EN", active="high")
|
|
)
|
|
|
|
pwr1 = Power("MCU")
|
|
pwr2 = Power("SENSOR")
|
|
|
|
Component(
|
|
name = "U1",
|
|
type = "mcu",
|
|
pin_defs = {"VCC": "1", "GND": "2", "EN": "3"},
|
|
footprint = "QFN:32",
|
|
pins = {
|
|
"VCC": pwr1.vcc,
|
|
"GND": pwr1.gnd,
|
|
"EN": pwr1.enable,
|
|
}
|
|
)
|
|
|
|
Component(
|
|
name = "U2",
|
|
type = "sensor",
|
|
pin_defs = {"VDD": "1", "VSS": "2", "ENABLE": "3"},
|
|
footprint = "SOT:23-6",
|
|
pins = {
|
|
"VDD": pwr2.vcc,
|
|
"VSS": pwr2.gnd,
|
|
"ENABLE": pwr2.enable,
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_nested_template, {
|
|
"test.star" => r#"
|
|
# Nested interface templates
|
|
PowerNets = interface(
|
|
vcc = Net("VCC", type="power"),
|
|
gnd = Net("GND", type="ground")
|
|
)
|
|
|
|
# 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 = "USB:TYPE-C",
|
|
pins = {
|
|
"VBUS": dev.power.vcc,
|
|
"D+": dev.data_p,
|
|
"D-": dev.data_n,
|
|
"GND": dev.power.gnd,
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_template_property_inheritance, {
|
|
"test.star" => r#"
|
|
# Test that properties are properly copied from templates
|
|
SignalInterface = interface(
|
|
clk = Net("CLK", frequency="100MHz", type="clock"),
|
|
data = Net("DATA", width="8", direction="bidirectional"),
|
|
valid = Net("VALID", active="high")
|
|
)
|
|
|
|
# Create multiple instances
|
|
bus1 = SignalInterface("CPU")
|
|
bus2 = SignalInterface("MEM")
|
|
|
|
# Connect them
|
|
Component(
|
|
name = "CPU",
|
|
type = "processor",
|
|
pin_defs = {"CLK": "1", "DATA": "2", "VALID": "3"},
|
|
footprint = "BGA:256",
|
|
pins = {
|
|
"CLK": bus1.clk,
|
|
"DATA": bus1.data,
|
|
"VALID": bus1.valid,
|
|
}
|
|
)
|
|
|
|
Component(
|
|
name = "MEM",
|
|
type = "memory",
|
|
pin_defs = {"CLK": "1", "DATA": "2", "VALID": "3"},
|
|
footprint = "TSOP:48",
|
|
pins = {
|
|
"CLK": bus2.clk,
|
|
"DATA": bus2.data,
|
|
"VALID": bus2.valid,
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(interface_mixed_templates_and_types, {
|
|
"test.star" => r#"
|
|
# Mix of templates and regular types
|
|
MixedInterface = interface(
|
|
# Template nets with properties
|
|
power = Net("VDD", voltage="1.8V"),
|
|
ground = Net("VSS", type="ground"),
|
|
# Regular net type
|
|
signal = Net,
|
|
# Nested interface template
|
|
control = interface(
|
|
enable = Net("EN"),
|
|
reset = Net("RST", active="low")
|
|
)()
|
|
)
|
|
|
|
# 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 = "QFN:48",
|
|
pins = {
|
|
"VDD": mixed.power,
|
|
"VSS": mixed.ground,
|
|
"SIG": mixed.signal,
|
|
"EN": mixed.control.enable,
|
|
"RST": mixed.control.reset,
|
|
}
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_with_convert_function, {
|
|
"Module.star" => r#"
|
|
# Define a record type for units
|
|
UnitType = record(
|
|
value = field(float),
|
|
unit = field(str),
|
|
)
|
|
|
|
# Define a converter function that parses strings like "5V" into the record
|
|
def parse_unit(s):
|
|
if type(s) == "string":
|
|
# Simple parser: extract number and unit
|
|
import_value = ""
|
|
import_unit = ""
|
|
for c in s.elems():
|
|
if c.isdigit() or c == ".":
|
|
import_value += c
|
|
else:
|
|
import_unit += c
|
|
|
|
if import_value and import_unit:
|
|
return UnitType(value = float(import_value), unit = import_unit)
|
|
return s
|
|
|
|
# Test 1: config with converter should accept string and convert to record
|
|
# Provide a default since records require defaults
|
|
voltage = config("voltage", UnitType, default = UnitType(value = 0.0, unit = "V"), convert = parse_unit)
|
|
|
|
# Test 2: config with converter and default value that needs conversion
|
|
# The default string should be converted when no value is provided
|
|
current = config("current", UnitType, default = "2.5A", convert = parse_unit)
|
|
|
|
# Test 3: optional config with converter
|
|
optional_power = config("power", UnitType, convert = parse_unit, optional = True)
|
|
|
|
# Add properties to verify the values
|
|
add_property("voltage_value", voltage.value)
|
|
add_property("voltage_unit", voltage.unit)
|
|
add_property("current_value", current.value)
|
|
add_property("current_unit", current.unit)
|
|
add_property("optional_power_is_none", optional_power == None)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
# Provide string input that should be converted
|
|
m = Module(
|
|
name = "test",
|
|
voltage = "5V",
|
|
# current uses default "2.5A" which should be converted
|
|
# power is optional and not provided
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_without_convert_fails_type_check, {
|
|
"Module.star" => r#"
|
|
UnitType = record(
|
|
value = field(float),
|
|
unit = field(str),
|
|
)
|
|
|
|
# This should fail because "5V" is not a record and no converter is provided
|
|
# Provide a default since records require defaults
|
|
voltage = config("voltage", UnitType, default = UnitType(value = 0.0, unit = "V"))
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
# This should fail - string cannot be used for record type without converter
|
|
m = Module(
|
|
name = "test",
|
|
voltage = "5V",
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_convert_with_default, {
|
|
"Module.star" => r#"
|
|
def int_to_string(x):
|
|
# Convert int to string with prefix
|
|
return "value_" + str(x)
|
|
|
|
# Config with default that needs conversion - int to string
|
|
name = config("name", str, default = 42, convert = int_to_string)
|
|
|
|
# Verify the default was converted by adding it as a property
|
|
add_property("name_value", name)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
# Don't provide input, so default is used and converted
|
|
m = Module(name = "test")
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_convert_preserves_correct_types, {
|
|
"Module.star" => r#"
|
|
UnitType = record(
|
|
value = field(float),
|
|
unit = field(str),
|
|
)
|
|
|
|
converter_called = [False] # Use list to allow mutation in nested function
|
|
|
|
def tracking_converter(x):
|
|
# This converter tracks if it was called
|
|
converter_called[0] = True
|
|
return x
|
|
|
|
# If we pass a proper record, the converter should not be invoked
|
|
# Provide a default since records require defaults
|
|
voltage = config("voltage", UnitType, default = UnitType(value = 0.0, unit = "V"), convert = tracking_converter)
|
|
|
|
# Add properties to verify behavior
|
|
add_property("converter_called", converter_called[0])
|
|
add_property("voltage_value", voltage.value)
|
|
add_property("voltage_unit", voltage.unit)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
# Create a proper record value
|
|
unit_value = Module.UnitType(value = 5.0, unit = "V")
|
|
|
|
# Pass the correct type - converter should not be called
|
|
m = Module(
|
|
name = "test",
|
|
voltage = unit_value,
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_convert_chain, {
|
|
"Module.star" => r#"
|
|
def parse_number(s):
|
|
if type(s) == "string":
|
|
return float(s)
|
|
return s
|
|
|
|
def multiply_by_two(x):
|
|
return x * 2
|
|
|
|
def composed_converter(s):
|
|
return multiply_by_two(parse_number(s))
|
|
|
|
# String "5" -> 5.0 -> 10.0
|
|
value = config("value", float, convert = composed_converter)
|
|
|
|
# Add property to verify the conversion
|
|
add_property("converted_value", value)
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
# Provide string that will be converted through the chain
|
|
m = Module(
|
|
name = "test",
|
|
value = "5",
|
|
)
|
|
"#
|
|
});
|
|
|
|
snapshot_eval!(config_convert_with_enum, {
|
|
"Module.star" => r#"
|
|
# Define an enum type
|
|
Direction = enum("NORTH", "SOUTH", "EAST", "WEST")
|
|
|
|
def direction_converter(s):
|
|
# Convert string to enum variant
|
|
if type(s) == "string":
|
|
# Call the enum factory with the uppercase string
|
|
return Direction(s.upper())
|
|
return s
|
|
|
|
# Config that converts string to enum
|
|
heading = config("heading", Direction, convert = direction_converter)
|
|
|
|
# Add property to verify conversion
|
|
add_property("heading_is_north", heading == Direction("NORTH"))
|
|
"#,
|
|
"top.star" => r#"
|
|
load(".", "Module")
|
|
|
|
# Provide lowercase string that should be converted to enum
|
|
m = Module(
|
|
name = "test",
|
|
heading = "north",
|
|
)
|
|
"#
|
|
});
|