opencos-eda 0.3.9__py3-none-any.whl → 0.3.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opencos/commands/deps_help.py +89 -120
- opencos/commands/export.py +7 -2
- opencos/commands/multi.py +3 -3
- opencos/commands/sim.py +14 -16
- opencos/commands/synth.py +1 -2
- opencos/commands/upload.py +192 -4
- opencos/commands/waves.py +8 -8
- opencos/deps/deps_commands.py +6 -6
- opencos/deps/deps_file.py +82 -79
- opencos/deps/deps_processor.py +129 -50
- opencos/docs/Architecture.md +45 -0
- opencos/docs/ConnectingApps.md +29 -0
- opencos/docs/DEPS.md +199 -0
- opencos/docs/Debug.md +77 -0
- opencos/docs/DirectoryStructure.md +22 -0
- opencos/docs/Installation.md +117 -0
- opencos/docs/OcVivadoTcl.md +63 -0
- opencos/docs/OpenQuestions.md +7 -0
- opencos/docs/README.md +13 -0
- opencos/docs/RtlCodingStyle.md +54 -0
- opencos/docs/eda.md +147 -0
- opencos/docs/oc_cli.md +135 -0
- opencos/eda.py +175 -41
- opencos/eda_base.py +180 -50
- opencos/eda_config.py +62 -16
- opencos/eda_config_defaults.yml +21 -4
- opencos/eda_deps_bash_completion.bash +37 -15
- opencos/files.py +26 -1
- opencos/tools/cocotb.py +5 -5
- opencos/tools/invio.py +2 -2
- opencos/tools/invio_yosys.py +2 -1
- opencos/tools/iverilog.py +3 -3
- opencos/tools/quartus.py +113 -115
- opencos/tools/questa_common.py +3 -4
- opencos/tools/riviera.py +3 -3
- opencos/tools/slang.py +11 -7
- opencos/tools/slang_yosys.py +1 -0
- opencos/tools/surelog.py +4 -3
- opencos/tools/verilator.py +5 -4
- opencos/tools/vivado.py +307 -176
- opencos/tools/yosys.py +4 -4
- opencos/util.py +6 -3
- opencos/utils/dict_helpers.py +31 -0
- opencos/utils/markup_helpers.py +2 -2
- opencos/utils/str_helpers.py +7 -0
- opencos/utils/subprocess_helpers.py +3 -3
- opencos/utils/vscode_helper.py +2 -2
- opencos/utils/vsim_helper.py +58 -22
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/METADATA +1 -1
- opencos_eda-0.3.11.dist-info/RECORD +94 -0
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/entry_points.txt +1 -0
- opencos/tests/__init__.py +0 -0
- opencos/tests/custom_config.yml +0 -13
- opencos/tests/deps_files/command_order/DEPS.yml +0 -44
- opencos/tests/deps_files/error_msgs/DEPS.yml +0 -55
- opencos/tests/deps_files/iverilog_test/DEPS.yml +0 -4
- opencos/tests/deps_files/no_deps_here/DEPS.yml +0 -0
- opencos/tests/deps_files/non_sv_reqs/DEPS.yml +0 -50
- opencos/tests/deps_files/tags_with_tools/DEPS.yml +0 -54
- opencos/tests/deps_files/test_err_fatal/DEPS.yml +0 -4
- opencos/tests/helpers.py +0 -354
- opencos/tests/test_build.py +0 -12
- opencos/tests/test_deps_helpers.py +0 -207
- opencos/tests/test_deps_schema.py +0 -30
- opencos/tests/test_eda.py +0 -921
- opencos/tests/test_eda_elab.py +0 -110
- opencos/tests/test_eda_synth.py +0 -150
- opencos/tests/test_oc_cli.py +0 -25
- opencos/tests/test_tools.py +0 -404
- opencos_eda-0.3.9.dist-info/RECORD +0 -99
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.9.dist-info → opencos_eda-0.3.11.dist-info}/top_level.txt +0 -0
opencos/docs/eda.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# EDA Tool
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
Using `uv` (recommended):
|
|
6
|
+
|
|
7
|
+
From PyPI:
|
|
8
|
+
```
|
|
9
|
+
uv tool install opencos-eda
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
OR, from the repo:
|
|
13
|
+
```
|
|
14
|
+
uv tool install /path/to/your/checkout/dir/of/opencos
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This makes the `eda` and `oc_cli` commands available in your environment.
|
|
18
|
+
|
|
19
|
+
## Basic Recipes
|
|
20
|
+
|
|
21
|
+
Run a single simulation using the default simulator (of those installed).
|
|
22
|
+
`oclib_fifo_test` is target in `./lib/tests/DEPS.yml`
|
|
23
|
+
```
|
|
24
|
+
eda sim lib/tests/oclib_fifo_test
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Run the same single simulation, but use `verilator`, dump waves
|
|
28
|
+
```
|
|
29
|
+
eda sim --waves --tool verilator lib/tests/oclib_fifo_test
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Run the same single simulation, but use `vivado` XSim, and run in GUI mode
|
|
33
|
+
```
|
|
34
|
+
eda sim --gui --tool vivado lib/tests/oclib_fifo_test
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Run a compile + elab without a DEPS.yml file or target involved for dependencies
|
|
38
|
+
```
|
|
39
|
+
eda elab --tool verilator +incdir+. ./lib/oclib_assert_pkg.sv ./lib/oclib_simple_reset_sync.sv
|
|
40
|
+
|
|
41
|
+
## Basic CLI usage:
|
|
42
|
+
eda <command> [--args] [+incdir+DIR|+define+KEY=VALUE|+plusarg=value] file1.sv file2.sv file3.sv
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Example Regression testing
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
eda multi sim '.../*_test' --parallel 16
|
|
49
|
+
eda multi elab 'lib/*' --parallel 16
|
|
50
|
+
eda multi elab 'sim/*' --parallel 16
|
|
51
|
+
eda multi elab 'top/*' --parallel 16
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If you'd like output to stdout, which is how our github Actions are run, use `--verbose` with `eda multi`
|
|
55
|
+
```
|
|
56
|
+
eda multi sim --verbose lib/tests/oc*test
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Example "sweep"
|
|
60
|
+
|
|
61
|
+
Sweeping a build across a range of parameters
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
eda sweep build u200 --seed=SEED "SEED=(1,2)" +define+OC_MEMORY_BIST_PORT_COUNT=PORTS "PORTS=[1,4,8,16,32]" +define+TARGET_PLL0_CLK_HZ=MHZ000000 "MHZ=(200,400,50)" --parallel 12
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Example for building treating non .sv file(s) as systemverilog
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
eda sim --tool verilator sv@several_modules.txt --top=my_module
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Note that you can prefix source files with `sv@`, `v@`, `vhdl@` or `cpp@` if the file contents do not match their filename extension, and you would like `eda` to force use that file as, for example, systemverilog.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Help
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
eda help
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
$ eda help
|
|
84
|
+
INFO: [EDA] eda: version X.Y.Z
|
|
85
|
+
INFO: [EDA] eda_config: --config-yml=eda_config_defaults.yml observed
|
|
86
|
+
INFO: [EDA] eda_config: using config: ..../site-packages/opencos/eda_config_defaults.yml
|
|
87
|
+
INFO: [EDA] *** OpenCOS EDA ***
|
|
88
|
+
INFO: [EDA] Detected slang (/usr/local/bin/slang), auto-setting up tool slang
|
|
89
|
+
INFO: [EDA] Detected verilator (/usr/local/bin/verilator), auto-setting up tool verilator
|
|
90
|
+
INFO: [EDA] Detected surelog (/usr/local/bin/surelog), auto-setting up tool surelog
|
|
91
|
+
INFO: [EDA] Detected gtkwave (/usr/bin/gtkwave), auto-setting up tool gtkwave
|
|
92
|
+
INFO: [EDA] Detected vivado (/tools/Xilinx/Vivado/VVVV.v/bin/vivado), auto-setting up tool vivado
|
|
93
|
+
INFO: [EDA] Detected slang_yosys (/usr/local/bin/yosys), auto-setting up tool slang_yosys
|
|
94
|
+
INFO: [EDA] Detected iverilog (/usr/local/bin/iverilog), auto-setting up tool iverilog
|
|
95
|
+
|
|
96
|
+
Usage:
|
|
97
|
+
eda [<options>] <command> [options] <files|targets, ...>
|
|
98
|
+
|
|
99
|
+
Where <command> is one of:
|
|
100
|
+
|
|
101
|
+
sim - Simulates a DEPS target
|
|
102
|
+
elab - Elaborates a DEPS target (sort of sim based LINT)
|
|
103
|
+
synth - Synthesizes a DEPS target
|
|
104
|
+
flist - Create dependency from a DEPS target
|
|
105
|
+
proj - Create a project from a DEPS target for GUI sim/waves/debug
|
|
106
|
+
multi - Run multiple DEPS targets, serially or in parallel
|
|
107
|
+
tools-multi - Same as 'multi' but run on all available tools, or specfied using --tools
|
|
108
|
+
sweep - Sweep one or more arguments across a range, serially or in parallel
|
|
109
|
+
build - Build for a board, creating a project and running build flow
|
|
110
|
+
waves - Opens waveform from prior simulation
|
|
111
|
+
upload - Uploads a finished design into hardware
|
|
112
|
+
open - Opens a project
|
|
113
|
+
export - Export files related to a target, tool independent
|
|
114
|
+
help - This help (without args), or i.e. "eda help sim" for specific help
|
|
115
|
+
|
|
116
|
+
And <files|targets, ...> is one or more source file or DEPS markup file target,
|
|
117
|
+
such as .v, .sv, .vhd[l], .cpp files, or a target key in a DEPS.[yml|yaml|toml|json].
|
|
118
|
+
Note that you can prefix source files with `sv@`, `v@`, `vhdl@` or `cpp@` to
|
|
119
|
+
force use that file as systemverilog, verilog, vhdl, or C++, respectively.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
opencos common options:
|
|
123
|
+
--version
|
|
124
|
+
--color, --no-color Use shell colors for info/warning/error messaging (default: True)
|
|
125
|
+
--quiet, --no-quiet Do not display info messaging
|
|
126
|
+
--verbose, --no-verbose
|
|
127
|
+
Display additional messaging level 2 or higher
|
|
128
|
+
--fancy, --no-fancy
|
|
129
|
+
--debug, --no-debug Display additional debug messaging level 1 or higher
|
|
130
|
+
--debug-level DEBUG_LEVEL
|
|
131
|
+
Set debug level messaging (default: 0)
|
|
132
|
+
--logfile LOGFILE Write eda messaging to logfile (default disabled)
|
|
133
|
+
--force-logfile FORCE_LOGFILE
|
|
134
|
+
Set to force overwrite the logfile
|
|
135
|
+
--no-respawn Legacy mode (default respawn disabled) for respawning eda.py using $OC_ROOT/bin
|
|
136
|
+
|
|
137
|
+
opencos eda config options:
|
|
138
|
+
--config-yml CONFIG_YML
|
|
139
|
+
YAML filename to use for configuration (default eda_config_defaults.yml)
|
|
140
|
+
|
|
141
|
+
eda options:
|
|
142
|
+
-q, --quit For interactive mode (eda called with no options, command, or targets)
|
|
143
|
+
--exit same as --quit
|
|
144
|
+
-h, --help
|
|
145
|
+
--tool TOOL Tool to use for this command, such as: modelsim_ase, verilator, modelsim_ase=/path/to/bin/vsim, verilator=/path/to/bin/verilator
|
|
146
|
+
--eda-safe disable all DEPS file deps shell commands, overrides values from --config-yml
|
|
147
|
+
```
|
opencos/docs/oc_cli.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
|
|
2
|
+
USAGE
|
|
3
|
+
=====
|
|
4
|
+
|
|
5
|
+
[sudo] oc_cli --serial <port>
|
|
6
|
+
|
|
7
|
+
<port> can be COM<x> (Windows) or /dev/ttyUSB<x> (Linux). Depending on permissions for /dev/ttyUSB<x>, sudo maybe required.
|
|
8
|
+
|
|
9
|
+
<port> can also be "tcp:<ip>:<port>:<device>" for connecting via TCP-to-UART bridge.
|
|
10
|
+
|
|
11
|
+
GLOBAL COMMANDS
|
|
12
|
+
===============
|
|
13
|
+
|
|
14
|
+
info
|
|
15
|
+
^^ prints compile-time info (UUID, build time/date, included userspace, etc)
|
|
16
|
+
|
|
17
|
+
scan
|
|
18
|
+
^^ scans the top level (ring) interfaces and reports the type of IP on each channel
|
|
19
|
+
|
|
20
|
+
perf (NOT PORTED YET)
|
|
21
|
+
^^ does a performance test of the comms channel
|
|
22
|
+
|
|
23
|
+
debug <n>
|
|
24
|
+
^^ set debug level <n>. without argument, reports debug level.
|
|
25
|
+
|
|
26
|
+
ping
|
|
27
|
+
^^ sends <CR> and checks for a prompt, sends "^" and checks for a syntax error response.
|
|
28
|
+
|
|
29
|
+
reset
|
|
30
|
+
^^ sends 64x '!' characters, which initiates a full chip reset regardless of the state of the device.
|
|
31
|
+
|
|
32
|
+
source <script file>
|
|
33
|
+
^^ reads in a script with oc_cli commands
|
|
34
|
+
|
|
35
|
+
import <python file>
|
|
36
|
+
^^ reads in Python code to extend the commands oc_cli understands. Typically used to pull in a userspace "driver". Can also be used to "live patch" oc_cli during development work.
|
|
37
|
+
|
|
38
|
+
set <var> <value>
|
|
39
|
+
^^ sets a variable for use in oc_cli commands. Refer to variables with $<var> syntax. Typically used to enable scripts (see 'source') that are independent of channel allocation in the target (for example, 'set user_ch 4' and then a script doing 'memtest $user_ch xxx')
|
|
40
|
+
|
|
41
|
+
read|rd|r <address> [<channel> <address space>]
|
|
42
|
+
^^ reads a CSR in the device. <channel> and <address space> are optional after the first rd/wr command; if not provided, the prior values will be used.
|
|
43
|
+
|
|
44
|
+
write|wr|w <address> <data> [<channel> <address space>]
|
|
45
|
+
^^ writes a CSR in the device. <channel> and <address space> are optional after the first rd/wr command; if not provided, the prior values will be used.
|
|
46
|
+
|
|
47
|
+
quit|q
|
|
48
|
+
^^ exit oc_cli
|
|
49
|
+
|
|
50
|
+
PLL COMMANDS
|
|
51
|
+
============
|
|
52
|
+
|
|
53
|
+
PLL commands are valid for channels that have a PLL IP (use 'scan' if not sure)
|
|
54
|
+
|
|
55
|
+
pll <channel>
|
|
56
|
+
^^ dumps the state of the PLL
|
|
57
|
+
|
|
58
|
+
pll <channel> measure
|
|
59
|
+
^^ measure clock 0 (for now) of the PLL on <channel>
|
|
60
|
+
|
|
61
|
+
pll <channel> reset
|
|
62
|
+
^^ reset PLL on <channel>. Generally required after changing multipliers, dividers, etc.
|
|
63
|
+
|
|
64
|
+
pll <channel> all_up (or all_down)
|
|
65
|
+
^^ move all clocks on PLL up (or down) in frequency, by changing the feedback divider. does not bounds check.
|
|
66
|
+
|
|
67
|
+
pll <channel> clk0_up (or clk0_down)
|
|
68
|
+
^^ move CLK0 on PLL up (or down) in frequency, by changing the fractional divider. does not bounds check.
|
|
69
|
+
|
|
70
|
+
pll <channel> throttle <level>
|
|
71
|
+
^^ throttles the output clock 0 (for now). <level> is 0-8, in 12.5% steps, with 0 meaning "no throttling" and 8 meaning "complete stop".
|
|
72
|
+
|
|
73
|
+
pll <channel> freq <mhz>
|
|
74
|
+
^^ attempts to set the clock of PLL <channel> to <mhz>. NOT IMPLEMENTED YET!!! (it's non-trivial)
|
|
75
|
+
|
|
76
|
+
pll <channel> ramp <command list>
|
|
77
|
+
^^ runs <command list> (an arbitrary oc_cli command line) at the current clock speeds, then ramps up the speed by reducing the fractional divider on the clock. This will test some limited range (for example 350-410MHz). To go beyond this, manually changing VCO freq, dividers, etc, will be required.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
CHIPMON COMMANDS
|
|
81
|
+
================
|
|
82
|
+
|
|
83
|
+
CHIPMON commands are valid for channels that have a CHIPMON IP (use 'scan' if not sure)
|
|
84
|
+
|
|
85
|
+
chipmon <channel>
|
|
86
|
+
^^ dumps the state of the CHIPMON
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
PROTECT COMMANDS
|
|
90
|
+
================
|
|
91
|
+
|
|
92
|
+
PROTECT commands are valid for channels that have a PROTECT IP (use 'scan' if not sure)
|
|
93
|
+
|
|
94
|
+
protect <channel>
|
|
95
|
+
^^ dumps the state of the PROTECT, including the bitstream ID and the FPGA's unique DNA fingerprint (both of which usually required to get a license)
|
|
96
|
+
|
|
97
|
+
protect <channel> unlock <key>
|
|
98
|
+
^^ unlocks the bitstream protection using <key>
|
|
99
|
+
|
|
100
|
+
MEMTEST COMMANDS
|
|
101
|
+
================
|
|
102
|
+
|
|
103
|
+
MEMTEST is available in the default userspace, and will not be in other userspaces unless specifically included (in which case, there maybe other commands needed to configure the userspace to connect memtest to the memory channels being tested).
|
|
104
|
+
|
|
105
|
+
memtest <channel>
|
|
106
|
+
^^ dumps the state of the MEMTEST
|
|
107
|
+
|
|
108
|
+
memtest <channel> <args>
|
|
109
|
+
^^ <args> is:
|
|
110
|
+
write - enables writing memory
|
|
111
|
+
read - enables reading memory
|
|
112
|
+
verbose=<x> - set verbosity to <n> (default 1)
|
|
113
|
+
ops=<x> - run <x> operations per port under test (default 1)
|
|
114
|
+
addr=<x> - <x> is the base address in memory (default 0)
|
|
115
|
+
addr_incr=<x> - <x> as the amount to increment per op (default 0x20, 32 Bytes)
|
|
116
|
+
addr_incr_mask=<x> - <x> is a bitmask applied to the increment value before ORing with addr (default 0xffffffffffffffff)
|
|
117
|
+
addr_rand_mask=<x> - <x> is a bitmask applied to a random value before ORing with addr (default 0)
|
|
118
|
+
addr_port_shift=<x> - <x> is the amount to shift the port number to the left before ORing with addr (default 0)
|
|
119
|
+
addr_port_mask=<x> - <x> is a bitmask applied to the shifted port number before ORing with addr (default 0)
|
|
120
|
+
waits=<x> - <x> is the number of waitstates between ops (default 0)
|
|
121
|
+
burst=<x> - <x> is the number of words in each op (default 1). Note the CSR actually will hold (burst-1).
|
|
122
|
+
pattern=<x> - <x> is a 32-bit seed for pattern generation (used to generate write data) (default 0x11111111).
|
|
123
|
+
signature=<x> - <x> is the expected signature (which is a combination of all read data)
|
|
124
|
+
write_max_id - <x> is the highest AXI ID used for issuing writes (default 0)
|
|
125
|
+
read_max_id - <x> is the highest AXI ID used for issuing reads (default 0)
|
|
126
|
+
prescale - sets the counter prescaler, will divide counters by N so that they can be sampled at the end of long tests
|
|
127
|
+
write_len_rand - enables random write length
|
|
128
|
+
read_len_rand - enables random read length
|
|
129
|
+
write_data_rotate - enables write data rotation, ensures bus toggling without random data (which can be harder to make sense of in waves)
|
|
130
|
+
write_data_random - enables write data randomization (still 'pseudorandom' and will be repeatable in terms of read signature)
|
|
131
|
+
measure - report AXI-Lite and AXI3 clock speeds, cycles under reset, and cycles since reset
|
|
132
|
+
nopoll - kick off the MEMTEST engine, but don't wait for completion, so oc_cli can be used to monitor temps, etc
|
|
133
|
+
stats - report stats (latency, contexts) in summary, or per-port (verbose>1)
|
|
134
|
+
get_sigs - report per-port signatures
|
|
135
|
+
signature_<p>=<x> - set port <p> expected signature to <x> (default for all ports is -1, which means don't check)
|
opencos/eda.py
CHANGED
|
@@ -9,7 +9,6 @@ markup files
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import os
|
|
11
11
|
import sys
|
|
12
|
-
import shutil
|
|
13
12
|
import re
|
|
14
13
|
import signal
|
|
15
14
|
import argparse
|
|
@@ -20,10 +19,10 @@ from pathlib import Path
|
|
|
20
19
|
|
|
21
20
|
import opencos
|
|
22
21
|
from opencos import util, eda_config, eda_base
|
|
22
|
+
from opencos.eda_base import Command, Tool, which_tool, print_eda_usage_line
|
|
23
|
+
from opencos.files import safe_shutil_which
|
|
23
24
|
from opencos.util import safe_emoji, Colors
|
|
24
|
-
from opencos.eda_base import Tool, which_tool, get_eda_exec, print_eda_usage_line
|
|
25
25
|
from opencos.utils import vsim_helper, vscode_helper
|
|
26
|
-
from opencos.utils.subprocess_helpers import subprocess_run_background
|
|
27
26
|
from opencos.utils import status_constants, str_helpers, subprocess_helpers
|
|
28
27
|
|
|
29
28
|
# Configure util:
|
|
@@ -44,7 +43,8 @@ util.global_log.default_log_disable_with_args.extend([
|
|
|
44
43
|
# These are also overriden depending on the tool, for example --tool verilator sets
|
|
45
44
|
# "sim": CommandSimVerilator.
|
|
46
45
|
def init_config(
|
|
47
|
-
config: dict, quiet: bool = False, tool=None,
|
|
46
|
+
config: dict, quiet: bool = False, tool=None, command: str = '',
|
|
47
|
+
run_auto_tool_setup: bool = True
|
|
48
48
|
) -> dict:
|
|
49
49
|
'''Sets or clears entries in config (dict) so tools can be re-loaded.'''
|
|
50
50
|
|
|
@@ -64,7 +64,7 @@ def init_config(
|
|
|
64
64
|
config['auto_tools_found'] = {}
|
|
65
65
|
config['tools_loaded'] = set()
|
|
66
66
|
if run_auto_tool_setup:
|
|
67
|
-
config = auto_tool_setup(config=config, quiet=quiet, tool=tool)
|
|
67
|
+
config = auto_tool_setup(config=config, quiet=quiet, tool=tool, command=command)
|
|
68
68
|
return config
|
|
69
69
|
|
|
70
70
|
|
|
@@ -137,7 +137,11 @@ def interactive(config: dict) -> int:
|
|
|
137
137
|
'''
|
|
138
138
|
rc = 0
|
|
139
139
|
while True:
|
|
140
|
-
|
|
140
|
+
try:
|
|
141
|
+
line = input('EDA->')
|
|
142
|
+
except EOFError:
|
|
143
|
+
util.info('End of input reached unexpectedly, exiting')
|
|
144
|
+
return 0
|
|
141
145
|
m = re.match(r'^([^\#]*)\#.*$', line)
|
|
142
146
|
if m:
|
|
143
147
|
line = m.group(1)
|
|
@@ -154,7 +158,8 @@ def interactive(config: dict) -> int:
|
|
|
154
158
|
|
|
155
159
|
|
|
156
160
|
def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
|
157
|
-
warnings: bool = True, config = None, quiet: bool = False, tool: str = ''
|
|
161
|
+
warnings: bool = True, config = None, quiet: bool = False, tool: str = '',
|
|
162
|
+
command: str = ''
|
|
158
163
|
) -> dict:
|
|
159
164
|
'''Returns an updated config, uses config['auto_tools_order'][0] dict, calls tool_setup(..)
|
|
160
165
|
|
|
@@ -172,9 +177,26 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
172
177
|
assert isinstance(config['auto_tools_order'], list)
|
|
173
178
|
assert isinstance(config['auto_tools_order'][0], dict)
|
|
174
179
|
|
|
180
|
+
if command:
|
|
181
|
+
util.info(f'Auto tool setup for command: {Colors.byellow}{command}')
|
|
182
|
+
|
|
175
183
|
for name, value in config['auto_tools_order'][0].items():
|
|
176
184
|
if tool and tool != name:
|
|
177
|
-
|
|
185
|
+
# if called with tool=(some_name), then only load that tool (which is not
|
|
186
|
+
# this one)
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
if command and command not in value.get('handlers', {}) and \
|
|
190
|
+
command not in config.get('command_has_subcommands', []):
|
|
191
|
+
# Skip tool_setup(..) if the tool handlers can't support command,
|
|
192
|
+
# this is a time-saving feature, but if the comman is multi, tools-multi,
|
|
193
|
+
# sweep, then don't skip this (we don't know what tools we need so load them
|
|
194
|
+
# all.
|
|
195
|
+
# We could figure this out if we went looking for all command(s)
|
|
196
|
+
# multi + sub-command, but that's slightly dangerous if we grab a 'command'
|
|
197
|
+
# from another arg.
|
|
198
|
+
util.debug(f"Skipping tool {name} because it cannot handle {command=}")
|
|
199
|
+
continue
|
|
178
200
|
|
|
179
201
|
util.debug(f"Checking for ability to run tool: {name}")
|
|
180
202
|
exe = value.get('exe', str())
|
|
@@ -204,9 +226,12 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
204
226
|
|
|
205
227
|
has_all_exe = True
|
|
206
228
|
has_all_in_exe_path = True
|
|
229
|
+
exe_path = None
|
|
207
230
|
for exe in exe_list:
|
|
208
231
|
assert exe != '', f'{name=} {value=} value missing "exe" {exe=}'
|
|
209
|
-
p =
|
|
232
|
+
p = safe_shutil_which(exe)
|
|
233
|
+
if not exe_path:
|
|
234
|
+
exe_path = p # set on first required exe
|
|
210
235
|
if not p:
|
|
211
236
|
has_all_exe = False
|
|
212
237
|
util.debug(f"... No, missing exe {exe}")
|
|
@@ -217,10 +242,11 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
217
242
|
|
|
218
243
|
has_vsim_helper = True
|
|
219
244
|
if value.get('requires_vsim_helper', False):
|
|
220
|
-
# This tool name must be in opencos.utils.vsim_helper.
|
|
245
|
+
# This tool name must be in opencos.utils.vsim_helper.TOOL_PATH[name].
|
|
221
246
|
# Special case for vsim being used by a lot of tools.
|
|
222
247
|
vsim_helper.init() # only runs checks once internally
|
|
223
|
-
|
|
248
|
+
exe_path = vsim_helper.TOOL_PATH[name]
|
|
249
|
+
has_vsim_helper = bool(exe_path)
|
|
224
250
|
|
|
225
251
|
has_vscode_helper = True
|
|
226
252
|
needs_vscode_extensions = value.get('requires_vscode_extension', None)
|
|
@@ -254,18 +280,28 @@ def auto_tool_setup( # pylint: disable=too-many-locals,too-many-branches,too-man
|
|
|
254
280
|
|
|
255
281
|
if all((has_all_py, has_all_env, has_all_exe, has_all_in_exe_path,
|
|
256
282
|
has_vsim_helper, has_vscode_helper)):
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
283
|
+
if exe_path:
|
|
284
|
+
p = exe_path
|
|
285
|
+
else:
|
|
286
|
+
p = safe_shutil_which(exe_list[0])
|
|
287
|
+
config['auto_tools_found'][name] = p # populate key-value pairs w/ first exe in list
|
|
260
288
|
if not quiet:
|
|
261
289
|
util.info(f"Detected {name} ({p})")
|
|
262
|
-
tool_setup(
|
|
290
|
+
tool_setup(
|
|
291
|
+
tool=name, quiet=True, auto_setup=(not tool), warnings=warnings, config=config
|
|
292
|
+
)
|
|
293
|
+
else:
|
|
294
|
+
util.debug(f'Tool {name} is missing one of: {has_all_py=} {has_all_env=}',
|
|
295
|
+
f'{has_all_exe=} {has_all_in_exe_path=} {has_vsim_helper=}',
|
|
296
|
+
f'{has_vscode_helper=}')
|
|
263
297
|
|
|
264
298
|
return config
|
|
265
299
|
|
|
266
300
|
|
|
267
|
-
def tool_setup(
|
|
268
|
-
|
|
301
|
+
def tool_setup( # pylint: disable=too-many-branches
|
|
302
|
+
tool: str, config: dict, quiet: bool = False, auto_setup: bool = False,
|
|
303
|
+
warnings: bool = True
|
|
304
|
+
):
|
|
269
305
|
''' Adds items to config["tools_loaded"] (set) and updates config['command_handler'].
|
|
270
306
|
|
|
271
307
|
config is potentially updated for entry ['command_handler'][command] with a Tool class.
|
|
@@ -322,10 +358,22 @@ def tool_setup(tool: str, config: dict, quiet: bool = False, auto_setup: bool =
|
|
|
322
358
|
|
|
323
359
|
cls = util.import_class_from_string(str_class_name)
|
|
324
360
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
361
|
+
if command in config.get('command_determines_tool', []) + \
|
|
362
|
+
config.get('command_tool_is_optional', []):
|
|
363
|
+
# we don't need to confirm the handler parent is a Tool class.
|
|
364
|
+
pass
|
|
365
|
+
else:
|
|
366
|
+
assert issubclass(cls, Tool), \
|
|
367
|
+
f'{str_class_name=} is does not have Tool class associated with it'
|
|
368
|
+
|
|
369
|
+
if not auto_setup or \
|
|
370
|
+
command not in config.get('command_determines_tool', []):
|
|
371
|
+
# If not auto_setup - then someone called this --tool by name on the command line,
|
|
372
|
+
# then update the command handler
|
|
373
|
+
# otherwise, if --tool was not set, and command determines tool, leave it with
|
|
374
|
+
# the default handler.
|
|
375
|
+
util.debug(f'Setting {cls=} for {command=} in config.command_handler')
|
|
376
|
+
config['command_handler'][command] = cls
|
|
329
377
|
|
|
330
378
|
config['tools_loaded'].add(tool)
|
|
331
379
|
|
|
@@ -348,7 +396,6 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
348
396
|
deferred_tokens = []
|
|
349
397
|
command = ""
|
|
350
398
|
run_auto_tool_setup = True
|
|
351
|
-
process_tokens_cwd = os.getcwd()
|
|
352
399
|
|
|
353
400
|
parser = eda_base.get_argparser()
|
|
354
401
|
try:
|
|
@@ -375,6 +422,8 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
375
422
|
|
|
376
423
|
# Attempt to get the 'command' in the unparsed args before we've even
|
|
377
424
|
# set the command handlers (some commands don't use tools).
|
|
425
|
+
# Note that we only grab the first command, and for multi, tools-multi,
|
|
426
|
+
# or sweep we do NOT get the subcommand.
|
|
378
427
|
for value in unparsed:
|
|
379
428
|
if value in config['DEFAULT_HANDLERS'].keys():
|
|
380
429
|
command = value
|
|
@@ -390,7 +439,7 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
390
439
|
# This will handle any --tool=<name>=/path/to/bin also, so don't have to
|
|
391
440
|
# run tool_setup(..) on its own.
|
|
392
441
|
config = init_config(
|
|
393
|
-
config, tool=parsed.tool,
|
|
442
|
+
config, tool=parsed.tool, command=command,
|
|
394
443
|
run_auto_tool_setup=run_auto_tool_setup
|
|
395
444
|
)
|
|
396
445
|
if not config:
|
|
@@ -457,26 +506,82 @@ def process_tokens( # pylint: disable=too-many-branches,too-many-statements,too-
|
|
|
457
506
|
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(sco)=}, {unparsed=}')
|
|
458
507
|
|
|
459
508
|
if rc == 0 and not parsed.tool and getattr(sco, 'tool_changed_respawn', False):
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
return status_constants.EDA_DEFAULT_ERROR
|
|
464
|
-
|
|
465
|
-
# close the util.log:
|
|
466
|
-
util.stop_log()
|
|
467
|
-
# respawn the original job, but with --tool=<use_tool> applied:
|
|
468
|
-
_command_list = [get_eda_exec(command), f"--tool={use_tool}"] + original_args
|
|
469
|
-
util.info(f'eda: respawn for tool change: {" ".join(_command_list)};',
|
|
470
|
-
f' (running from: {process_tokens_cwd})')
|
|
471
|
-
subprocess_run_background(
|
|
472
|
-
work_dir=process_tokens_cwd,
|
|
473
|
-
command_list=_command_list,
|
|
474
|
-
background=util.args.get('quiet', False)
|
|
509
|
+
return respawn_new_sub_command_object(
|
|
510
|
+
sco=sco, parsed=parsed, config=config, command=command, tokens=tokens,
|
|
511
|
+
deferred_tokens=deferred_tokens
|
|
475
512
|
)
|
|
476
513
|
|
|
477
514
|
return rc
|
|
478
515
|
|
|
479
516
|
|
|
517
|
+
def respawn_new_sub_command_object(
|
|
518
|
+
sco: Command, parsed: argparse.Namespace, config: dict, command: str,
|
|
519
|
+
tokens: list, deferred_tokens: list
|
|
520
|
+
) -> int:
|
|
521
|
+
'''Returns retcode (int). Creates a new Command object, presumably using a different tool,
|
|
522
|
+
|
|
523
|
+
due to args changes from DEPS parsing that led to --tool=<different value> vs the automatic
|
|
524
|
+
value if --tool was not originally set. Will run process_tokens(..) on the new sub commmand
|
|
525
|
+
object.
|
|
526
|
+
'''
|
|
527
|
+
|
|
528
|
+
use_tool = sco.args.get('tool', '')
|
|
529
|
+
|
|
530
|
+
if not use_tool:
|
|
531
|
+
util.error(f'Unable to change tool from {parsed.tool}, internal eda problem.')
|
|
532
|
+
return status_constants.EDA_DEFAULT_ERROR
|
|
533
|
+
|
|
534
|
+
util.info(f'Changing {Colors.bcyan}--tool{Colors.normal}{Colors.green} --->',
|
|
535
|
+
f'{Colors.bcyan}{use_tool}{Colors.normal}{Colors.green} for command:',
|
|
536
|
+
f'{Colors.byellow}{command}')
|
|
537
|
+
|
|
538
|
+
# Update the command handler(s) with this new tool. We don't really respawn, just
|
|
539
|
+
# try to swap out the sco (Command obj handle)
|
|
540
|
+
entry = config['auto_tools_order'][0].get(use_tool, {})
|
|
541
|
+
tool_cmd_handler_dict = entry.get('handlers', {})
|
|
542
|
+
|
|
543
|
+
for _command, str_class_name in tool_cmd_handler_dict.items():
|
|
544
|
+
if _command and command and _command != command:
|
|
545
|
+
# This isn't the command we care about (it's just one of the commands
|
|
546
|
+
# this tool supports, so don't bother loading a handler for it:
|
|
547
|
+
continue
|
|
548
|
+
|
|
549
|
+
cls = util.import_class_from_string(str_class_name)
|
|
550
|
+
if _command in config.get('command_determines_tool', []) + \
|
|
551
|
+
config.get('command_tool_is_optional', []):
|
|
552
|
+
# we don't need to confirm the handler parent is a Tool class.
|
|
553
|
+
pass
|
|
554
|
+
else:
|
|
555
|
+
assert issubclass(cls, Tool), \
|
|
556
|
+
f'command {_command} {str_class_name=} does not have Tool class associated with it'
|
|
557
|
+
|
|
558
|
+
util.debug(f'Setting {cls=} for command={_command} in config.command_handler')
|
|
559
|
+
config['command_handler'][_command] = cls
|
|
560
|
+
|
|
561
|
+
old_sco = sco
|
|
562
|
+
sco = config['command_handler'][command](config=config) # sub command object
|
|
563
|
+
util.debug(f'No longer using handler: {type(old_sco)}; now using: {type(sco)}')
|
|
564
|
+
sco.config['eda_original_args'] = old_sco.config['eda_original_args']
|
|
565
|
+
del old_sco
|
|
566
|
+
|
|
567
|
+
rc = check_command_handler_cls(command_obj=sco, command=command, parsed_args=parsed)
|
|
568
|
+
if rc > 0:
|
|
569
|
+
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(sco)=},'
|
|
570
|
+
f'unparsed={deferred_tokens}')
|
|
571
|
+
return rc
|
|
572
|
+
|
|
573
|
+
setattr(sco, 'command_name', command) # as a safeguard, 'command' set in 'sco'
|
|
574
|
+
util.info(f'--tool={use_tool}: running command: {Colors.byellow}eda {command} ',
|
|
575
|
+
' '.join(deferred_tokens))
|
|
576
|
+
unparsed = sco.process_tokens(tokens=deferred_tokens, pwd=os.getcwd())
|
|
577
|
+
|
|
578
|
+
# query the status from the Command object (0 is pass, > 0 is fail, but we'd prefer to
|
|
579
|
+
# avoid rc=1 because that's the python exception rc)
|
|
580
|
+
rc = getattr(sco, 'status', 2)
|
|
581
|
+
util.debug(f'Return from main process_tokens({tokens=}), {rc=}, {type(sco)=}, {unparsed=}')
|
|
582
|
+
return rc
|
|
583
|
+
|
|
584
|
+
|
|
480
585
|
def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> int:
|
|
481
586
|
'''Returns bash/sh return code, checks that a command handling class has all
|
|
482
587
|
|
|
@@ -514,7 +619,7 @@ def check_command_handler_cls(command_obj:object, command:str, parsed_args) -> i
|
|
|
514
619
|
if k == 'exe' or k.startswith('requires_cmd'):
|
|
515
620
|
util.warning(
|
|
516
621
|
f"tool '{parsed_tool}' has requirements that may not have been met --",
|
|
517
|
-
f"{k}: {v}"
|
|
622
|
+
f"{k}: {v} (Perhaps not in PATH?)"
|
|
518
623
|
)
|
|
519
624
|
if k == 'requires_vsim_helper':
|
|
520
625
|
if found_tool := vsim_helper.found():
|
|
@@ -585,7 +690,8 @@ def main(*args):
|
|
|
585
690
|
if not util.args['quiet']:
|
|
586
691
|
util.info(f'eda: version {opencos.__version__}', color=Colors.bcyan)
|
|
587
692
|
# And show the command that was run (all args):
|
|
588
|
-
util.info(f"main: eda {' '.join(args)}
|
|
693
|
+
util.info(f"main: {Colors.byellow}eda {' '.join(args)}{Colors.normal}{Colors.green};",
|
|
694
|
+
f"(run from {os.getcwd()})")
|
|
589
695
|
|
|
590
696
|
# Handle --config-yml= arg
|
|
591
697
|
config, unparsed = eda_config.get_eda_config(unparsed)
|
|
@@ -613,13 +719,41 @@ def main(*args):
|
|
|
613
719
|
util.global_log.stop()
|
|
614
720
|
return main_ret
|
|
615
721
|
|
|
722
|
+
def main_show_autocomplete_instructions() -> None:
|
|
723
|
+
''' Executable script entry point - eda_show_autocomplete
|
|
724
|
+
|
|
725
|
+
from pyproject.toml::project.scripts. Shows instructions on how to enable bash autocomplete
|
|
726
|
+
'''
|
|
727
|
+
source_filepath = opencos.__file__.replace(
|
|
728
|
+
'__init__.py', 'eda_deps_bash_completion.bash'
|
|
729
|
+
)
|
|
730
|
+
if os.path.exists(source_filepath):
|
|
731
|
+
print(
|
|
732
|
+
f"{Colors.yellow}To enable bash autocompletion with"
|
|
733
|
+
f" {Colors.bold}eda{Colors.normal}{Colors.yellow} script (uv not equired):"
|
|
734
|
+
)
|
|
735
|
+
print(f"{Colors.normal}")
|
|
736
|
+
print(f" source {source_filepath}")
|
|
737
|
+
print("")
|
|
738
|
+
print(f"{Colors.yellow}Feel free to inspect this script prior to sourcing.")
|
|
739
|
+
print(f"{Colors.normal}")
|
|
740
|
+
sys.exit(0)
|
|
741
|
+
else:
|
|
742
|
+
util.error(f"It appears the following file(s) doe not exist: {source_filepath}")
|
|
743
|
+
sys.exit(1)
|
|
744
|
+
|
|
616
745
|
|
|
617
746
|
def main_cli() -> None:
|
|
618
|
-
'''
|
|
747
|
+
''' Executable script entry point - eda
|
|
748
|
+
|
|
749
|
+
from pyproject.tom::project.scripts, or from __main__.
|
|
750
|
+
Returns None, will exit with return code.
|
|
751
|
+
'''
|
|
619
752
|
signal.signal(signal.SIGINT, signal_handler)
|
|
620
753
|
util.global_exit_allowed = True
|
|
621
754
|
# Strip eda or eda.py from sys.argv, we know who we are if called from __main__:
|
|
622
755
|
rc = main()
|
|
756
|
+
subprocess_helpers.cleanup_all()
|
|
623
757
|
util.exit(rc)
|
|
624
758
|
|
|
625
759
|
|