Coverage for apio/scons/plugin_ecp5.py: 100%
50 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 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.yosys_lib_file = yosys_path / "ecp5" / "cells_sim.v"
47 # -- For black-box cells such as EHXPLLL PLL.
48 self.yosys_bb_lib_file = 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" {2} {3} '
70 "$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("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(source, target, 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("sim"),
160 lib_dirs=[self.yosys_lib_dir],
161 lib_files=[self.yosys_lib_file],
162 ),
163 ]
164 return action
166 # -- The testbench compiler builder.
167 return Builder(
168 # -- Dynamic action string generator.
169 generator=action_generator,
170 suffix=".out",
171 src_suffix=SRC_SUFFIXES,
172 source_scanner=self.verilog_src_scanner,
173 )
175 # @overrides
176 def lint_config_builder(self) -> BuilderBase:
177 """Creates and returns the lint config builder."""
179 # -- Sanity checks
180 assert self.apio_env.targeting("lint")
182 # -- Make the builder.
183 return make_verilator_config_builder(self.yosys_lib_dir)
185 # @overrides
186 def lint_builder(self) -> BuilderBase:
187 """Creates and returns the lint builder."""
189 return Builder(
190 action=verilator_lint_action(
191 self.apio_env,
192 lib_dirs=[self.yosys_lib_dir],
193 lib_files=[self.yosys_lib_file, self.yosys_bb_lib_file],
194 ),
195 src_suffix=SRC_SUFFIXES,
196 source_scanner=self.verilog_src_scanner,
197 )