Skip to content

Latest commit

 

History

History
186 lines (146 loc) · 6.35 KB

README.md

File metadata and controls

186 lines (146 loc) · 6.35 KB

PyTCL

PyTCL allows control EDA tools directly from Python that use TCL.

Features

  • It executes Python method with provided positional arguments directly as TCL procedure For example invocation of Python <object>.<name>(*args) method is like calling TCL procedure <name> {*}${args}
  • Any Python value is converted to TCL value like for example Python list to TCL list
  • Result from invoked TCL procedure is returned as pytcl.TCLValue that can handle any TCL value (that is represented always as string) to Python str, int, bool, float, list, dict, ...
  • TCL error is returned as Python exception pytcl.TCLError
  • High performance and very low (unnoticeable) overhead by using Unix domain sockets for communication between Python and TCL in streamable way (sockets are always open and ready)
  • It allows to create and access TCL variables from Python side. Please see tests/test_tclsh.py for some examples
  • It can work with any EDA tool. Please see tests/test_vivado.py how to use bare PyTCL class for that
  • No external dependencies

Install

pip install pytcl-eda

Examples

Creating new Vivado project:

#!/usr/bin/env python3
from pathlib import Path
from pytcl import Vivado

def main() -> None:
    """Create new Vivado project."""
    hdl_dir: Path = Path.cwd() / "hdl"
    project_dir: Path = Path.cwd() / "my-awesome-project"

    with Vivado() as vivado:
        # See Vivado Design Suite Tcl Command Reference Guide (UG835) for all available Vivado TCL procedures
        # https://docs.amd.com/r/en-US/ug835-vivado-tcl-commands
        vivado.create_project(project_dir.name, project_dir)
        vivado.add_files(hdl_dir / "my_awesome_design.sv")

        synthesis_runs = list(vivado.get_runs("synth_*"))
        vivado.launch_runs(synthesis_runs)

        # wait_on_runs was introduced in Vivado 2021.2. For backward compatibility we will use wait_on_run
        # https://docs.amd.com/r/2021.2-English/ug835-vivado-tcl-commands/wait_on_runs
        # Vivado >= 2021.2 can just use: vivado.wait_on_runs(synthesis_runs)
        for run in synthesis_runs:
            vivado.wait_on_run(run)

        implementation_runs = list(vivado.get_runs("impl_*"))
        vivado.launch_runs(implementation_runs)

        for run in implementation_runs:
            vivado.wait_on_run(run)

        vivado.close_project()

if __name__ == "__main__":
    main()

To use any EDA tool where PyTCL doesn't provide neat helper classes like pytcl.Vivado you can use the pytcl.PyTCL class directly:

#!/usr/bin/env python3
from pathlib import Path
from pytcl import PyTCL

def main() -> None:
    """Create new Vivado project."""
    project_dir: Path = Path.cwd() / "my-awesome-project"

    # PyTCL offers some string placeholders {} that you can use:
    # {tcl}      -> it will insert <pytcl>/execute.tcl
    # {receiver} -> it will insert <pytcl>/receiver.tcl
    # {rx}       -> it will insert /tmp/pytcl-XXXXX/rx.sock
    # {sender}   -> it will insert <pytcl>/sender.tcl
    # {tx}       -> it will insert /tmp/pytcl-XXXXX/tx.sock
    # {args}     -> it will insert '{receier} {rx} {sender} {tx}' in one go
    cmd: list[str] = [
        "vivado",
        "-nojournal",
        "-notrace",
        "-nolog",
        "-mode",
        "batch",
        "-source",
        "{tcl}",
        "-tclargs",
        "{receiver}",
        "{rx}",
        "{sender}",
        "{tx}",
    ]

    with PyTCL(*cmd) as vivado:
        vivado.create_project(project_dir.name, project_dir)

        # Do the same magic that you would normally do in TCL

        vivado.close_project()

if __name__ == "__main__":
    main()

Architecture

stateDiagram-v2
    direction LR
    PyTCL --> rx.sock: send()
    rx.sock --> receiver.py: string
    state tool {
        receiver.py --> execute.tcl: stdin
        execute.tcl --> sender.py: stdout
    }
    sender.py --> tx.sock: NDJSON
    tx.sock --> PyTCL: recv()
Loading
  • PyTCL will start new receiver listened on Unix domain socket /tmp/pytcl-XXXX/tx.sock for any incoming NDJSON messages {"result": "<tcl-result>", "status": <tcl-status>} from execute.tcl script file
  • PyTCL will call command line tool (by default tclsh) with execute.tcl script file and arguments receiver.py /tmp/pytcl-XXXX/rx.sock sender.py /tmp/pytcl-XXXX/tx.sock
  • Started execute.tcl will create own listener with Unix domain socket /tmp/pytcl-XXXX/rx.sock to receive incoming TCL expressions from PyTCL
  • PyTCL will start new client and connect to Unix domain socket /tmp/pytcl-XXXX/rx.sock to send TCL expressions with arguments to be evaluated by execute.tcl script file
  • PyTCL will transform any Python method call <object>.<name>(*args) to TCL expression <name> {*}${args}
  • PyTCL will send TCL expression to execute.tcl using Unix domain socket /tmp/pytcl-XXXX/rx.sock
  • execute.tcl will receive TCL expressions from Unix domain socket /tmp/pytcl-XXXX/rx.sock
  • Received TCL expression is evaluated by TCL eval within TCL catch
  • TCL result and status from evaluated TCL expression will be packed into NDJSON message {"result": "<tcl-result>", "status": <tcl-status>}
  • Packed NDJSON message with TCL result and status will be send back to PyTCL
  • PyTCL will return received NDJSON message as pytcl.TCLValue
  • PyTCL will raise a Python exception pytcl.TCLError if received TCL status was non-zero

Development

Create Python virtual environment:

python3 -m venv .venv

Activate created Python virtual environment:

. .venv/bin/activate

Upgrade pip:

pip install --upgrade pip

Install project in editable mode with pytest:

pip install --editable .[test]

Run tests:

pytest