PyTCL allows control EDA tools directly from Python that use TCL.
- 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 Pythonstr
,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
pip install pytcl-eda
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()
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()
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>}
fromexecute.tcl
script filePyTCL
will call command line tool (by defaulttclsh
) withexecute.tcl
script file and argumentsreceiver.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 fromPyTCL
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 byexecute.tcl
script filePyTCL
will transform any Python method call<object>.<name>(*args)
to TCL expression<name> {*}${args}
PyTCL
will send TCL expression toexecute.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 TCLcatch
- 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 aspytcl.TCLValue
PyTCL
will raise a Python exceptionpytcl.TCLError
if received TCL status was non-zero
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