Coverage for apio / scons / plugin_base.py: 91%
65 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-24 01:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-24 01:53 +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
15from typing import List
16import webbrowser
17from SCons.Builder import BuilderBase
18from SCons.Action import Action
19from SCons.Script import Builder
20from SCons.Node.FS import File
21from SCons.Script.SConscript import SConsEnvironment
22from SCons.Node.Alias import Alias
23from apio.common.apio_console import cout
24from apio.common.apio_styles import SUCCESS
25from apio.common.common_util import SRC_SUFFIXES
26from apio.scons.apio_env import ApioEnv
27from apio.common.proto.apio_pb2 import GraphOutputType
28from apio.scons.plugin_util import (
29 verilog_src_scanner,
30 get_constraint_file,
31 get_define_flags,
32)
35# -- Supported apio graph types.
36SUPPORTED_GRAPH_TYPES = ["svg", "pdf", "png"]
39@dataclass(frozen=True)
40class ArchPluginInfo:
41 """Provides information about the plugin."""
43 constrains_file_ext: str
44 bin_file_suffix: str
45 clk_name_index: int
48class PluginBase:
49 """Base apio arch plugin handler"""
51 def __init__(self, apio_env: ApioEnv):
52 self.apio_env = apio_env
54 # -- Scanner for verilog source files.
55 self.verilog_src_scanner = verilog_src_scanner(apio_env)
57 # -- A placeholder for the constraint file name.
58 self._constrain_file: str = None
60 def plugin_info(self) -> ArchPluginInfo: # pragma: no cover
61 """Return plugin specific parameters."""
62 raise NotImplementedError("Implement in subclass.")
64 def constrain_file(self) -> str:
65 """Finds and returns the constraint file path."""
66 # -- Keep short references.
67 apio_env = self.apio_env
69 # -- On first call, determine and cache.
70 if self._constrain_file is None:
71 self._constrain_file = get_constraint_file(
72 apio_env, self.plugin_info().constrains_file_ext
73 )
74 return self._constrain_file
76 def synth_builder(self) -> BuilderBase: # pragma: no cover
77 """Creates and returns the synth builder."""
78 raise NotImplementedError("Implement in subclass.")
80 def pnr_builder(self) -> BuilderBase: # pragma: no cover
81 """Creates and returns the pnr builder."""
82 raise NotImplementedError("Implement in subclass.")
84 def bitstream_builder(self) -> BuilderBase: # pragma: no cover
85 """Creates and returns the bitstream builder."""
86 raise NotImplementedError("Implement in subclass.")
88 def testbench_compile_builder(self) -> BuilderBase: # pragma: no cover
89 """Creates and returns the testbench compile builder."""
90 raise NotImplementedError("Implement in subclass.")
92 def testbench_run_builder(self) -> BuilderBase:
93 """Creates and returns the testbench run builder."""
95 # -- Sanity checks
96 assert self.apio_env.targeting("sim", "test")
97 assert self.apio_env.params.target.HasField(
98 "sim"
99 ) or self.apio_env.params.target.HasField("test")
101 return Builder(
102 action="vvp $SOURCE -dumpfile=$TARGET",
103 suffix=".vcd",
104 src_suffix=".out",
105 )
107 def yosys_dot_builder(self) -> BuilderBase:
108 """Creates and returns the yosys dot builder. Should be called
109 only when serving the graph command."""
111 # -- Sanity checks
112 assert self.apio_env.targeting("graph")
113 assert self.apio_env.params.target.HasField("graph")
115 # -- Shortcuts.
116 apio_env = self.apio_env
117 params = apio_env.params
118 graph_params = params.target.graph
120 # -- Determine top module value. First priority is to the
121 # -- graph cmd param.
122 top_module = (
123 graph_params.top_module
124 if graph_params.top_module
125 else params.apio_env_params.top_module
126 )
128 return Builder(
129 # See https://tinyurl.com/yosys-sv-graph
130 # For -wireshape see https://github.com/YosysHQ/yosys/pull/4252
131 action=(
132 'yosys -p "read_verilog -sv $SOURCES; show -format dot'
133 ' -colors 1 -wireshape plaintext -prefix {0} {1}" {2} {3}'
134 ).format(
135 apio_env.graph_target,
136 top_module,
137 "" if params.verbosity.all else "-q",
138 get_define_flags(apio_env),
139 ),
140 suffix=".dot",
141 src_suffix=SRC_SUFFIXES,
142 source_scanner=self.verilog_src_scanner,
143 )
145 def graphviz_renderer_builder(self) -> BuilderBase:
146 """Creates and returns the graphviz renderer builder. Should
147 be called only when serving the graph command."""
149 # -- Sanity checks.
150 assert self.apio_env.targeting("graph")
151 assert self.apio_env.params.target.HasField("graph")
153 # -- Shortcuts.
154 apio_env = self.apio_env
155 params = apio_env.params
156 graph_params = params.target.graph
158 # -- Determine the output type string.
159 type_map = {
160 GraphOutputType.PDF: "pdf",
161 GraphOutputType.PNG: "png",
162 GraphOutputType.SVG: "svg",
163 }
164 type_str = type_map[graph_params.output_type]
165 assert type_str, f"Unexpected graph type {graph_params.output_type}"
167 def completion_action(
168 target: List[Alias],
169 source: List[File],
170 env: SConsEnvironment,
171 ): # noqa
172 """Action function that prints a completion message and if
173 requested, open a viewer on the output file.."""
174 _ = (source, env) # Unused
175 # -- Get the rendered file.
176 target_file: File = target[0]
177 assert isinstance(target_file, File)
178 # -- Print a message
179 cout(f"Generated {str(target_file)}", style=SUCCESS)
180 # -- If requested, convert the file to URI and open it in the
181 # -- default browser.
182 if graph_params.open_viewer: 182 ↛ 183line 182 didn't jump to line 183 because the condition on line 182 was never true
183 cout("Opening default browser")
184 file_path = Path(target_file.get_abspath())
185 file_uri = file_path.resolve().as_uri()
186 default_browser = webbrowser.get()
187 default_browser.open(file_uri)
188 else:
189 cout("User requested no graph viewer")
191 actions = [
192 f"dot -T{type_str} $SOURCES -o $TARGET",
193 Action(completion_action, "completion_action"),
194 ]
196 graphviz_builder = Builder(
197 # Expecting graphviz dot to be installed and in the path.
198 action=actions,
199 suffix=f".{type_str}",
200 src_suffix=".dot",
201 )
203 return graphviz_builder
205 def lint_config_builder(self) -> BuilderBase: # pragma: no cover
206 """Creates and returns the lint config builder."""
207 raise NotImplementedError("Implement in subclass.")
209 def lint_builder(self) -> BuilderBase: # pragma: no cover
210 """Creates and returns the lint builder."""
211 raise NotImplementedError("Implement in subclass.")