Coverage for apio/scons/plugin_base.py: 91%
63 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 10:20 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 10:20 +0000
1# -*- coding: utf-8 -*-
2# -- This file is part of the Apio project
3# -- (C) 2016-2018 FPGAwars
4# -- Author Jesús Arroyo
5# -- License GPLv2
6# -- Derived from:
7# ---- Platformio project
8# ---- (C) 2014-2016 Ivan Kravets <me@ikravets.com>
9# ---- License Apache v2
11"""Apio scons related utilities.."""
13from pathlib import Path
14from dataclasses import dataclass
15import webbrowser
16from SCons.Builder import BuilderBase
17from SCons.Action import Action
18from SCons.Script import Builder
19from SCons.Node.FS import File
20from apio.common.apio_console import cout
21from apio.common.apio_styles import SUCCESS
22from apio.common.common_util import SRC_SUFFIXES
23from apio.scons.apio_env import ApioEnv
24from apio.common.proto.apio_pb2 import GraphOutputType
25from apio.scons.plugin_util import (
26 verilog_src_scanner,
27 get_constraint_file,
28 get_define_flags,
29)
32# -- Supported apio graph types.
33SUPPORTED_GRAPH_TYPES = ["svg", "pdf", "png"]
36@dataclass(frozen=True)
37class ArchPluginInfo:
38 """Provides information about the plugin."""
40 constrains_file_ext: str
41 bin_file_suffix: str
42 clk_name_index: int
45class PluginBase:
46 """Base apio arch plugin handler"""
48 def __init__(self, apio_env: ApioEnv):
49 self.apio_env = apio_env
51 # -- Scanner for verilog source files.
52 self.verilog_src_scanner = verilog_src_scanner(apio_env)
54 # -- A placeholder for the constraint file name.
55 self._constrain_file: str = None
57 def plugin_info(self) -> ArchPluginInfo: # pragma: no cover
58 """Return plugin specific parameters."""
59 raise NotImplementedError("Implement in subclass.")
61 def constrain_file(self) -> str:
62 """Finds and returns the constraint file path."""
63 # -- Keep short references.
64 apio_env = self.apio_env
66 # -- On first call, determine and cache.
67 if self._constrain_file is None:
68 self._constrain_file = get_constraint_file(
69 apio_env, self.plugin_info().constrains_file_ext
70 )
71 return self._constrain_file
73 def synth_builder(self) -> BuilderBase: # pragma: no cover
74 """Creates and returns the synth builder."""
75 raise NotImplementedError("Implement in subclass.")
77 def pnr_builder(self) -> BuilderBase: # pragma: no cover
78 """Creates and returns the pnr builder."""
79 raise NotImplementedError("Implement in subclass.")
81 def bitstream_builder(self) -> BuilderBase: # pragma: no cover
82 """Creates and returns the bitstream builder."""
83 raise NotImplementedError("Implement in subclass.")
85 def testbench_compile_builder(self) -> BuilderBase: # pragma: no cover
86 """Creates and returns the testbench compile builder."""
87 raise NotImplementedError("Implement in subclass.")
89 def testbench_run_builder(self) -> BuilderBase:
90 """Creates and returns the testbench run builder."""
92 # -- Sanity checks
93 assert self.apio_env.targeting("sim", "test")
94 assert self.apio_env.params.target.HasField(
95 "sim"
96 ) or self.apio_env.params.target.HasField("test")
98 return Builder(
99 action="vvp $SOURCE -dumpfile=$TARGET",
100 suffix=".vcd",
101 src_suffix=".out",
102 )
104 def yosys_dot_builder(self) -> BuilderBase:
105 """Creates and returns the yosys dot builder. Should be called
106 only when serving the graph command."""
108 # -- Sanity checks
109 assert self.apio_env.targeting("graph")
110 assert self.apio_env.params.target.HasField("graph")
112 # -- Shortcuts.
113 apio_env = self.apio_env
114 params = apio_env.params
115 graph_params = params.target.graph
117 # -- Determine top module value. First priority is to the
118 # -- graph cmd param.
119 top_module = (
120 graph_params.top_module
121 if graph_params.top_module
122 else params.apio_env_params.top_module
123 )
125 return Builder(
126 # See https://tinyurl.com/yosys-sv-graph
127 # For -wireshape see https://github.com/YosysHQ/yosys/pull/4252
128 action=(
129 'yosys -p "read_verilog -sv $SOURCES; show -format dot'
130 ' -colors 1 -wireshape plaintext -prefix {0} {1}" {2} {3}'
131 ).format(
132 apio_env.graph_target,
133 top_module,
134 "" if params.verbosity.all else "-q",
135 get_define_flags(apio_env),
136 ),
137 suffix=".dot",
138 src_suffix=SRC_SUFFIXES,
139 source_scanner=self.verilog_src_scanner,
140 )
142 def graphviz_renderer_builder(self) -> BuilderBase:
143 """Creates and returns the graphviz renderer builder. Should
144 be called only when serving the graph command."""
146 # -- Sanity checks.
147 assert self.apio_env.targeting("graph")
148 assert self.apio_env.params.target.HasField("graph")
150 # -- Shortcuts.
151 apio_env = self.apio_env
152 params = apio_env.params
153 graph_params = params.target.graph
155 # -- Determine the output type string.
156 type_map = {
157 GraphOutputType.PDF: "pdf",
158 GraphOutputType.PNG: "png",
159 GraphOutputType.SVG: "svg",
160 }
161 type_str = type_map[graph_params.output_type]
162 assert type_str, f"Unexpected graph type {graph_params.output_type}"
164 def completion_action(source, target, env): # noqa
165 """Action function that prints a completion message and if
166 requested, open a viewer on the output file.."""
167 _ = source # Unused
168 _ = env # Unused
169 # -- Get the rendered file.
170 target_file: File = target[0]
171 assert isinstance(target_file, File)
172 # -- Print a message
173 cout(f"Generated {str(target_file)}", style=SUCCESS)
174 # -- If requested, convert the file to URI and open it in the
175 # -- default browser.
176 if graph_params.open_viewer: 176 ↛ 177line 176 didn't jump to line 177 because the condition on line 176 was never true
177 cout("Opening default browser")
178 file_path = Path(target_file.get_abspath())
179 file_uri = file_path.resolve().as_uri()
180 default_browser = webbrowser.get()
181 default_browser.open(file_uri)
182 else:
183 cout("User requested no graph viewer")
185 actions = [
186 f"dot -T{type_str} $SOURCES -o $TARGET",
187 Action(completion_action, "completion_action"),
188 ]
190 graphviz_builder = Builder(
191 # Expecting graphviz dot to be installed and in the path.
192 action=actions,
193 suffix=f".{type_str}",
194 src_suffix=".dot",
195 )
197 return graphviz_builder
199 def lint_config_builder(self) -> BuilderBase: # pragma: no cover
200 """Creates and returns the lint config builder."""
201 raise NotImplementedError("Implement in subclass.")
203 def lint_builder(self) -> BuilderBase: # pragma: no cover
204 """Creates and returns the lint builder."""
205 raise NotImplementedError("Implement in subclass.")