Coverage for apio / scons / plugin_ecp5.py: 100%
50 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 02:47 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 02:47 +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 plugin for the ecp5 architecture."""
13# pylint: disable=duplicate-code
15from pathlib import Path
16from SCons.Script import Builder
17from SCons.Builder import BuilderBase
18from apio.common.common_util import SRC_SUFFIXES
19from apio.scons.apio_env import ApioEnv
20from apio.scons.plugin_base import PluginBase, ArchPluginInfo
21from apio.scons.plugin_util import (
22 verilator_lint_action,
23 has_testbench_name,
24 announce_testbench_action,
25 source_files_issue_scanner_action,
26 iverilog_action,
27 basename,
28 make_verilator_config_builder,
29 get_define_flags,
30)
33class PluginEcp5(PluginBase):
34 """Apio scons plugin for the ice40 architecture."""
36 def __init__(self, apio_env: ApioEnv):
37 # -- Call parent constructor.
38 super().__init__(apio_env)
40 # -- Cache values.
41 trellis_path = Path(apio_env.params.environment.trellis_path)
42 yosys_path = Path(apio_env.params.environment.yosys_path)
44 self.database_path = trellis_path / "database"
45 self.yosys_lib_dir = yosys_path / "ecp5"
46 self.sim_lib_files = [yosys_path / "ecp5" / "cells_sim.v"]
47 # -- For black-box cells such as EHXPLLL PLL.
48 self.lint_lib_files = [yosys_path / "ecp5" / "cells_bb.v"]
50 def plugin_info(self) -> ArchPluginInfo:
51 """Return plugin specific parameters."""
52 return ArchPluginInfo(
53 constrains_file_ext=".lpf",
54 bin_file_suffix=".bit",
55 clk_name_index=2,
56 )
58 # @overrides
59 def synth_builder(self) -> BuilderBase:
60 """Creates and returns the synth builder."""
62 # -- Keep short references.
63 apio_env = self.apio_env
64 params = apio_env.params
66 # -- The yosys synth builder.
67 return Builder(
68 action=(
69 'yosys -p "synth_ecp5 -top {0} {1} -json $TARGET" '
70 "{2} -DSYNTHESIZE {3} $SOURCES"
71 ).format(
72 params.apio_env_params.top_module,
73 " ".join(params.apio_env_params.yosys_synth_extra_options),
74 "" if params.verbosity.all or params.verbosity.synth else "-q",
75 get_define_flags(apio_env),
76 ),
77 suffix=".json",
78 src_suffix=SRC_SUFFIXES,
79 source_scanner=self.verilog_src_scanner,
80 )
82 # @overrides
83 def pnr_builder(self) -> BuilderBase:
84 """Creates and returns the pnr builder."""
86 # -- Keep short references.
87 apio_env = self.apio_env
88 params = apio_env.params
90 # -- We use an emmiter to add to the builder a second output file.
91 def emitter(target, source, env):
92 _ = env # Unused
93 target.append(apio_env.target + ".pnr")
94 return target, source
96 # -- Create the builder.
97 return Builder(
98 action=(
99 "nextpnr-ecp5 --{0} --package {1} --speed {2} "
100 "--json $SOURCE --textcfg $TARGET "
101 "--report {3} --lpf {4} {5} {6} --timing-allow-fail --force"
102 ).format(
103 params.fpga_info.ecp5.type,
104 params.fpga_info.ecp5.pack,
105 params.fpga_info.ecp5.speed,
106 apio_env.target + ".pnr",
107 self.constrain_file(),
108 "" if params.verbosity.all or params.verbosity.pnr else "-q",
109 " ".join(params.apio_env_params.nextpnr_extra_options),
110 ),
111 suffix=".config",
112 src_suffix=".json",
113 emitter=emitter,
114 )
116 # @overrides
117 def bitstream_builder(self) -> BuilderBase:
118 """Creates and returns the bitstream builder."""
120 return Builder(
121 action="ecppack --compress --db {0} $SOURCE $TARGET".format(
122 self.database_path,
123 ),
124 suffix=".bit",
125 src_suffix=".config",
126 )
128 # @overrides
129 def testbench_compile_builder(self) -> BuilderBase:
130 """Creates and returns the testbench compile builder."""
131 # -- Keep short references.
132 apio_env = self.apio_env
133 params = apio_env.params
135 # -- Sanity checks
136 assert apio_env.targeting_one_of("sim", "test")
137 assert params.target.HasField("sim") or params.target.HasField("test")
139 # -- We use a generator because we need a different action
140 # -- string for sim and test.
141 def action_generator(target, source, env, for_signature):
142 _ = (source, env, for_signature) # Unused
143 # Extract testbench name from target file name.
144 testbench_file = str(target[0])
145 assert has_testbench_name(testbench_file), testbench_file
146 testbench_name = basename(testbench_file)
148 # Construct the actions list.
149 action = [
150 # -- Print a testbench title.
151 announce_testbench_action(),
152 # -- Scan source files for issues.
153 source_files_issue_scanner_action(),
154 # -- Perform the actual test or sim compilation.
155 iverilog_action(
156 apio_env,
157 verbose=params.verbosity.all,
158 vcd_output_name=testbench_name,
159 is_interactive=apio_env.targeting_one_of("sim"),
160 # -- Per https://github.com/YosysHQ/yosys/issues/5668
161 extra_params=["-DNO_INCLUDES"],
162 lib_dirs=[self.yosys_lib_dir],
163 lib_files=self.sim_lib_files,
164 ),
165 ]
166 return action
168 # -- The testbench compiler builder.
169 return Builder(
170 # -- Dynamic action string generator.
171 generator=action_generator,
172 suffix=".out",
173 src_suffix=SRC_SUFFIXES,
174 source_scanner=self.verilog_src_scanner,
175 )
177 # @overrides
178 def lint_config_builder(self) -> BuilderBase:
179 """Creates and returns the lint config builder."""
181 # -- Sanity checks
182 assert self.apio_env.targeting_one_of("lint")
184 # -- Make the builder.
185 return make_verilator_config_builder(
186 self.yosys_lib_dir,
187 rules_to_supress=[],
188 )
190 # @overrides
191 def lint_builder(self) -> BuilderBase:
192 """Creates and returns the lint builder."""
194 return Builder(
195 action=verilator_lint_action(
196 self.apio_env,
197 lib_dirs=[self.yosys_lib_dir],
198 lib_files=self.lint_lib_files,
199 ),
200 src_suffix=SRC_SUFFIXES,
201 source_scanner=self.verilog_src_scanner,
202 )