Coverage for tsfpga/yosys/test/test_project.py: 100%
356 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-09 00:40 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-09 00:40 +0000
1# --------------------------------------------------------------------------------------------------
2# Copyright (c) Lukas Vik. All rights reserved.
3#
4# This file is part of the tsfpga project, a project platform for modern FPGA development.
5# https://tsfpga.com
6# https://github.com/tsfpga/tsfpga
7# --------------------------------------------------------------------------------------------------
9import gc
10import sys
11import threading
12import time
13from pathlib import Path
14from unittest.mock import patch
16import pytest
18from tsfpga.module import get_modules
19from tsfpga.system_utils import create_file
20from tsfpga.vivado.build_result_checker import EqualTo, Ffs, TotalLuts
21from tsfpga.vivado.generics import BitVectorGenericValue, StringGenericValue
22from tsfpga.yosys.project import (
23 YosysIntelNetlistBuild,
24 YosysMicrochipNetlistBuild,
25 YosysNetlistBuild,
26 YosysXilinxNetlistBuild,
27 _get_ghdl_generic_value,
28 _get_verilog_parameter_value,
29 _suppress_stdout,
30)
32# ruff: noqa: ARG002
35def _create_module(modules_path, module_name="apa", top_name=None):
36 top_name = f"{module_name}_top" if top_name is None else top_name
37 create_file(
38 modules_path / module_name / "src" / f"{top_name}.vhd",
39 f"""\
40library ieee;
41use ieee.std_logic_1164.all;
43entity {top_name} is
44 port (
45 clk : in std_logic
46 );
47end entity;
49architecture a of {top_name} is
50begin
51end architecture;
52""",
53 )
54 return get_modules(modules_folder=modules_path)
57def test_casting_to_string():
58 project = YosysNetlistBuild(name="my_project", modules=[])
59 assert (
60 str(project)
61 == """\
62my_project
63Type: YosysNetlistBuild
64Top level: my_project_top
65Generics: -
66"""
67 )
69 project = YosysNetlistBuild(
70 name="my_project",
71 modules=[],
72 top="apa",
73 generics={"hest": True, "zebra": 3, "foo": StringGenericValue("test")},
74 )
75 assert (
76 str(project)
77 == """\
78my_project
79Type: YosysNetlistBuild
80Top level: apa
81Generics: hest=True, zebra=3, foo=test
82"""
83 )
85 project = YosysNetlistBuild(name="my_project", modules=[], apa=123, hest=456)
86 assert (
87 str(project)
88 == """\
89my_project
90Type: YosysNetlistBuild
91Top level: my_project_top
92Generics: -
93Arguments: apa=123, hest=456
94"""
95 )
98def test_modules_list_should_be_copied():
99 modules = [1]
100 proj = YosysNetlistBuild(name="name", modules=modules)
102 modules.append(2)
103 assert len(proj.modules) == 1
106def test_static_generics_dictionary_should_be_copied():
107 generics = {"apa": 3}
108 proj = YosysNetlistBuild(name="name", modules=[], generics=generics)
110 generics["apa"] = False
111 assert proj.static_generics["apa"] == 3
114def test_build_result_checkers_list_should_be_copied():
115 checkers = [TotalLuts(EqualTo(3))]
116 proj = YosysNetlistBuild(name="name", modules=[], build_result_checkers=checkers)
118 checkers.append(Ffs(EqualTo(4)))
119 assert len(proj.build_result_checkers) == 1
122def test_vhdl_entities_list_should_be_copied():
123 vhdl_entities = ["apa"]
124 proj = YosysNetlistBuild(name="name", modules=[], vhdl_entities=vhdl_entities)
126 vhdl_entities.append("hest")
127 assert len(proj.vhdl_entities) == 1
130def test_top_name():
131 assert YosysNetlistBuild(name="apa", modules=[]).top == "apa_top"
132 assert YosysNetlistBuild(name="apa", modules=[], top="hest").top == "hest"
135def test_project_file_name_is_same_as_project_name():
136 project = YosysNetlistBuild(name="apa", modules=[])
137 assert project.project_file(Path("/hest")) == Path("/hest/apa.ys")
140def test_xilinx_netlist_build_synth_command():
141 project = YosysXilinxNetlistBuild(name="apa", modules=[])
142 assert project._get_synth_command() == "synth_xilinx -top apa_top -flatten" # noqa: SLF001
144 project = YosysXilinxNetlistBuild(name="apa", modules=[], family="xc7")
145 assert (
146 project._get_synth_command() # noqa: SLF001
147 == "synth_xilinx -family xc7 -top apa_top -flatten"
148 )
151def test_intel_netlist_build_synth_command():
152 project = YosysIntelNetlistBuild(name="apa", modules=[])
153 # 'synth_intel' does not accept a '-flatten' flag, unlike 'synth_xilinx'.
154 assert project._get_synth_command() == "synth_intel -top apa_top" # noqa: SLF001
156 project = YosysIntelNetlistBuild(name="apa", modules=[], family="cycloneiv")
157 assert (
158 project._get_synth_command() # noqa: SLF001
159 == "synth_intel -family cycloneiv -top apa_top"
160 )
163def test_microchip_netlist_build_synth_command():
164 project = YosysMicrochipNetlistBuild(name="apa", modules=[])
165 assert project._get_synth_command() == "synth_microchip -top apa_top" # noqa: SLF001
167 project = YosysMicrochipNetlistBuild(name="apa", modules=[], family="polarfire")
168 assert (
169 project._get_synth_command() # noqa: SLF001
170 == "synth_microchip -family polarfire -top apa_top"
171 )
173 project = YosysMicrochipNetlistBuild(name="apa", modules=[], discard_ffinit=True)
174 assert (
175 project._get_synth_command() # noqa: SLF001
176 == "synth_microchip -discard-ffinit -top apa_top"
177 )
180def test_synth_command_and_arguments_can_be_set_when_subclassing():
181 class YosysIntelAlmNetlistBuild(YosysNetlistBuild):
182 _synth_command = "synth_intel_alm"
183 _synth_arguments = ("-noiopad",)
185 project = YosysIntelAlmNetlistBuild(name="apa", modules=[])
186 assert (
187 project._get_synth_command() # noqa: SLF001
188 == "synth_intel_alm -noiopad -top apa_top -flatten"
189 )
191 # Arguments set by a subclass are kept when the parent class adds its own.
192 class YosysXilinxNoBramNetlistBuild(YosysXilinxNetlistBuild):
193 _synth_arguments = ("-nobram",)
195 project = YosysXilinxNoBramNetlistBuild(name="apa", modules=[], family="xc7")
196 assert (
197 project._get_synth_command() # noqa: SLF001
198 == "synth_xilinx -nobram -family xc7 -top apa_top -flatten"
199 )
202def test_synth_arguments_are_not_shared_between_objects():
203 YosysXilinxNetlistBuild(name="apa", modules=[], family="xc7")
205 # Adding the family argument to the first object must not affect the class, or any
206 # object created after it.
207 assert YosysXilinxNetlistBuild._synth_arguments == () # noqa: SLF001
208 project = YosysXilinxNetlistBuild(name="hest", modules=[])
209 assert project._get_synth_command() == "synth_xilinx -top hest_top -flatten" # noqa: SLF001
212def test_get_ghdl_generic_value():
213 assert _get_ghdl_generic_value(True) == "true"
214 assert _get_ghdl_generic_value(False) == "false"
215 assert _get_ghdl_generic_value(3) == "3"
216 assert _get_ghdl_generic_value(3.5) == "3.5"
217 assert _get_ghdl_generic_value(BitVectorGenericValue("1010")) == "1010"
218 assert _get_ghdl_generic_value(StringGenericValue("hest")) == "hest"
220 with pytest.raises(TypeError):
221 _get_ghdl_generic_value("apa")
224def test_get_read_verilog_command_returns_none_if_no_verilog_files(tmp_path):
225 modules = _create_module(tmp_path)
226 project = YosysNetlistBuild(name="apa", modules=modules)
228 assert project._get_read_verilog_command() is None # noqa: SLF001
231def test_get_read_verilog_command_with_verilog_and_systemverilog_files(tmp_path):
232 src_path = tmp_path / "apa" / "src"
233 create_file(src_path / "counter.v", "module counter (); endmodule\n")
234 create_file(src_path / "adder.sv", "module adder (); endmodule\n")
235 create_file(src_path / "counter_defines.vh", "`define WIDTH 8\n")
237 modules = _create_module(tmp_path)
238 project = YosysNetlistBuild(name="apa", modules=modules)
240 command = project._get_read_verilog_command() # noqa: SLF001
242 assert command is not None
243 assert command.startswith("read_verilog -sv ")
244 # Include directories must NOT be quoted, Yosys would treat the quotes as part of the path.
245 assert f"-I{src_path.resolve().as_posix()} " in command
246 assert '-I"' not in command
247 assert (src_path / "counter.v").resolve().as_posix() in command
248 assert (src_path / "adder.sv").resolve().as_posix() in command
249 # Header files are 'include'd by the source files, not passed as source arguments themselves.
250 assert "counter_defines.vh" not in command
253@pytest.fixture
254def yosys_project_test(tmp_path):
255 class YosysProjectTest:
256 def __init__(self):
257 self.modules_path = tmp_path / "modules"
258 self.project_path = tmp_path / "projects" / "apa" / "project"
259 self.output_path = tmp_path / "projects" / "apa"
260 self.build_time_generics = {"enable": True}
262 self.mocked_run_ghdl = None
263 self.mocked_run_yosys = None
265 def create(self, project, **other_arguments):
266 with patch("tsfpga.yosys.project.run_ghdl", autospec=True) as self.mocked_run_ghdl:
267 self.mocked_run_ghdl.return_value = True
268 return project.create(project_path=self.project_path, **other_arguments)
270 def build(self, project, ghdl_side_effect=None, **other_arguments):
271 # Note that 'build' analyzes the sources as well, so that any changes made by the
272 # pre-build hooks are picked up. Hence GHDL is mocked here too.
273 with (
274 patch("tsfpga.yosys.project.run_ghdl", autospec=True) as self.mocked_run_ghdl,
275 patch("tsfpga.yosys.project.run_yosys", autospec=True) as self.mocked_run_yosys,
276 patch(
277 "tsfpga.yosys.project.YosysNetlistBuild._get_size", autospec=True
278 ) as mocked_get_size,
279 ):
280 if ghdl_side_effect is None:
281 self.mocked_run_ghdl.return_value = True
282 else:
283 self.mocked_run_ghdl.side_effect = ghdl_side_effect
284 self.mocked_run_yosys.return_value = True
285 mocked_get_size.return_value = {"Total LUTs": 0, "FFs": 0}
287 return project.build(
288 project_path=self.project_path,
289 output_path=self.output_path,
290 generics=self.build_time_generics,
291 **other_arguments,
292 )
294 return YosysProjectTest()
297def test_default_pre_create_hook_should_pass(yosys_project_test):
298 class CustomYosysNetlistBuild(YosysNetlistBuild):
299 pass
301 modules = _create_module(yosys_project_test.modules_path)
302 project = CustomYosysNetlistBuild(name="apa", modules=modules)
303 assert yosys_project_test.create(project)
304 yosys_project_test.mocked_run_ghdl.assert_called_once()
307def test_create_with_vhdl_extension_top_level_file(yosys_project_test):
308 # Regression test: the top-level file lookup used to hardcode the '.vhd' file ending,
309 # which meant it failed to find a top-level file using e.g. the '.vhdl' file ending.
310 create_file(
311 yosys_project_test.modules_path / "apa" / "src" / "apa_top.vhdl",
312 """\
313library ieee;
314use ieee.std_logic_1164.all;
316entity apa_top is
317 port (
318 clk : in std_logic
319 );
320end entity;
322architecture a of apa_top is
323begin
324end architecture;
325""",
326 )
327 modules = get_modules(modules_folder=yosys_project_test.modules_path)
328 project = YosysNetlistBuild(name="apa", modules=modules)
330 assert yosys_project_test.create(project)
331 yosys_project_test.mocked_run_ghdl.assert_called_once()
334def test_project_pre_create_hook_returning_false_should_fail_and_not_call_ghdl(
335 yosys_project_test,
336):
337 class CustomYosysNetlistBuild(YosysNetlistBuild):
338 def pre_create(self, **kwargs):
339 return False
341 modules = _create_module(yosys_project_test.modules_path)
342 project = CustomYosysNetlistBuild(name="apa", modules=modules)
343 assert not yosys_project_test.create(project)
344 yosys_project_test.mocked_run_ghdl.assert_not_called()
347def test_create_should_call_pre_create_with_correct_parameters(yosys_project_test):
348 modules = _create_module(yosys_project_test.modules_path)
349 project = YosysNetlistBuild(name="apa", modules=modules, generics={"apa": 123}, hest=456)
350 with patch("tsfpga.yosys.project.YosysNetlistBuild.pre_create") as mocked_pre_create:
351 yosys_project_test.create(project, zebra=789)
352 mocked_pre_create.assert_called_once_with(
353 project_path=yosys_project_test.project_path,
354 hest=456,
355 zebra=789,
356 )
357 yosys_project_test.mocked_run_ghdl.assert_called_once()
360def test_default_pre_and_post_build_hooks_should_pass(yosys_project_test):
361 modules = _create_module(yosys_project_test.modules_path)
362 project = YosysNetlistBuild(name="apa", modules=modules)
363 yosys_project_test.create(project)
365 build_result = yosys_project_test.build(project)
366 assert build_result.success
367 yosys_project_test.mocked_run_yosys.assert_called_once()
370def test_project_pre_build_hook_returning_false_should_fail_and_not_call_yosys(
371 yosys_project_test,
372):
373 class CustomYosysNetlistBuild(YosysNetlistBuild):
374 def pre_build(self, **kwargs):
375 return False
377 modules = _create_module(yosys_project_test.modules_path)
378 project = CustomYosysNetlistBuild(name="apa", modules=modules)
379 yosys_project_test.create(project)
381 build_result = yosys_project_test.build(project)
382 assert not build_result.success
383 yosys_project_test.mocked_run_yosys.assert_not_called()
386def test_project_post_build_hook_returning_false_should_fail(yosys_project_test):
387 class CustomYosysNetlistBuild(YosysNetlistBuild):
388 def post_build(self, **kwargs):
389 return False
391 modules = _create_module(yosys_project_test.modules_path)
392 project = CustomYosysNetlistBuild(name="apa", modules=modules)
393 yosys_project_test.create(project)
395 build_result = yosys_project_test.build(project)
396 assert not build_result.success
397 yosys_project_test.mocked_run_yosys.assert_called_once()
400def test_module_pre_build_hook_and_create_regs_are_called(yosys_project_test):
401 # Patched on the class, since 'YosysNetlistBuild' deep-copies the module list before
402 # 'pre_build'/'build' is called.
403 with (
404 patch("tsfpga.module.BaseModule.pre_build", autospec=True) as mocked_pre_build,
405 patch(
406 "tsfpga.module.BaseModule.create_register_synthesis_files", autospec=True
407 ) as mocked_create_register_synthesis_files,
408 ):
409 mocked_pre_build.return_value = True
411 modules = _create_module(yosys_project_test.modules_path)
412 project = YosysNetlistBuild(name="apa", modules=modules)
413 yosys_project_test.create(project)
415 build_result = yosys_project_test.build(project)
416 assert build_result.success
418 mocked_pre_build.assert_called_once()
419 # Note: 'BaseModule.get_synthesis_files' also triggers register file generation as a side
420 # effect, and is called a few times while resolving compile order in 'create()'. So this
421 # will be called more than once, which is expected and not a problem since it is idempotent.
422 mocked_create_register_synthesis_files.assert_called()
425def test_build_should_raise_exception_if_project_does_not_exist(yosys_project_test):
426 modules = _create_module(yosys_project_test.modules_path)
427 project = YosysNetlistBuild(name="apa", modules=modules)
429 with pytest.raises(ValueError, match='Project "apa" does not exist'):
430 yosys_project_test.build(project)
433def test_build_result_checkers_are_run_and_can_fail_build(yosys_project_test):
434 modules = _create_module(yosys_project_test.modules_path)
435 project = YosysNetlistBuild(
436 name="apa", modules=modules, build_result_checkers=[TotalLuts(EqualTo(0))]
437 )
438 yosys_project_test.create(project)
440 with (
441 patch("tsfpga.yosys.project.YosysNetlistBuild._get_size", autospec=True) as mocked_get_size,
442 patch("tsfpga.yosys.project.run_ghdl", autospec=True) as mocked_run_ghdl,
443 patch("tsfpga.yosys.project.run_yosys", autospec=True) as mocked_run_yosys,
444 ):
445 mocked_get_size.return_value = {"Total LUTs": 3}
446 mocked_run_ghdl.return_value = True
447 mocked_run_yosys.return_value = True
448 (yosys_project_test.project_path / "ghdl").mkdir(parents=True, exist_ok=True)
449 build_result = project.build(
450 project_path=yosys_project_test.project_path,
451 output_path=yosys_project_test.output_path,
452 )
454 assert not build_result.success
455 assert build_result.synthesis_size == {"Total LUTs": 3}
458def test_build_time_generics_are_copied(yosys_project_test):
459 modules = _create_module(yosys_project_test.modules_path)
460 project = YosysNetlistBuild(name="apa", modules=modules)
462 generics = {"apa": 3}
463 yosys_project_test.build_time_generics = generics
464 yosys_project_test.create(project)
465 yosys_project_test.build(project)
467 generics["apa"] = False
468 _, kwargs = yosys_project_test.mocked_run_yosys.call_args
469 # Generics are baked into the script file content, not passed as an explicit argument.
470 script_content = kwargs["script_file"].read_text()
471 assert "-gapa=3" in script_content
474def test_different_generic_combinations(yosys_project_test):
475 modules = _create_module(yosys_project_test.modules_path)
477 # Only build time generics
478 project = YosysNetlistBuild(name="apa", modules=modules)
479 yosys_project_test.build_time_generics = {"runtime": 1}
480 yosys_project_test.create(project)
481 yosys_project_test.build(project)
482 _, kwargs = yosys_project_test.mocked_run_yosys.call_args
483 assert "-gruntime=1" in kwargs["script_file"].read_text()
485 # Static and build time generics
486 project = YosysNetlistBuild(name="apa", modules=modules, generics={"static": 2})
487 yosys_project_test.build_time_generics = {"runtime": 1}
488 yosys_project_test.create(project)
489 yosys_project_test.build(project)
490 _, kwargs = yosys_project_test.mocked_run_yosys.call_args
491 script_content = kwargs["script_file"].read_text()
492 assert "-gruntime=1" in script_content
493 assert "-gstatic=2" in script_content
495 # Same key in both. Should prefer build time.
496 project = YosysNetlistBuild(name="apa", modules=modules, generics={"common": 2})
497 yosys_project_test.build_time_generics = {"common": 5}
498 yosys_project_test.create(project)
499 yosys_project_test.build(project)
500 _, kwargs = yosys_project_test.mocked_run_yosys.call_args
501 assert "-gcommon=5" in kwargs["script_file"].read_text()
504def test_non_vhdl_top_with_vhdl_entities(yosys_project_test):
505 modules = _create_module(yosys_project_test.modules_path, module_name="apa", top_name="sub_a")
506 modules = _create_module(yosys_project_test.modules_path, module_name="apa", top_name="sub_b")
508 project = YosysNetlistBuild(
509 name="apa", modules=modules, top="verilog_top", vhdl_entities=["sub_a", "sub_b"]
510 )
512 yosys_project_test.build_time_generics = {}
513 yosys_project_test.create(project)
514 # One call per analyzed VHDL source file ('sub_a.vhd' and 'sub_b.vhd').
515 assert yosys_project_test.mocked_run_ghdl.call_count == 2
517 build_result = yosys_project_test.build(project)
518 assert build_result.success
520 _, kwargs = yosys_project_test.mocked_run_yosys.call_args
521 script_content = kwargs["script_file"].read_text()
523 assert script_content.count("ghdl --std=08") == 2
524 assert script_content.splitlines()[0].endswith(" sub_a")
525 assert script_content.splitlines()[1].endswith(" sub_b")
526 assert "-top verilog_top" in script_content
529def test_non_vhdl_top_with_vhdl_entity_not_found_should_raise_exception(yosys_project_test):
530 modules = _create_module(yosys_project_test.modules_path)
531 project = YosysNetlistBuild(
532 name="apa", modules=modules, top="verilog_top", vhdl_entities=["non_existent"]
533 )
535 yosys_project_test.build_time_generics = {}
536 yosys_project_test.create(project)
538 with pytest.raises(
539 ValueError, match='Could not find a VHDL source file for entity "non_existent"'
540 ):
541 yosys_project_test.build(project)
544def test_non_vhdl_top_with_generics_sets_verilog_parameters(yosys_project_test):
545 modules = _create_module(yosys_project_test.modules_path)
546 project = YosysNetlistBuild(
547 name="apa", modules=modules, top="verilog_top", generics={"width": 16}
548 )
550 yosys_project_test.create(project)
552 yosys_project_test.build_time_generics = {"enable": True}
553 build_result = yosys_project_test.build(project)
554 assert build_result.success
556 _, kwargs = yosys_project_test.mocked_run_yosys.call_args
557 script_content = kwargs["script_file"].read_text()
559 # Static and build-time generics both end up as Verilog parameters on the top level.
560 assert "hierarchy -top verilog_top " in script_content
561 assert "-chparam width 16" in script_content
562 assert "-chparam enable 1" in script_content
563 # The parameters must be set before synthesis.
564 assert script_content.index("hierarchy -top") < script_content.index(
565 "-top verilog_top -flatten"
566 )
569def test_vhdl_top_with_generics_does_not_set_verilog_parameters(yosys_project_test):
570 modules = _create_module(yosys_project_test.modules_path)
571 project = YosysNetlistBuild(name="apa", modules=modules, generics={"width": 16})
573 yosys_project_test.create(project)
574 yosys_project_test.build(project)
576 _, kwargs = yosys_project_test.mocked_run_yosys.call_args
577 script_content = kwargs["script_file"].read_text()
579 # Generics go to GHDL for a VHDL top level, not to a Yosys 'hierarchy' command.
580 assert "hierarchy" not in script_content
581 assert "-gwidth=16" in script_content
584def test_non_vhdl_top_with_generics_and_vhdl_entities(yosys_project_test):
585 """
586 Project generics target the project top level. When that is a Verilog module they are applied
587 as parameters there, and must not be passed to the VHDL entities listed in 'vhdl_entities',
588 which generally do not declare them.
589 """
590 _create_module(yosys_project_test.modules_path, module_name="apa", top_name="sub_a")
591 modules = _create_module(yosys_project_test.modules_path, module_name="apa", top_name="sub_b")
593 project = YosysNetlistBuild(
594 name="apa",
595 modules=modules,
596 top="verilog_top",
597 vhdl_entities=["sub_a", "sub_b"],
598 generics={"width": 16},
599 )
601 yosys_project_test.create(project)
603 yosys_project_test.build_time_generics = {}
604 assert yosys_project_test.build(project).success
606 _, kwargs = yosys_project_test.mocked_run_yosys.call_args
607 script_content = kwargs["script_file"].read_text()
609 assert "-chparam width 16" in script_content
610 # The GHDL commands for the listed entities must carry no generic arguments.
611 assert "-gwidth" not in script_content
614def test_pre_build_changes_to_source_are_analyzed(yosys_project_test):
615 """
616 A 'pre_build' hook may rewrite synthesis sources based on the build-time generics, which is
617 an explicitly supported use case. Those changes must be analyzed before synthesis, not left
618 behind in the work library that 'create' produced.
619 """
620 modules_path = yosys_project_test.modules_path
621 modules = _create_module(modules_path)
623 create_file(
624 modules_path / "apa" / "module_apa.py",
625 """\
626from tsfpga.module import BaseModule
629class Module(BaseModule):
630 def pre_build(self, **kwargs):
631 width = kwargs["generics"]["width"]
632 (self.path / "src" / "apa_top.vhd").write_text(f"-- width is {width}\\n")
633 return True
634""",
635 )
636 modules = get_modules(modules_folder=modules_path)
638 project = YosysNetlistBuild(name="apa", modules=modules, top="apa_top")
639 yosys_project_test.create(project)
641 analyzed_contents = []
643 def record_analyzed(ghdl_path, arguments, cwd): # noqa: ARG001
644 analyzed_contents.append(Path(arguments[-1]).read_text())
645 return True
647 for width in [8, 16]:
648 analyzed_contents.clear()
649 yosys_project_test.build_time_generics = {"width": width}
650 yosys_project_test.build(project, ghdl_side_effect=record_analyzed)
652 # The file that the hook just rewrote is what got analyzed.
653 assert any(f"width is {width}" in contents for contents in analyzed_contents), (
654 f"Stale source analyzed for width={width}: {analyzed_contents}"
655 )
658def test_suppress_stdout_is_serialized():
659 """
660 Netlist builds are created from VUnit test-runner worker threads. Two threads inside the
661 global 'sys.stdout' swap at the same time interleave their save/restore, and the last one
662 out installs an already-closed file as 'sys.stdout'. So it must be mutually exclusive.
663 """
664 original_stdout = sys.stdout
665 num_inside = 0
666 max_num_inside = 0
667 counter_lock = threading.Lock()
669 def worker():
670 nonlocal num_inside, max_num_inside
671 for _ in range(20):
672 with _suppress_stdout():
673 with counter_lock:
674 num_inside += 1
675 max_num_inside = max(max_num_inside, num_inside)
677 # Widen the window, so that a missing lock is detected reliably.
678 time.sleep(0.001)
680 with counter_lock:
681 num_inside -= 1
683 threads = [threading.Thread(target=worker) for _ in range(4)]
684 for thread in threads:
685 thread.start()
686 for thread in threads:
687 thread.join()
689 assert max_num_inside == 1, "Global 'sys.stdout' swap was not serialized"
690 assert sys.stdout is original_stdout
691 assert not sys.stdout.closed
694def test_get_verilog_parameter_value():
695 assert _get_verilog_parameter_value(True) == "1"
696 assert _get_verilog_parameter_value(False) == "0"
697 assert _get_verilog_parameter_value(3) == "3"
698 assert _get_verilog_parameter_value(3.5) == "3.5"
699 assert _get_verilog_parameter_value(BitVectorGenericValue("1010")) == "4'b1010"
701 # Yosys can not set string parameters.
702 with pytest.raises(TypeError, match="can not set string parameters"):
703 _get_verilog_parameter_value(StringGenericValue("hest"))
706def test_vunit_output_directory_is_removed_with_the_project(yosys_project_test):
707 """
708 The VUnit project is only used to resolve compile order, and its output is throwaway. It
709 used to be created with 'mkdtemp', which never removes anything, so every netlist build
710 left a directory behind in the system temp directory forever.
711 """
712 project = YosysNetlistBuild(name="apa", modules=_create_module(yosys_project_test.modules_path))
714 project._get_vunit_project() # noqa: SLF001
715 output_directory = Path(project._vunit_output_dir.name) # noqa: SLF001
716 assert output_directory.exists()
718 # Dropping the last reference must take the directory with it. 'gc.collect()' rather than
719 # relying on refcounting alone, since the VUnit object graph can contain cycles.
720 del project
721 gc.collect()
723 assert not output_directory.exists()