Coverage for apio/scons/plugin_ecp5.py: 100%
50 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-09 01:55 +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, CompositeBuilder
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 lint, the simulation models PLUS the black-box cells that
48 # -- have no model in cells_sim.v (EHXPLLL, DCUA, ...). Passing only
49 # -- cells_bb.v (as before) made verilator fail with MODMISSING on
50 # -- designs that instantiate simulation-modeled cells (IO cells,
51 # -- DP16KD, ...). The two files declare disjoint modules.
52 self.lint_lib_files = self.sim_lib_files + [
53 yosys_path / "ecp5" / "cells_bb.v"
54 ]
56 def plugin_info(self) -> ArchPluginInfo:
57 """Return plugin specific parameters."""
58 return ArchPluginInfo(
59 constrains_file_suffix=".lpf",
60 pnr_file_suffix=".config",
61 bitstream_file_suffix=".bit",
62 )
64 # @overrides
65 def synth_builder(self) -> BuilderBase | CompositeBuilder:
66 """Creates and returns the synth builder."""
68 # -- Keep short references.
69 apio_env = self.apio_env
70 params = apio_env.params
72 # -- The yosys synth builder.
73 return Builder(
74 action=(
75 'yosys -p "synth_ecp5 -top {0} -json $TARGET {1}" '
76 "{2} -DSYNTHESIZE {3} $SOURCES"
77 ).format(
78 params.apio_env_params.top_module,
79 " ".join(params.apio_env_params.yosys_extra_options),
80 "" if params.verbosity.all or params.verbosity.synth else "-q",
81 get_define_flags(apio_env),
82 ),
83 suffix=".json",
84 src_suffix=SRC_SUFFIXES,
85 source_scanner=self.verilog_src_scanner,
86 )
88 # @overrides
89 def pnr_builder(self) -> BuilderBase | CompositeBuilder:
90 """Creates and returns the pnr builder."""
92 # -- Keep short references.
93 apio_env = self.apio_env
94 params = apio_env.params
96 # -- We use an emmiter to add to the builder a second output file.
97 def emitter(target, source, env):
98 _ = env # Unused
99 target.append(apio_env.target + ".pnr")
100 return target, source
102 # -- Create the builder.
103 return Builder(
104 action=(
105 "nextpnr-ecp5 --{0} --package {1} --speed {2} "
106 "--json $SOURCE --textcfg $TARGET "
107 "--report {3} --lpf {4} --timing-allow-fail --force "
108 "{5} {6} {7}"
109 ).format(
110 params.fpga_info.ecp5_params.type,
111 params.fpga_info.ecp5_params.package,
112 params.fpga_info.ecp5_params.speed,
113 apio_env.target + ".pnr",
114 self.constrain_file(),
115 "" if params.verbosity.all or params.verbosity.pnr else "-q",
116 "--gui" if params.nextpnr_gui else "",
117 " ".join(params.apio_env_params.nextpnr_extra_options),
118 ),
119 suffix=".config",
120 src_suffix=".json",
121 emitter=emitter,
122 )
124 # @overrides
125 def bitstream_builder(self) -> BuilderBase | CompositeBuilder:
126 """Creates and returns the bitstream builder."""
128 return Builder(
129 action="ecppack --compress --db {0} $SOURCE $TARGET".format(
130 self.database_path,
131 ),
132 suffix=".bit",
133 src_suffix=".config",
134 )
136 # @overrides
137 def testbench_compile_builder(self) -> BuilderBase | CompositeBuilder:
138 """Creates and returns the testbench compile builder."""
139 # -- Keep short references.
140 apio_env = self.apio_env
141 params = apio_env.params
143 # -- Sanity checks
144 assert apio_env.targeting_one_of("sim", "test")
145 assert params.target.HasField("sim") or params.target.HasField("test")
147 # -- We use a generator because we need a different action
148 # -- string for sim and test.
149 def action_generator(target, source, env, for_signature):
150 _ = (source, env, for_signature) # Unused
151 # Extract testbench name from target file name.
152 testbench_file = str(target[0])
153 assert has_testbench_name(testbench_file), testbench_file
154 testbench_name = basename(testbench_file)
156 # Construct the actions list.
157 action = [
158 # -- Print a testbench title.
159 announce_testbench_action(),
160 # -- Scan source files for issues.
161 source_files_issue_scanner_action(),
162 # -- Perform the actual test or sim compilation.
163 iverilog_action(
164 apio_env,
165 verbose=params.verbosity.all,
166 vcd_output_name=testbench_name,
167 is_interactive=apio_env.targeting_one_of("sim"),
168 # -- Per https://github.com/YosysHQ/yosys/issues/5668
169 extra_params=["-DNO_INCLUDES"],
170 lib_dirs=[self.yosys_lib_dir],
171 lib_files=self.sim_lib_files,
172 ),
173 ]
174 return action
176 # -- The testbench compiler builder.
177 return Builder(
178 # -- Dynamic action string generator.
179 generator=action_generator,
180 suffix=".out",
181 src_suffix=SRC_SUFFIXES,
182 source_scanner=self.verilog_src_scanner,
183 )
185 # @overrides
186 def lint_config_builder(self) -> BuilderBase:
187 """Creates and returns the lint config builder."""
189 # -- Sanity checks
190 assert self.apio_env.targeting_one_of("lint")
192 # -- Make the builder.
193 return make_verilator_config_builder(
194 self.yosys_lib_dir,
195 rules_to_supress=[
196 # -- cells_sim.v double-includes cells_ff.vh/cells_io.vh
197 # -- (directly and via common_sim.vh); the duplicates are
198 # -- identical and verilator keeps the first definition.
199 "MODDUP",
200 # -- Benign width mismatch in common_sim.vh (BB model).
201 "WIDTHEXPAND",
202 # -- The techmap wrappers in cells_io.vh/cells_ff.vh
203 # -- instantiate TRELLIS_* cells with partial pin lists on
204 # -- purpose; user instances keep full pin checking (the
205 # -- waiver is scoped to the yosys lib dir).
206 "PINMISSING",
207 ],
208 )
210 # @overrides
211 def lint_builder(self) -> BuilderBase | CompositeBuilder:
212 """Creates and returns the lint builder."""
214 return Builder(
215 action=verilator_lint_action(
216 self.apio_env,
217 lib_dirs=[self.yosys_lib_dir],
218 lib_files=self.lint_lib_files,
219 ),
220 src_suffix=SRC_SUFFIXES,
221 source_scanner=self.verilog_src_scanner,
222 )