Skip to content

Commit

Permalink
Add generic method for converter drawing
Browse files Browse the repository at this point in the history
Signed-off-by: Travis F. Collins <[email protected]>
  • Loading branch information
tfcollins committed Jan 10, 2025
1 parent 0c7b461 commit 6b0bdc5
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 12 deletions.
2 changes: 1 addition & 1 deletion adijif/converters/ad9680.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _convert_to_config(
}


class ad9680(ad9680_bf, ad9680_draw):
class ad9680(ad9680_draw, ad9680_bf):
"""AD9680 high speed ADC model.
This model supports direct clock configurations
Expand Down
44 changes: 33 additions & 11 deletions adijif/converters/adrv9009.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def get_required_clock_names(self) -> List[str]:
Returns:
List[str]: List of strings of clock names mapped by get_required_clocks
"""
return ["adrv9009_device_clock", "adrv9009_sysref"]
return ["adrv9009_ref_clk", "adrv9009_sysref"]

def get_config(self, solution: CpoSolveResult = None) -> Dict:
"""Extract configurations from solver results.
Expand Down Expand Up @@ -143,10 +143,10 @@ def _gekko_get_required_clocks(self) -> List[Dict]:
if self.device_clock_min <= dev_clock <= self.device_clock_max:
possible_device_clocks.append(dev_clock)

self.config["device_clock"] = self.model.sos1(possible_device_clocks)
self.config["ref_clk"] = self.model.sos1(possible_device_clocks)
# self.model.Obj(-1 * self.config["device_clock"])

return [self.config["device_clock"], self.config["sysref"]]
return [self.config["ref_clk"], self.config["sysref"]]

def get_required_clocks(self) -> List[Dict]:
"""Generate list of required clocks.
Expand All @@ -167,7 +167,7 @@ def get_required_clocks(self) -> List[Dict]:
self.config["input_clock_divider_x2"] = self._convert_input(
self.input_clock_dividers_times2_available
)
self.config["device_clock"] = self._add_intermediate(
self.config["ref_clk"] = self._add_intermediate(
self.sample_clock / self.config["input_clock_divider_x2"]
)
self.config["sysref"] = self._add_intermediate(
Expand All @@ -177,12 +177,12 @@ def get_required_clocks(self) -> List[Dict]:

self._add_equation(
[
self.device_clock_min <= self.config["device_clock"],
self.config["device_clock"] <= self.device_clock_max,
self.device_clock_min <= self.config["ref_clk"],
self.config["ref_clk"] <= self.device_clock_max,
]
)

return [self.config["device_clock"], self.config["sysref"]]
return [self.config["ref_clk"], self.config["sysref"]]


class adrv9009_rx(adc, adrv9009_clock_common, adrv9009_core):
Expand Down Expand Up @@ -228,6 +228,17 @@ class adrv9009_rx(adc, adrv9009_clock_common, adrv9009_core):
_decimation = 8
decimation_available = [4, 5, 8, 10, 16, 20, 32, 40]

def get_required_clock_names(self) -> List[str]:
"""Get list of strings of names of requested clocks.
This list of names is for the clocks defined by
get_required_clocks
Returns:
List[str]: List of strings of clock names mapped by get_required_clocks
"""
return ["adrv9009_rx_ref_clk", "adrv9009_rx_sysref"]


class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core):
"""ADRV9009 Transmit model."""
Expand Down Expand Up @@ -265,6 +276,17 @@ class adrv9009_tx(dac, adrv9009_clock_common, adrv9009_core):
_interpolation = 8
interpolation_available = [1, 2, 4, 5, 8, 10, 16, 20, 32]

def get_required_clock_names(self) -> List[str]:
"""Get list of strings of names of requested clocks.
This list of names is for the clocks defined by
get_required_clocks
Returns:
List[str]: List of strings of clock names mapped by get_required_clocks
"""
return ["adrv9009_tx_ref_clk", "adrv9009_tx_sysref"]


class adrv9009(adrv9009_core):
"""ADRV9009 combined transmit and receive model."""
Expand Down Expand Up @@ -343,7 +365,7 @@ def get_required_clocks(self) -> List[Dict]:
)

faster_clk = max([self.adc.sample_clock, self.dac.sample_clock])
self.config["device_clock"] = self._add_intermediate(
self.config["ref_clk"] = self._add_intermediate(
faster_clk / self.config["input_clock_divider_x2"]
)

Expand All @@ -356,13 +378,13 @@ def get_required_clocks(self) -> List[Dict]:

self._add_equation(
[
self.device_clock_min <= self.config["device_clock"],
self.config["device_clock"] <= self.device_clock_max,
self.device_clock_min <= self.config["ref_clk"],
self.config["ref_clk"] <= self.device_clock_max,
]
)

return [
self.config["device_clock"],
self.config["ref_clk"],
self.config["sysref_adc"],
self.config["sysref_dac"],
]
84 changes: 84 additions & 0 deletions adijif/converters/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from adijif.gekko_trans import gekko_translation
from adijif.jesd import jesd

from ..draw import Layout, Node


class converter(core, jesd, gekko_translation, metaclass=ABCMeta):
"""Converter base meta class used to enforce all converter classes.
Expand Down Expand Up @@ -35,6 +37,88 @@ class converter(core, jesd, gekko_translation, metaclass=ABCMeta):
"global_index",
]

def draw(self, clocks: dict, lo=None) -> str:
"""Generic Draw converter model.
Args:
clocks (Dict): Clocking configuration
lo (Dict): Local oscillator configuration
Returns:
str: Path to image file
"""
system_draw = lo is not None
name = self.name.lower()

if not system_draw:
lo = Layout(f"{name} Example")
else:
assert isinstance(lo, Layout), "lo must be a Layout object"

ic_node = Node(self.name)
lo.add_node(ic_node)

if not system_draw:
ref_in = Node("REF_IN", ntype="input")
lo.add_node(ref_in)
else:
to_node = lo.get_node(f"{name}_ref_clk")
from_node = lo.get_connection(to=to_node.name)
assert from_node, "No connection found"
assert isinstance(from_node, list), "Connection must be a list"
assert len(from_node) == 1, "Only one connection allowed"
ref_in = from_node[0]["from"]
# Remove to_node since it is not needed
lo.remove_node(to_node.name)

rate = clocks[f"{name}_ref_clk"]

lo.add_connection({"from": ref_in, "to": ic_node, "rate": rate})

# Add framer
jesd204_framer = Node("JESD204 Framer", ntype="jesd204framer")
ic_node.add_child(jesd204_framer)

# SYSREF
if not system_draw:
sysref_in = Node("SYSREF_IN", ntype="input")
lo.add_connection(
{
"from": sysref_in,
"to": jesd204_framer,
"rate": clocks[f"{name}_sysref"],
}
)
else:
to_node = lo.get_node(f"{name}_sysref")
from_node = lo.get_connection(to=to_node.name)
assert from_node, "No connection found"
assert isinstance(from_node, list), "Connection must be a list"
assert len(from_node) == 1, "Only one connection allowed"
sysref_in = from_node[0]["from"]
lo.remove_node(to_node.name)

lo.add_connection(
{
"from": sysref_in,
"to": jesd204_framer,
"rate": clocks[f"{name}_sysref"],
}
)

# WIP Add remote deframer
jesd204_deframer = Node("JESD204 Deframer", ntype="deframer")

# Add connect for each lane
for i in range(self.L):
lane_rate = self.bit_clock
lo.add_connection(
{"from": jesd204_framer, "to": jesd204_deframer, "rate": lane_rate}
)

if not system_draw:
return lo.draw()

def validate_config(self) -> None:
"""Validate device configuration including JESD and clocks.
Expand Down
36 changes: 36 additions & 0 deletions tests/test_draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,42 @@ def test_ad9680_draw():
# f.write(image_data)


@pytest.mark.drawing
def test_generic_converter_draw():
adc = jif.adrv9009_rx()
adc.sample_clock = 122.88e6
adc.decimation = 4
adc.set_quick_configuration_mode("17", "jesd204b")

# Check static
adc.validate_config()

required_clocks = adc.get_required_clocks()
required_clock_names = adc.get_required_clock_names()

# Add generic clock sources for solver
clks = []
for clock, name in zip(required_clocks, required_clock_names):
clk = jif.types.arb_source(name)
adc._add_equation(clk(adc.model) == clock)
clks.append(clk)

# Solve
solution = adc.model.solve(LogVerbosity="Quiet")
settings = adc.get_config(solution)

# Get clock values
clock_values = {}
for clk in clks:
clock_values.update(clk.get_config(solution))
settings["clocks"] = clock_values

print(settings)

image_data = adc.draw(settings["clocks"])
assert image_data is not None


@pytest.mark.drawing
def test_xilinx_draw():
import adijif as jif
Expand Down

0 comments on commit 6b0bdc5

Please sign in to comment.