├── tests ├── includes │ └── inc.sv ├── invalid.v ├── ius_defines.f ├── invalid.vhdl ├── glbl_sink.v ├── Makefile ├── glbl.v ├── dff.verilog.Makefile ├── test_parameters.v ├── dff.sv ├── verilator.include-limits.patch ├── test_parameters.vhdl ├── dff.vhdl ├── dff_wrapper.vhdl ├── plus_args.sv ├── test_long_log.py ├── test_named_lib.py ├── dff_cocotb.py ├── test_dff.py ├── test_simulator_kwarg.py ├── subfolder1 │ └── subfolder2 │ │ └── test_work_dir.py ├── test_multitop.py ├── test_plus_args.py ├── test_cocotb_examples.py ├── test_parallel.py ├── test_cocotb_tests.py ├── test_dff_custom_sim.py ├── test_compile_errors.py └── test_parameters.py ├── cocotb_test ├── __init__.py ├── run.py ├── Makefile.inc ├── compat.py ├── plugin.py ├── cli.py └── simulator.py ├── MANIFEST.in ├── tox.ini ├── LICENSE ├── setup.py ├── .gitignore ├── README.md └── azure-pipelines.yml /tests/includes/inc.sv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/invalid.v: -------------------------------------------------------------------------------- 1 | Invalid Verilog file 2 | -------------------------------------------------------------------------------- /cocotb_test/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.2.6" 2 | -------------------------------------------------------------------------------- /tests/ius_defines.f: -------------------------------------------------------------------------------- 1 | -64 2 | dff.sv 3 | -top dff 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include cocotb_test/Makefile.inc 3 | -------------------------------------------------------------------------------- /tests/invalid.vhdl: -------------------------------------------------------------------------------- 1 | -- Invalid VHDL file 2 | 3 | invalid_statement; 4 | -------------------------------------------------------------------------------- /tests/glbl_sink.v: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ns 2 | 3 | module glbl_sink (); 4 | 5 | wire rst; 6 | 7 | assign rst = glbl.rst; 8 | 9 | endmodule 10 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | 3 | all: test 4 | 5 | test: 6 | $(MAKE) -f dff.verilog.Makefile 7 | 8 | clean: 9 | $(MAKE) -f dff.verilog.Makefile clean 10 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py 3 | 4 | [testenv] 5 | passenv = 6 | SIM 7 | 8 | commands = 9 | pytest -s tests/test_dff.py 10 | 11 | [pytest] 12 | markers = 13 | compile 14 | -------------------------------------------------------------------------------- /tests/glbl.v: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ns 2 | 3 | module glbl (); 4 | 5 | reg rst; 6 | 7 | initial begin 8 | rst = 1; 9 | #10 10 | rst = 0; 11 | end 12 | endmodule 13 | -------------------------------------------------------------------------------- /cocotb_test/run.py: -------------------------------------------------------------------------------- 1 | 2 | import cocotb_test.simulator 3 | 4 | 5 | # For partial back compatibility 6 | def run(simulator=None, **kwargs): 7 | 8 | if simulator: 9 | sim = simulator(**kwargs) 10 | sim.run() 11 | else: 12 | cocotb_test.simulator.run(**kwargs) 13 | -------------------------------------------------------------------------------- /tests/dff.verilog.Makefile: -------------------------------------------------------------------------------- 1 | # 2 | 3 | CWD=$(shell pwd) 4 | 5 | TOPLEVEL_LANG=verilog 6 | VERILOG_SOURCES =$(CWD)/dff.sv 7 | 8 | TOPLEVEL=dff_test 9 | MODULE=dff_cocotb 10 | 11 | ifeq ($(SIM),questa) 12 | SIM_ARGS=-t 1ps 13 | endif 14 | 15 | include $(shell cocotb-test --inc-makefile) 16 | 17 | -------------------------------------------------------------------------------- /tests/test_parameters.v: -------------------------------------------------------------------------------- 1 | 2 | `include "inc.sv" 3 | 4 | module test_parameters #( 5 | parameter WIDTH_IN = 8, 6 | parameter WIDTH_OUT = 8 7 | ) ( 8 | input [WIDTH_IN-1:0] data_in, 9 | output [WIDTH_OUT-1:0] data_out, 10 | output [`DEFINE-1:0] define_out 11 | ); 12 | 13 | endmodule 14 | -------------------------------------------------------------------------------- /tests/dff.sv: -------------------------------------------------------------------------------- 1 | // This file is public domain, it can be freely copied without restrictions. 2 | // SPDX-License-Identifier: CC0-1.0 3 | 4 | `timescale 1us/1us 5 | 6 | module dff_test ( 7 | input logic clk, d, 8 | output logic q 9 | ); 10 | 11 | always @(posedge clk) begin 12 | q <= d; 13 | end 14 | 15 | endmodule 16 | -------------------------------------------------------------------------------- /cocotb_test/Makefile.inc: -------------------------------------------------------------------------------- 1 | 2 | all: test 3 | 4 | export VERILOG_SOURCES 5 | export VHDL_SOURCES 6 | export TOPLEVEL_LANG 7 | export TOPLEVEL 8 | export MODULE 9 | export SIM_ARGS 10 | 11 | PYTHONPATH += $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) 12 | export PYTHONPATH 13 | 14 | test: 15 | cocotb-run --env 16 | 17 | clean: 18 | cocotb-clean 19 | -------------------------------------------------------------------------------- /tests/verilator.include-limits.patch: -------------------------------------------------------------------------------- 1 | diff --git a/include/verilated.cpp b/include/verilated.cpp 2 | index 1010544..2b2fd3c 100644 3 | --- a/include/verilated.cpp 4 | +++ b/include/verilated.cpp 5 | @@ -33,6 +33,7 @@ 6 | #include // mkdir 7 | #include 8 | #include 9 | +#include 10 | 11 | // clang-format off 12 | #if defined(_WIN32) || defined(__MINGW32__) 13 | -------------------------------------------------------------------------------- /cocotb_test/compat.py: -------------------------------------------------------------------------------- 1 | 2 | from packaging.version import parse as parse_version 3 | import cocotb 4 | 5 | # Use 1.999.0 in comparison to match pre-release versions of cocotb too 6 | cocotb_2x_or_newer = parse_version(cocotb.__version__) > parse_version("1.999.0") 7 | 8 | if cocotb_2x_or_newer: 9 | import cocotb_tools.config as cocotb_config 10 | else: 11 | import cocotb.config as cocotb_config 12 | -------------------------------------------------------------------------------- /tests/test_parameters.vhdl: -------------------------------------------------------------------------------- 1 | 2 | library ieee; 3 | use ieee.std_logic_1164.all; 4 | use ieee.numeric_std.all; 5 | 6 | entity test_parameters is 7 | generic( 8 | WIDTH_IN : integer := 8; 9 | WIDTH_OUT : integer := 8); 10 | port( 11 | data_in : in signed(WIDTH_IN-1 downto 0); 12 | data_out : out signed(WIDTH_OUT-1 downto 0)); 13 | end entity; 14 | 15 | architecture rtl of test_parameters is 16 | begin 17 | 18 | end architecture; 19 | -------------------------------------------------------------------------------- /tests/dff.vhdl: -------------------------------------------------------------------------------- 1 | -- This file is public domain, it can be freely copied without restrictions. 2 | -- SPDX-License-Identifier: CC0-1.0 3 | 4 | library ieee; 5 | use ieee.std_logic_1164.all; 6 | 7 | entity dff_test_vhdl is 8 | port( 9 | clk: in std_logic; 10 | d: in std_logic; 11 | q: out std_logic); 12 | end dff_test_vhdl; 13 | 14 | architecture behavioral of dff_test_vhdl is 15 | begin 16 | process (clk) begin 17 | if rising_edge(clk) then 18 | q <= d; 19 | end if; 20 | end process; 21 | end behavioral; 22 | -------------------------------------------------------------------------------- /tests/dff_wrapper.vhdl: -------------------------------------------------------------------------------- 1 | -- This file is public domain, it can be freely copied without restrictions. 2 | -- SPDX-License-Identifier: CC0-1.0 3 | 4 | library ieee; 5 | use ieee.std_logic_1164.all; 6 | 7 | library some_lib; 8 | use some_lib.dff_test_vhdl; 9 | 10 | entity dff_wrapper is 11 | port( 12 | clk: in std_logic; 13 | d: in std_logic; 14 | q: out std_logic); 15 | end dff_wrapper; 16 | 17 | architecture behavioral of dff_wrapper is 18 | begin 19 | 20 | dff_inst: entity some_lib.dff_test_vhdl 21 | port map ( 22 | clk => clk, 23 | d => d, 24 | q => q 25 | ); 26 | 27 | end behavioral; 28 | -------------------------------------------------------------------------------- /tests/plus_args.sv: -------------------------------------------------------------------------------- 1 | 2 | module plus_args (output reg user_mode); 3 | 4 | /* verilator lint_off WIDTH */ 5 | 6 | initial begin 7 | user_mode = 0; 8 | if ($test$plusargs("USER_MODE")) begin 9 | $display("plus_args:Configuring User mode"); 10 | //Execute some code for this mode 11 | user_mode = 1; 12 | end 13 | end 14 | 15 | initial begin 16 | string testname = ""; 17 | if ($value$plusargs("TEST=%s",testname)) begin 18 | $display("plus_args:Running TEST=%s.",testname); 19 | end 20 | end 21 | 22 | 23 | endmodule 24 | -------------------------------------------------------------------------------- /tests/test_long_log.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | 4 | import pytest 5 | from cocotb_test.simulator import run 6 | import os 7 | 8 | hdl_dir = os.path.dirname(__file__) 9 | 10 | 11 | @cocotb.test() 12 | async def run_test_long_log(dut): 13 | 14 | await Timer(1) 15 | 16 | dut._log.info("BEFORE") 17 | dut._log.info("LONGLOG" * 100000) 18 | dut._log.info("AFTER") 19 | 20 | 21 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="VHDL not suported") 22 | def test_long_log(): 23 | run(verilog_sources=[os.path.join(hdl_dir, "dff.sv")], module="test_long_log", toplevel="dff_test") 24 | -------------------------------------------------------------------------------- /tests/test_named_lib.py: -------------------------------------------------------------------------------- 1 | from cocotb_test.simulator import run 2 | import pytest 3 | import os 4 | 5 | tests_dir = os.path.dirname(__file__) 6 | 7 | @pytest.mark.skipif(os.getenv("SIM") not in ("questa", "ghdl", "nvc"), reason="Named libraries only supported for Questa, GHDL, and NVC.") 8 | def test_dff_vhdl(): 9 | run( 10 | vhdl_sources = { 11 | "some_lib": [os.path.join(tests_dir, "dff.vhdl")], 12 | "some_other_lib": [os.path.join(tests_dir, "dff_wrapper.vhdl")], 13 | }, 14 | toplevel="some_other_lib.dff_wrapper", 15 | module="dff_cocotb", 16 | toplevel_lang="vhdl", 17 | ) 18 | 19 | 20 | if __name__ == "__main__": 21 | test_dff_vhdl() 22 | -------------------------------------------------------------------------------- /tests/dff_cocotb.py: -------------------------------------------------------------------------------- 1 | # This file is public domain, it can be freely copied without restrictions. 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | import random 5 | import cocotb 6 | from cocotb.clock import Clock 7 | from cocotb.triggers import FallingEdge 8 | 9 | 10 | @cocotb.test() 11 | async def test_dff_simple(dut): 12 | """ Test that d propagates to q """ 13 | 14 | clock = Clock(dut.clk, 10, units="us") # Create a 10us period clock on port clk 15 | cocotb.start_soon(clock.start()) # Start the clock 16 | 17 | await FallingEdge(dut.clk) # Synchronize with the clock 18 | for i in range(10): 19 | val = random.randint(0, 1) 20 | dut.d.value = val # Assign the random value val to the input port d 21 | await FallingEdge(dut.clk) 22 | assert dut.q.value == val, "output q was incorrect on the {}th cycle".format(i) 23 | -------------------------------------------------------------------------------- /tests/test_dff.py: -------------------------------------------------------------------------------- 1 | from cocotb_test.simulator import run 2 | import pytest 3 | import os 4 | 5 | tests_dir = os.path.dirname(__file__) 6 | 7 | 8 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 9 | # @pytest.mark.parametrize("seed", range(10)) 10 | def test_dff_verilog(): 11 | run(verilog_sources=[os.path.join(tests_dir, "dff.sv")], toplevel="dff_test", module="dff_cocotb", waves=True) # sources # top level HDL # name of cocotb test module 12 | 13 | 14 | @pytest.mark.skipif(os.getenv("SIM") == "verilator", reason="VHDL not suported") 15 | @pytest.mark.skipif(os.getenv("SIM") == "icarus", reason="VHDL not suported") 16 | def test_dff_vhdl(): 17 | run(vhdl_sources=[os.path.join(tests_dir, "dff.vhdl")], toplevel="dff_test_vhdl", module="dff_cocotb", toplevel_lang="vhdl") 18 | 19 | 20 | if __name__ == "__main__": 21 | test_dff_verilog() 22 | # test_dff_vhdl() 23 | -------------------------------------------------------------------------------- /tests/test_simulator_kwarg.py: -------------------------------------------------------------------------------- 1 | from cocotb_test.simulator import run 2 | import pytest 3 | import os 4 | 5 | tests_dir = os.path.dirname(__file__) 6 | 7 | # The regression test system currently runs all tests with SIM=(something valid) 8 | # It would be nice to include a test this with SIM unset in the automated regressions. 9 | 10 | 11 | # Check that SIM= environment variable takes priority over kwarg 12 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 13 | @pytest.mark.xfail(os.getenv("SIM") is None, 14 | reason="Bad simulator name should be used when SIM is unset", 15 | strict=True, 16 | raises=NotImplementedError) 17 | def test_env_takes_priority(): 18 | run( 19 | verilog_sources=[ 20 | os.path.join(tests_dir, "dff.sv"), 21 | ], 22 | toplevel="dff_test", 23 | module="dff_cocotb", 24 | simulator="bad_sim_name" 25 | ) 26 | -------------------------------------------------------------------------------- /tests/subfolder1/subfolder2/test_work_dir.py: -------------------------------------------------------------------------------- 1 | from cocotb_test.simulator import run 2 | import pytest 3 | import os 4 | 5 | dir = os.path.dirname(__file__) 6 | 7 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 8 | @pytest.mark.xfail(reason="Expected to fail with no work_dir specified") 9 | def test_dff_no_workdir(): 10 | run(verilog_sources=[os.path.join(dir, "..", "..", "dff.sv")], toplevel="dff_test", module="dff_cocotb") # sources # top level HDL # name of cocotb test module 11 | 12 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 13 | def test_dff_workdir(): 14 | run(verilog_sources=[os.path.join(dir, "..", "..", "dff.sv")], work_dir=os.path.join(dir, "..", ".."), toplevel="dff_test", module="dff_cocotb") # sources # top level HDL # name of cocotb test module 15 | 16 | 17 | if __name__ == "__main__": 18 | # Intended to be run via pytest, not directly 19 | for test in [test_dff_no_workdir, test_dff_workdir]: 20 | try: 21 | test() 22 | except: 23 | if test == test_dff_no_workdir: 24 | print("\n**** Expected failure when no work_dir is set.\n") 25 | else: 26 | raise -------------------------------------------------------------------------------- /tests/test_multitop.py: -------------------------------------------------------------------------------- 1 | from cocotb_test.simulator import run 2 | import pytest 3 | import os 4 | 5 | import cocotb 6 | from cocotb.triggers import Timer, ReadOnly 7 | 8 | tests_dir = os.path.dirname(__file__) 9 | 10 | @pytest.mark.skipif(os.getenv("SIM") not in ("icarus", "questa"), reason="Multitop feature only supported for Icarus and Questa") 11 | def test_dff_verilog(): 12 | run( 13 | verilog_sources=[ 14 | os.path.join(tests_dir, "glbl.v"), 15 | os.path.join(tests_dir, "glbl_sink.v"), 16 | ], 17 | toplevel=["glbl_sink", "glbl"], 18 | module="test_multitop", 19 | # time unit and precision set the same 20 | timescale="1ns/1ns", 21 | ) 22 | 23 | 24 | @cocotb.test() 25 | async def glbl(dut): 26 | await ReadOnly() 27 | assert dut.rst.value != 0 28 | 29 | # BEWARE: Timer(10) is equal to Timer(10, 'step') which is ten simulator *precision* steps. 30 | # Only the same as Verilog delay '#10' (ten *time units*) when timescale directive sets 31 | # the time unit same as precision ie. with timescale="1ns/1ns" or "1ps/1ps" 32 | await Timer(10) 33 | await ReadOnly() 34 | assert dut.rst.value != 1 35 | 36 | 37 | if __name__ == "__main__": 38 | test_dff_verilog() 39 | -------------------------------------------------------------------------------- /tests/test_plus_args.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer 3 | 4 | import pytest 5 | from cocotb_test.simulator import run 6 | import os 7 | 8 | hdl_dir = os.path.dirname(__file__) 9 | 10 | @cocotb.test(skip=False) 11 | async def run_test(dut): 12 | 13 | await Timer(1) 14 | 15 | user_mode = int(dut.user_mode) 16 | 17 | assert user_mode == 1, "user_mode mismatch detected : got %d, exp %d!" % (dut.user_mode, 1) 18 | 19 | 20 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 21 | def test_plus_args(): 22 | run( 23 | verilog_sources=[os.path.join(hdl_dir, "plus_args.sv")], 24 | module="test_plus_args", 25 | toplevel="plus_args", 26 | plus_args=["+USER_MODE", "+TEST=ARB_TEST"], 27 | ) 28 | 29 | 30 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 31 | @pytest.mark.xfail 32 | def test_plus_args_fail(): 33 | run(verilog_sources=[os.path.join(hdl_dir,"plus_args.sv")], toplevel="plus_args") 34 | 35 | 36 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 37 | @pytest.mark.xfail 38 | def test_plus_args_test_wrong(): 39 | run( 40 | verilog_sources=[os.path.join(hdl_dir, "plus_args.sv")], toplevel="plus_args", plus_args=["+XUSER_MODE"] 41 | ) 42 | 43 | 44 | if __name__ == "__main__": 45 | test_plus_args() 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, Tomasz Hemperek 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | # Read version from cocotb_test/__init__.py without importing the package 5 | version = {} 6 | with open(os.path.join(os.path.dirname(__file__), "cocotb_test", "__init__.py"), "r") as f: 7 | exec(f.read(), version) 8 | 9 | def read_file(fname): 10 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 11 | 12 | 13 | setup( 14 | name="cocotb-test", 15 | version=version["__version__"], 16 | description="", 17 | url="", 18 | license="BSD", 19 | long_description=read_file("README.md"), 20 | long_description_content_type="text/markdown", 21 | author="Tomasz Hemperek", 22 | author_email="hemperek@uni-bonn.de", 23 | packages=find_packages(include=["cocotb_test", "cocotb_test.*"]), 24 | include_package_data = True, 25 | python_requires=">=3.7", 26 | install_requires=[ 27 | "cocotb>=1.5", 28 | "pytest", 29 | "find_libpython", 30 | "packaging", 31 | ], 32 | entry_points={ 33 | "console_scripts": [ 34 | "cocotb-test=cocotb_test.cli:config", 35 | "cocotb-run=cocotb_test.cli:run", 36 | "cocotb-clean=cocotb_test.cli:clean", 37 | ], 38 | "pytest11": ["pytest-cocotb = cocotb_test.plugin"], 39 | }, 40 | platforms="any", 41 | classifiers=[ 42 | "Programming Language :: Python :: 3", 43 | "License :: OSI Approved :: BSD License", 44 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", 45 | "Framework :: cocotb", 46 | ], 47 | ) 48 | -------------------------------------------------------------------------------- /tests/test_cocotb_examples.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cocotb 3 | import pytest 4 | 5 | from cocotb_test.compat import cocotb_2x_or_newer 6 | from cocotb_test.simulator import run 7 | 8 | if cocotb_2x_or_newer: 9 | cocotb_repo_root = os.path.dirname(os.path.dirname(os.path.dirname(cocotb.__file__))) 10 | else: 11 | cocotb_repo_root = os.path.dirname(os.path.dirname(cocotb.__file__)) 12 | 13 | example_dir = os.path.join(cocotb_repo_root, "examples") 14 | 15 | if os.path.isdir(example_dir) == False: 16 | raise IOError("Cocotb example directory not found. Please clone with git and install with `pip -e`") 17 | 18 | 19 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 20 | def test_adder_verilog(): 21 | run( 22 | verilog_sources=[os.path.join(example_dir, "adder", "hdl", "adder.sv")], 23 | toplevel="adder", 24 | python_search=[os.path.join(example_dir, "adder", "tests"), os.path.join(example_dir, "adder", "model")], 25 | module="test_adder", 26 | force_compile=True, 27 | ) 28 | 29 | 30 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Errors in compilation cocotb example") 31 | @pytest.mark.skipif(os.getenv("SIM") in ["icarus", "verilator"], reason="VHDL not supported") 32 | def test_adder_vhdl(): 33 | run( 34 | vhdl_sources=[os.path.join(example_dir, "adder", "hdl", "adder.vhdl")], 35 | toplevel="adder", 36 | python_search=[os.path.join(example_dir, "adder", "tests"), os.path.join(example_dir, "adder", "model")], 37 | module="test_adder", 38 | toplevel_lang="vhdl", 39 | force_compile=True, 40 | ) 41 | -------------------------------------------------------------------------------- /tests/test_parallel.py: -------------------------------------------------------------------------------- 1 | from cocotb_test.simulator import run 2 | import pytest 3 | import os 4 | 5 | tests_dir = os.path.dirname(__file__) 6 | 7 | # For parallel runs 8 | # Pre-compile: 9 | # pytest -m compile test_parallel.py 10 | # Run: 11 | # pytest -m 'not compile' -n 2 test_parallel.py 12 | # There are possibly better ways to do this 13 | 14 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 15 | @pytest.mark.compile 16 | def test_complile(): 17 | run( 18 | verilog_sources=[os.path.join(tests_dir, "dff.sv")], 19 | toplevel="dff_test", 20 | module="dff_cocotb", 21 | compile_only=True, 22 | ) 23 | 24 | 25 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 26 | @pytest.mark.parametrize("seed", range(6)) 27 | def test_dff_verilog_param(seed): 28 | run( 29 | verilog_sources=[os.path.join(tests_dir, "dff.sv")], 30 | toplevel="dff_test", 31 | module="dff_cocotb", 32 | seed=seed, 33 | ) 34 | 35 | 36 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 37 | @pytest.mark.parametrize("seed", range(2)) 38 | def test_dff_verilog_testcase(seed): 39 | run( 40 | verilog_sources=[os.path.join(tests_dir, "dff.sv")], 41 | toplevel="dff_test", 42 | module="dff_cocotb", 43 | seed=seed 44 | ) 45 | 46 | 47 | # For GHDL create new build/direcotry for every run 48 | @pytest.mark.skipif(os.getenv("SIM") == "verilator", reason="VHDL not suported") 49 | @pytest.mark.skipif(os.getenv("SIM") == "icarus", reason="VHDL not suported") 50 | @pytest.mark.parametrize("seed", range(8)) 51 | def test_dff_vhdl_param(seed): 52 | run( 53 | vhdl_sources=[os.path.join(tests_dir, "dff.vhdl")], 54 | toplevel="dff_test_vhdl", 55 | module="dff_cocotb", 56 | toplevel_lang="vhdl", 57 | seed=seed, 58 | sim_build="sim_build/" + str(seed) 59 | ) 60 | -------------------------------------------------------------------------------- /tests/test_cocotb_tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cocotb 3 | import pytest 4 | import sys 5 | 6 | from cocotb_test.compat import cocotb_2x_or_newer 7 | from cocotb_test.simulator import run 8 | 9 | if cocotb_2x_or_newer: 10 | cocotb_repo_root = os.path.dirname(os.path.dirname(os.path.dirname(cocotb.__file__))) 11 | else: 12 | cocotb_repo_root = os.path.dirname(os.path.dirname(cocotb.__file__)) 13 | 14 | tests_dir = os.path.join(cocotb_repo_root, "tests") 15 | 16 | if os.path.isdir(tests_dir) == False: 17 | raise IOError( 18 | "Cocotb test directory not found. Please clone with git and install with `pip -e`" 19 | ) 20 | 21 | 22 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 23 | @pytest.mark.skipif(os.getenv("SIM") in ["icarus", "verilator"] , reason="VHDL not supported") 24 | def test_verilog_access(): 25 | run( 26 | verilog_sources=[ 27 | os.path.join(tests_dir, "designs", "uart2bus", "verilog", file) 28 | for file in [ 29 | "baud_gen.v", 30 | "uart_parser.v", 31 | "uart_rx.v", 32 | "uart_tx.v", 33 | "uart_top.v", 34 | "uart2bus_top.v", 35 | ] 36 | ] 37 | + [os.path.join(tests_dir, "designs", "uart2bus", "top", "verilog_toplevel.sv")], 38 | vhdl_sources=[ 39 | os.path.join( 40 | tests_dir, "designs", "uart2bus", "vhdl", "uart2BusTop_pkg.vhd" 41 | ), 42 | os.path.join(tests_dir, "designs", "uart2bus", "vhdl", "baudGen.vhd"), 43 | os.path.join(tests_dir, "designs", "uart2bus", "vhdl", "uartParser.vhd"), 44 | os.path.join(tests_dir, "designs", "uart2bus", "vhdl", "uartRx.vhd"), 45 | os.path.join(tests_dir, "designs", "uart2bus", "vhdl", "uartTx.vhd"), 46 | os.path.join(tests_dir, "designs", "uart2bus", "vhdl", "uartTop.vhd"), 47 | os.path.join(tests_dir, "designs", "uart2bus", "vhdl", "uart2BusTop.vhd"), 48 | ], 49 | python_search=[os.path.join(tests_dir, "test_cases", "test_verilog_access")], 50 | toplevel="verilog_toplevel", 51 | module="test_verilog_access", 52 | ) 53 | -------------------------------------------------------------------------------- /tests/test_dff_custom_sim.py: -------------------------------------------------------------------------------- 1 | 2 | from cocotb_test.simulator import Icarus, Ius, run 3 | import pytest 4 | import os 5 | import cocotb 6 | from cocotb_test.compat import cocotb_config 7 | 8 | hdl_dir = os.path.dirname(__file__) 9 | 10 | class IcarusCustom(Icarus): 11 | def __init__(self, logfile, *argv, **kwargs): 12 | self.logfile = logfile 13 | super().__init__(*argv, **kwargs) 14 | 15 | def run_command(self): 16 | return ( 17 | ["vvp", "-v", "-l", self.logfile, "-M", self.lib_dir, "-m", cocotb_config.lib_name("vpi", "icarus")] 18 | + self.simulation_args 19 | + [self.sim_file] 20 | + self.plus_args 21 | ) 22 | 23 | @pytest.fixture(scope="module", autouse=True) 24 | def module_run_at_beginning(request): 25 | print("\n\nIn module_run_at_beginning()\n\n") 26 | 27 | def module_run_at_end(): 28 | print("\n\nIn module_run_at_end()\n\n") 29 | 30 | request.addfinalizer(module_run_at_end) 31 | 32 | 33 | @pytest.mark.skipif(os.getenv("SIM") != "icarus", reason="Custom for Icarus") 34 | def test_dff_custom_icarus(): 35 | IcarusCustom( 36 | verilog_sources=[os.path.join(hdl_dir, "dff.sv")], 37 | toplevel="dff_test", 38 | python_search=[hdl_dir], 39 | module="dff_cocotb", 40 | logfile="custom_log.log", # extra custom argument 41 | ).run() 42 | 43 | 44 | class IusCustom(Ius): 45 | def __init__(self, defsfile, *argv, **kwargs): 46 | self.defsfile = defsfile 47 | super().__init__(*argv, **kwargs) 48 | 49 | def build_command(self): 50 | cmd = [ 51 | "xrun", 52 | "-loadvpi", 53 | os.path.join(self.lib_dir, "libvpi." + self.lib_ext) + ":vlog_startup_routines_bootstrap", 54 | "-plinowarn", 55 | "-access", 56 | "+rwc", 57 | "-f", 58 | self.defsfile, 59 | ] 60 | 61 | return [cmd] 62 | 63 | 64 | @pytest.mark.skipif(os.getenv("SIM") != "ius", reason="Custom for IUS") 65 | def test_dff_custom_ius(): 66 | IusCustom( 67 | verilog_sources=[os.path.join(hdl_dir, "dff.sv")], 68 | toplevel="dff_test", 69 | python_search=[hdl_dir], 70 | module="dff_cocotb", 71 | defsfile="ius_defines.f" 72 | ) 73 | -------------------------------------------------------------------------------- /tests/test_compile_errors.py: -------------------------------------------------------------------------------- 1 | # Test if compile errors are caught 2 | 3 | from cocotb_test.simulator import run 4 | import pytest 5 | import os 6 | 7 | tests_dir = os.path.dirname(__file__) 8 | 9 | 10 | @pytest.mark.skipif(os.getenv("SIM") == "verilator", reason="VHDL not suported") 11 | @pytest.mark.skipif(os.getenv("SIM") == "icarus", reason="VHDL not suported") 12 | @pytest.mark.xfail(strict=True, raises=SystemExit) 13 | def test_invalid_vhdl(): 14 | run( 15 | vhdl_sources=[ 16 | os.path.join(tests_dir, "dff.vhdl"), 17 | os.path.join(tests_dir, "invalid.vhdl"), 18 | ], 19 | toplevel="dff_test_vhdl", 20 | module="dff_cocotb", 21 | toplevel_lang="vhdl", 22 | sim_build="sim_build/test_invalid_vhdl" 23 | ) 24 | 25 | 26 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 27 | @pytest.mark.xfail(strict=True, raises=SystemExit) 28 | def test_invalid_verilog(): 29 | run( 30 | verilog_sources=[ 31 | os.path.join(tests_dir, "dff.sv"), 32 | os.path.join(tests_dir, "invalid.v"), 33 | ], 34 | toplevel="dff_test", 35 | module="dff_cocotb", 36 | sim_build="sim_build/test_invalid_verilog" 37 | ) 38 | 39 | 40 | @pytest.mark.skipif(os.getenv("SIM") == "verilator", reason="VHDL not suported") 41 | @pytest.mark.skipif(os.getenv("SIM") == "icarus", reason="VHDL not suported") 42 | @pytest.mark.xfail(strict=True) 43 | def test_missing_vhdl(): 44 | run( 45 | vhdl_sources=[ 46 | os.path.join(tests_dir, "dff.vhdl"), 47 | os.path.join(tests_dir, "missing_file.vhdl"), 48 | ], 49 | toplevel="dff_test_vhdl", 50 | module="dff_cocotb", 51 | toplevel_lang="vhdl", 52 | sim_build="sim_build/test_missing_vhdl" 53 | ) 54 | 55 | 56 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 57 | @pytest.mark.skipif(os.getenv("SIM") == "icarus", reason="Missing source is not an error in Icarus?") 58 | @pytest.mark.xfail(strict=True) 59 | def test_missing_verilog(): 60 | run( 61 | verilog_sources=[ 62 | os.path.join(tests_dir, "dff.sv"), 63 | os.path.join(tests_dir, "missing_file.v"), 64 | ], 65 | toplevel="dff_test", 66 | module="dff_cocotb", 67 | sim_build="sim_build/test_missing_verilog" 68 | ) 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .vscode 107 | 108 | sim_build 109 | cocotb_build 110 | 111 | # Vim tmp files 112 | *.swp 113 | *~ 114 | 115 | # Emacs tmp files 116 | \#*\# 117 | \.\#* 118 | 119 | # Mergetool tmp files 120 | *.orig 121 | *.bak 122 | 123 | # Waveforms 124 | *.vcd 125 | 126 | # Results 127 | results.xml 128 | combined_results.xml 129 | 130 | # VCS files 131 | *.tab 132 | sim_build 133 | ucli.key 134 | 135 | # Cadence Incisive/Xcelium 136 | *.elog 137 | irun.log 138 | xrun.log 139 | irun.key 140 | xrun.key 141 | irun.history 142 | xrun.history 143 | INCA_libs 144 | xcelium.d 145 | ncelab_*.err 146 | xmelab_*.err 147 | ncsim_*.err 148 | xmsim_*.err 149 | bpad_*.err 150 | .bpad/ 151 | .simvision/ 152 | waves.shm/ 153 | 154 | /.project 155 | /.pydevproject 156 | /.dvt 157 | 158 | /cocotb_test/libs 159 | .cocotb-results 160 | 161 | -------------------------------------------------------------------------------- /tests/test_parameters.py: -------------------------------------------------------------------------------- 1 | from cocotb_test.simulator import run 2 | import pytest 3 | import os 4 | 5 | import cocotb 6 | from cocotb.triggers import Timer 7 | 8 | tests_dir = os.path.dirname(__file__) 9 | 10 | 11 | @cocotb.test() 12 | async def run_test_parameters(dut): 13 | 14 | await Timer(1) 15 | 16 | WIDTH_IN = int(os.environ.get("WIDTH_IN", "8")) 17 | WIDTH_OUT = int(os.environ.get("WIDTH_OUT", "8")) 18 | 19 | assert WIDTH_IN == len(dut.data_in) 20 | assert WIDTH_OUT == len(dut.data_out) 21 | 22 | 23 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 24 | @pytest.mark.parametrize( 25 | "parameters", [{"WIDTH_IN": "8", "WIDTH_OUT": "16"}, {"WIDTH_IN": "16"}] 26 | ) 27 | def test_dff_verilog_testcase(parameters): 28 | run( 29 | verilog_sources=[os.path.join(tests_dir, "test_parameters.v")], 30 | toplevel="test_parameters", 31 | module="test_parameters", 32 | parameters=parameters, 33 | includes=[os.path.join(tests_dir, "includes")], 34 | defines= ["DEFINE=1"], 35 | extra_env=parameters, 36 | sim_build="sim_build/" 37 | + "_".join(("{}={}".format(*i) for i in parameters.items())), 38 | ) 39 | 40 | 41 | @pytest.mark.skipif(os.getenv("SIM") == "verilator", reason="VHDL not suported") 42 | @pytest.mark.skipif(os.getenv("SIM") == "icarus", reason="VHDL not suported") 43 | @pytest.mark.parametrize( 44 | "parameters", [{"WIDTH_IN": "8", "WIDTH_OUT": "16"}, {"WIDTH_IN": "16"}] 45 | ) 46 | def test_dff_vhdl_testcase(parameters): 47 | run( 48 | toplevel_lang="vhdl", 49 | vhdl_sources=[os.path.join(tests_dir, "test_parameters.vhdl")], 50 | toplevel="test_parameters", 51 | module="test_parameters", 52 | parameters=parameters, 53 | extra_env=parameters, 54 | sim_build="sim_build/" 55 | + "_".join(("{}={}".format(*i) for i in parameters.items())), 56 | ) 57 | 58 | @pytest.mark.skipif(os.getenv("SIM") in ("ghdl", "nvc"), reason="Verilog not suported") 59 | def test_bad_timescales(): 60 | 61 | kwargs = { 62 | 'verilog_sources': [ 63 | os.path.join(tests_dir, "dff.sv"), 64 | ], 65 | 'module': "dff_cocotb", 66 | 'toplevel': "dff_test", 67 | 'sim_build': "sim_build/test_missing_verilog", 68 | } 69 | 70 | with pytest.raises(ValueError, match='Invalid timescale: 1ns'): 71 | run(timescale="1ns", **kwargs) 72 | 73 | with pytest.raises(ValueError, match='Invalid timescale: 1qs/1s'): 74 | run(timescale="1qs/1s", **kwargs) 75 | 76 | run(timescale="100ns/100ns", **kwargs) 77 | run(timescale="1ns/1ns", **kwargs) 78 | run(timescale=None, **kwargs) 79 | 80 | -------------------------------------------------------------------------------- /cocotb_test/plugin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | from xml.etree import cElementTree as ET 4 | 5 | 6 | class ResultsCocotb(object): 7 | def __init__(self, results_xml_output): 8 | self.results_xml_output = results_xml_output 9 | self.names = [] 10 | self.results_xml_dir = os.path.abspath(".cocotb-results") 11 | 12 | def get_results_xml_file(self, nodeid): 13 | return os.path.join(self.results_xml_dir, nodeid.replace(os.sep, "_").replace("/", "_").replace(":", "-") + ".xml") 14 | 15 | def pytest_runtest_logreport(self, report): 16 | if report.when == "call" and report.outcome != "skipped": 17 | self.names.append(report.nodeid) 18 | 19 | def pytest_sessionstart(self, session): 20 | os.makedirs(self.results_xml_dir, exist_ok=True) 21 | 22 | def pytest_runtest_setup(self, item): 23 | 24 | cocotb_result_file = self.get_results_xml_file(item._nodeid) 25 | if os.path.exists(cocotb_result_file): 26 | os.remove(cocotb_result_file) 27 | 28 | os.environ["COCOTB_RESULTS_FILE"] = self.get_results_xml_file(item._nodeid) 29 | os.environ["RESULT_TESTPACKAGE"] = item._nodeid 30 | 31 | def pytest_sessionfinish(self, session): 32 | 33 | result = ET.Element("testsuites", name="results") 34 | 35 | for nodeid in self.names: 36 | fname = self.get_results_xml_file(nodeid) 37 | 38 | if os.path.isfile(fname): 39 | tree = ET.parse(fname) 40 | for testsuite in tree.iter("testsuite"): 41 | use_element = None 42 | 43 | for testcase in testsuite.iter("testcase"): 44 | testcase.set("classname", "cocotb." + testcase.get("classname")) # add cocotb. for easier selection 45 | 46 | for existing in result: 47 | if existing.get("name") == testsuite.get("name") and (existing.get("package") == testsuite.get("package")): 48 | use_element = existing 49 | break 50 | if use_element is None: 51 | result.append(testsuite) 52 | else: 53 | use_element.extend(list(testsuite)) 54 | 55 | ET.ElementTree(result).write(self.results_xml_output, encoding="UTF-8") 56 | 57 | def pytest_runtest_teardown(self, item, nextitem): 58 | results_xml_file = self.get_results_xml_file(item._nodeid) 59 | results_xml_file_defulat = os.path.join("sim_build", "results.xml") 60 | 61 | if os.path.isfile(results_xml_file_defulat): 62 | os.rename(results_xml_file_defulat, results_xml_file) 63 | 64 | 65 | def pytest_unconfigure(config): 66 | cocotb = getattr(config, "_cocotb", None) 67 | if cocotb: 68 | config.pluginmanager.unregister(cocotb) 69 | 70 | 71 | def pytest_configure(config): 72 | if config.option.cocotb_xml: 73 | config._cocotb = ResultsCocotb(config.option.cocotb_xml) 74 | config.pluginmanager.register(config._cocotb) 75 | 76 | 77 | def pytest_addoption(parser): 78 | group = parser.getgroup("terminal reporting") 79 | group.addoption( 80 | "--cocotbxml", 81 | "--cocotb-xml", 82 | action="store", 83 | dest="cocotb_xml", 84 | default=None, 85 | metavar="path", 86 | help="create junit-xml style report file for cocotb reports at given path.", 87 | ) 88 | -------------------------------------------------------------------------------- /cocotb_test/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ############################################################################### 3 | # Copyright (c) 2013 Potential Ventures Ltd 4 | # Copyright (c) 2013 SolarFlare Communications Inc 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions are met: 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # * Redistributions in binary form must reproduce the above copyright 12 | # notice, this list of conditions and the following disclaimer in the 13 | # documentation and/or other materials provided with the distribution. 14 | # * Neither the name of Potential Ventures Ltd, 15 | # SolarFlare Communications Inc nor the 16 | # names of its contributors may be used to endorse or promote products 17 | # derived from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY 23 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | ############################################################################### 30 | 31 | import os 32 | import sys 33 | import argparse 34 | from cocotb_test import simulator, __version__ 35 | 36 | 37 | class PrintAction(argparse.Action): 38 | def __init__(self, option_strings, dest, text=None, **kwargs): 39 | super(PrintAction, self).__init__(option_strings, dest, nargs=0, **kwargs) 40 | self.text = text 41 | 42 | def __call__(self, parser, namespace, values, option_string=None): 43 | print(self.text) 44 | parser.exit() 45 | 46 | 47 | def config(): 48 | 49 | makefiles_dir = os.path.join(os.path.dirname(__file__), "Makefile.inc") 50 | version = __version__ 51 | 52 | parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter) 53 | parser.add_argument( 54 | "--inc-makefile", 55 | help="echos the makefile to include", 56 | action=PrintAction, 57 | text=makefiles_dir, 58 | ) 59 | parser.add_argument( 60 | "-v", 61 | "--version", 62 | help="echos version of cocotb", 63 | action=PrintAction, 64 | text=version, 65 | ) 66 | 67 | if len(sys.argv) == 1: 68 | parser.print_help(sys.stderr) 69 | sys.exit(1) 70 | 71 | args = parser.parse_args() 72 | 73 | 74 | def run(): 75 | parser = argparse.ArgumentParser( 76 | description="cocotb-run", formatter_class=argparse.RawTextHelpFormatter 77 | ) 78 | parser.add_argument( 79 | "-e", 80 | "--env", 81 | dest="env", 82 | action="store_true", 83 | help="Run simulation based on enviroment variables", 84 | ) 85 | 86 | args = parser.parse_args() 87 | 88 | if args.env: 89 | kwargs = {} 90 | kwargs["verilog_sources"] = os.getenv("VERILOG_SOURCES", "").split() 91 | kwargs["vhdl_sources"] = os.getenv("VHDL_SOURCES", "").split() 92 | kwargs["toplevel"] = os.getenv("TOPLEVEL") 93 | kwargs["toplevel_lang"] = os.getenv("TOPLEVEL_LANG") 94 | kwargs["module"] = os.getenv("MODULE") 95 | kwargs["simulation_args"] = os.getenv("SIM_ARGS", "").split() 96 | kwargs["compile_args"] = os.getenv("COMPILE_ARGS", "").split() 97 | kwargs["extra_args"] = os.getenv("EXTRA_ARGS", "").split() 98 | kwargs["plus_args"] = os.getenv("PLUS_ARGS", "").split() 99 | kwargs["timescale"] = os.getenv("TIMESCALE", "1ns/1ps") 100 | kwargs["python_search"] = ( 101 | os.getenv("PYTHONPATH", "").replace(";", " ").replace(":", " ").split() 102 | ) 103 | simulator.run(**kwargs) 104 | else: 105 | parser.print_help(sys.stderr) 106 | sys.exit(1) 107 | 108 | 109 | def clean(): 110 | parser = argparse.ArgumentParser( 111 | description="cocotb-clean", formatter_class=argparse.RawTextHelpFormatter 112 | ) 113 | parser.add_argument( 114 | "-r", 115 | "--recursive", 116 | dest="recursive", 117 | action="store_true", 118 | help="Recursive clean", 119 | ) 120 | args = parser.parse_args() 121 | 122 | simulator.clean(recursive=args.recursive) 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cocotb-test 2 | [![Build Status](https://dev.azure.com/themperek/themperek/_apis/build/status/themperek.cocotb-test?branchName=master)](https://dev.azure.com/themperek/themperek/_build/latest?definitionId=2&branchName=master) 3 | [![PyPI version](https://badge.fury.io/py/cocotb-test.svg)](https://badge.fury.io/py/cocotb-test) 4 | 5 | > [!NOTE] 6 | > Since version 1.8 [cocotb](https://github.com/cocotb/cocotb) supports [python based runner](https://docs.cocotb.org/en/stable/runner.html). 7 | 8 | ``cocotb-test`` provides standard python unit testing capabilities for [cocotb](https://github.com/cocotb/cocotb) 9 | - allow the look and feel of Python unit testing 10 | - remove the need for Makefiles (includes Makefile compatibility mode) 11 | - allow easy customization of simulation flow 12 | - allow to use [pytest-xdist](https://pypi.org/project/pytest-xdist/) or [pytest-parallel](https://github.com/browsertron/pytest-parallel) for parallel runs 13 | 14 | # Usage: 15 | 16 | - Install [cocotb](https://docs.cocotb.org/). 17 | 18 | - Install simulator (for Icarus Verilog with conda, on widows use 10.3 or devel [steveicarus/iverilog#395](https://github.com/steveicarus/iverilog/issues/395) ): 19 | ```bash 20 | conda install -c conda-forge iverilog==10.3 21 | ``` 22 | - Install the package via [pip](https://pip.pypa.io/en/stable/user_guide/): 23 | ```bash 24 | pip install cocotb-test 25 | ``` 26 |  or development version 27 | ```bash 28 | pip install -v https://github.com/themperek/cocotb-test/archive/master.zip 29 | ``` 30 |  or 31 | ```bash 32 | git clone https://github.com/themperek/cocotb-test.git 33 | pip install -v -e cocotb-test 34 | ``` 35 | 36 | - Create a `test_dff.py` file (check [test folder](https://github.com/themperek/cocotb-test/tree/master/tests) for more examples): 37 | ```python 38 | from cocotb_test.simulator import run 39 | def test_dff(): 40 | run( 41 | verilog_sources=["dff.sv"], # sources 42 | toplevel="dff", # top level HDL 43 | module="dff_cocotb" # name of cocotb test module 44 | ) 45 | ``` 46 | 47 | - Run [pytest](https://docs.pytest.org/en/latest/contents.html) (need `dff.sv` and `dff_cocotb.py` in same directory where running `pytest`): 48 | ```bash 49 | SIM=icarus pytest -o log_cli=True test_dff.py 50 | ``` 51 | 52 | - To clean (remove all `sim_build` folders): 53 | ```bash 54 | cocotb-clean -r 55 | ``` 56 | ### Arguments for `simulator.run`: 57 | 58 | * `toplevel`: Top level module name to indicate the instance in the hierarchy to use as the DUT. Multiple top levels can be specified as a list, if the simulator supports it. 59 | * `module`: The name of the module(s) to search for test functions (see [MODULE](https://docs.cocotb.org/en/stable/building.html?#envvar-MODULE) ). 60 | 61 | * `python_search` : List of additional directories to search for python/cocotb modules. 62 | * `verilog_sources`: Verilog source files to include. Can be specified as a `list` or as a `dict` of `list`s with the library name as key, if the simulator supports named libraries. 63 | * `vhdl_sources`: VHDL source files to include. Can be specified as a `list` or as a `dict` of `list`s with the library name as key, if the simulator supports named libraries. 64 | * `toplevel_lang`: see [TOPLEVEL_LANG](https://docs.cocotb.org/en/stable/building.html?#var-TOPLEVEL_LANG). (default: `verilog`) 65 | * `includes`: A list of directories to search for includes. 66 | * `defines`: A list of the defines. 67 | * `parameters`: A dictionary of top-level parameters/generics. 68 | * `compile_args`: Any arguments or flags to pass to the compile stage of the simulation. 69 | * `vhdl_compile_args`: Additional arguments or flags passed only when compiling VHDL sources. 70 | * `verilog_compile_args`: Additional arguments or flags passed only when compiling Verilog sources. 71 | * `sim_args`: Any arguments or flags to pass to the execution of the compiled simulation. 72 | * `extra_args`: Passed to both the compile and execute phases of simulators. 73 | * `plus_args`: plusargs arguments passed to simulator. 74 | * `force_compile`: Force compilation even if sources did not change. (default: `False`) 75 | * `compile_only`: Only compile sources. Do not run simulation. (default: `False`) 76 | * `testcase`: The name of the test function(s) to run (see [TESTCASE](https://docs.cocotb.org/en/stable/building.html?#envvar-TESTCASE) ). 77 | * `sim_build`: The directory used to compile the tests. (default: `sim_build`) 78 | * `work_dir`: The directory used to tun the tests. (default: same as `sim_build` argument) 79 | * `seed`: Seed the Python random module to recreate a previous test stimulus (see [RANDOM_SEED](https://docs.cocotb.org/en/stable/building.html?#envvar-RANDOM_SEED) ). 80 | * `extra_env`: A dictionary of extra environment variables set in simulator process. 81 | * `waves`: Enable wave dumps (not all simulators supported). 82 | * `timescale`: Set simulator time unit/precision (default: `None`) 83 | * `gui`: Starts in gui mode (not all simulators supported). 84 | * `make_args`: Arguments passed to make phase (`Verilator` only). 85 | 86 | 87 | ### Environmental variables: 88 | 89 | * `SIM`: Selects which simulator to use. (default: `icarus`) 90 | * `WAVES`: Overwrite enable wave dumps argument. Example use `WAVES=1 pytest test_dff.py`. 91 | 92 | ### pytest arguments 93 | 94 | * `cocotbxml`: Combines and saves junitxml reports from cocotb tests. Example use `pytest --cocotbxml=test-cocotb.xml`. 95 | 96 | ### Tips and tricks: 97 | 98 | * List all available test: 99 | ```bash 100 | pytest --collect-only 101 | ``` 102 | 103 | * Run only selected test: 104 | ```bash 105 | pytest -k test_dff_verilog_param[3] 106 | ``` 107 | 108 | * Test parameterization (for more see: [test_parameters.py](https://github.com/themperek/cocotb-test/blob/master/tests/test_parameters.py) ) 109 | 110 | ```python 111 | @pytest.mark.parametrize("width", [{"WIDTH_IN": "8"}, {"WIDTH_IN": "16"}]) 112 | def test_dff_verilog_testcase(width): 113 | run( 114 | ... 115 | parameters=width, 116 | sim_build="sim_build/" + "_".join(("{}={}".format(*i) for i in width.items())), 117 | ) 118 | ``` 119 | 120 | * Run test in parallel (after installing [pytest-xdist](https://pypi.org/project/pytest-xdist/) ) 121 | ```bash 122 | pytest -n NUMCPUS 123 | ``` 124 | 125 | # Running (some) tests and examples from cocotb 126 | For cocotb tests/examples install cocotb in editable mode 127 | ```bash 128 | git clone https://github.com/potentialventures/cocotb.git 129 | pip install -e cocotb 130 | SIM=icarus pytest -o log_cli=True --junitxml=test-results.xml --cocotbxml=test-cocotb.xml tests 131 | ``` 132 | 133 | # Related resources 134 | - [pytest logging](https://docs.pytest.org/en/stable/logging.html) - pytest logging documentation 135 | - [pytest-xdist](https://pypi.org/project/pytest-xdist/) - test run parallelization (see [test_parallel](https://github.com/themperek/cocotb-test/blob/master/tests/test_parallel.py)) 136 | - [pytest-parallel](https://github.com/browsertron/pytest-parallel) - parallel and concurrent testing (see [test_parallel](https://github.com/themperek/cocotb-test/blob/master/tests/test_parallel.py)) 137 | - [pytest-html](https://github.com/pytest-dev/pytest-html) - generates a HTML report for the test results 138 | - [pytest-sugar](https://github.com/Teemu/pytest-sugar/) - sugar 139 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - '*' 5 | 6 | jobs: 7 | - job: 8 | strategy: 9 | matrix: 10 | Linux-Icarus-py37: 11 | python.version: "3.7" 12 | imageName: "ubuntu-latest" 13 | SIM: "icarus" 14 | cocotb_version: "v1.9.0" 15 | Linux-Icarus-py38: 16 | python.version: "3.8" 17 | imageName: "ubuntu-latest" 18 | SIM: "icarus" 19 | cocotb_version: "v1.9.0" 20 | Linux-Icarus-py39: 21 | python.version: "3.9" 22 | imageName: "ubuntu-latest" 23 | SIM: "icarus" 24 | cocotb_version: "v1.9.0" 25 | Linux-Icarus-py310: 26 | python.version: "3.10" 27 | imageName: "ubuntu-latest" 28 | SIM: "icarus" 29 | cocotb_version: "v1.9.0" 30 | Linux-Icarus-py310-cocotb20: 31 | python.version: "3.10" 32 | imageName: "ubuntu-latest" 33 | SIM: "icarus" 34 | # master as of 2025-01-03 35 | cocotb_version: "5237f9de3620aaf64e36d2e76960b9f7121bea96" 36 | Linux-GHDL-py38: 37 | python.version: "3.8" 38 | imageName: "ubuntu-latest" 39 | SIM: "ghdl" 40 | cocotb_version: "v1.9.0" 41 | Linux-NVC-py38: 42 | python.version: "3.8" 43 | imageName: "ubuntu-latest" 44 | SIM: "nvc" 45 | cocotb_version: "v1.9.0" 46 | Windows-Icarus-py310: 47 | python.version: "3.10" 48 | imageName: "windows-latest" 49 | SIM: "icarus" 50 | cocotb_version: "v1.9.0" 51 | # Mac-Icarus-py38: 52 | # imageName: "macOS-latest" 53 | # python.version: "3.8" 54 | # SIM: "icarus" 55 | # cocotb_version: "v1.9.0" 56 | Linux-Verilator-py38: 57 | python.version: "3.8" 58 | imageName: "ubuntu-latest" 59 | SIM: "verilator" 60 | cocotb_version: "v1.9.0" 61 | Linux-Verilator-py38-cocotb20: 62 | python.version: "3.8" 63 | imageName: "ubuntu-latest" 64 | SIM: "verilator" 65 | # master as of 2025-01-03 66 | cocotb_version: "5237f9de3620aaf64e36d2e76960b9f7121bea96" 67 | 68 | pool: 69 | vmImage: $(imageName) 70 | 71 | variables: 72 | SIM: $(SIM) 73 | cocotb_version: $(cocotb_version) 74 | readonly: true 75 | 76 | steps: 77 | - powershell: | 78 | Invoke-WebRequest -outfile miniconda3.exe https://repo.continuum.io/miniconda/Miniconda3-latest-Windows-x86_64.exe 79 | Start-Process .\miniconda3.exe -ArgumentList '/S /D=C:\miniconda3' -Wait 80 | Write-Host "##vso[task.setvariable variable=CONDA;]C:\miniconda3" 81 | Write-Host "##vso[task.prependpath]C:\miniconda3;C:\miniconda3\Library\mingw-w64\bin;C:\miniconda3\Library\usr\bin;C:\miniconda3\Library\bin;C:\miniconda3\Scripts;C:\miniconda3\bin;C:\miniconda3\condabin" 82 | displayName: Download and Install Anaconda on Windows 83 | condition: eq( variables['Agent.OS'], 'Windows_NT' ) 84 | 85 | - script: | 86 | wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O ~/miniconda.sh 87 | bash ~/miniconda.sh -b -p $HOME/miniconda 88 | echo "##vso[task.setvariable variable=CONDA;]$HOME/miniconda" 89 | displayName: Download and Install Anaconda on Darwin 90 | condition: eq( variables['Agent.OS'], 'Darwin' ) 91 | 92 | - bash: echo "##vso[task.prependpath]$CONDA/bin" 93 | displayName: Add conda to PATH 94 | 95 | - script: conda init 96 | displayName: Init Conda 97 | 98 | - script: | 99 | conda install -y python=$(python.version) 100 | conda update -q -y conda 101 | displayName: Update set python version and update 102 | 103 | - script: conda install --yes m2w64-gcc libpython 104 | displayName: Install Windows conda development 105 | condition: eq( variables['Agent.OS'], 'Windows_NT' ) 106 | 107 | - script: | 108 | sudo apt-get update 109 | sudo apt-get install -y gnat 110 | git clone https://github.com/ghdl/ghdl.git --depth=1 --branch v0.37 111 | cd ghdl && ./configure && make -j2 && sudo make install && cd .. 112 | displayName: Compile and Install GHDL on Linux 113 | condition: and( eq( variables['Agent.OS'], 'Linux' ), eq(variables['SIM'], 'ghdl')) 114 | 115 | - script: | 116 | sudo apt-get update 117 | sudo apt-get install -y build-essential automake autoconf flex check llvm-dev pkg-config zlib1g-dev libdw-dev libffi-dev libzstd-dev 118 | git clone https://github.com/nickg/nvc.git --depth=1 --branch r1.10.4 119 | cd nvc && ./autogen.sh && mkdir build && cd build && ../configure && make -j$(nproc) && sudo make install && cd ../.. 120 | displayName: Compile and Install NVC on Linux 121 | condition: and( eq( variables['Agent.OS'], 'Linux' ), eq(variables['SIM'], 'nvc')) 122 | 123 | - script: | 124 | sudo apt-get update 125 | sudo apt-get install git make autoconf g++ flex bison 126 | sudo apt-get install libfl2 127 | sudo apt-get install libfl-dev 128 | sudo apt-get install help2man 129 | git clone https://github.com/verilator/verilator --depth=1 130 | cd verilator && autoconf && ./configure && make -j2 && sudo make install && cd .. 131 | displayName: Compile and Install Verilator on Linux 132 | condition: and( eq( variables['Agent.OS'], 'Linux' ), eq(variables['SIM'], 'verilator')) 133 | 134 | - script: conda install --yes -c conda-forge iverilog 135 | displayName: Install Icarus Verilog 136 | condition: and( ne( variables['Agent.OS'], 'Windows_NT' ), eq(variables['SIM'], 'icarus')) 137 | 138 | - script: conda install --yes -c conda-forge iverilog==10.3 139 | displayName: Install Icarus Verilog 140 | condition: and( eq( variables['Agent.OS'], 'Windows_NT' ), eq(variables['SIM'], 'icarus')) 141 | 142 | - script: | 143 | conda install --yes -c conda-forge tox 144 | tox 145 | displayName: "Package Test with Tox" 146 | condition: ne( variables['Agent.OS'], 'Windows_NT' ) 147 | 148 | # AttributeError: module 'py' has no attribute 'log' for pytest 7.2 149 | - script: | 150 | pip install pytest-parallel pytest==7.1 151 | displayName: "Install pytest-parallel" 152 | 153 | - script: | 154 | pip install pytest-xdist 155 | displayName: "Install pytest-xdist" 156 | 157 | - script: | 158 | conda install --yes pip && git clone https://github.com/cocotb/cocotb.git && cd cocotb && git checkout ${{ variables.cocotb_version }} && cd .. && pip install -e cocotb 159 | displayName: "Install cocotb from source" 160 | 161 | - script: | 162 | pip install . 163 | displayName: "Install cocotb-test" 164 | 165 | - script: | 166 | pytest --junitxml=test-results.xml --cocotbxml=test-cocotb.xml tests 167 | displayName: "Test with pytest" 168 | condition: ne(variables['SIM'], 'verilator') 169 | 170 | - script: | 171 | pytest -m compile tests/test_parallel.py 172 | pytest -m "not compile" --workers 2 tests/test_parallel.py 173 | displayName: "Test with pytest-parallel" 174 | condition: and(ne( variables['Agent.OS'], 'Windows_NT' ), ne(variables['python.version'],3.7)) 175 | 176 | - script: | 177 | pytest -m compile tests/test_parallel.py 178 | pytest -m "not compile" -n 2 --cocotbxml=test-cocotb.xml tests/test_parallel.py 179 | displayName: "Test with pytest-xdidt" 180 | condition: ne( variables['Agent.OS'], 'Windows_NT' ) 181 | 182 | - script: | 183 | cd tests; make 184 | displayName: "Test Makefile flow (Linux only)" 185 | condition: and( eq( variables['Agent.OS'], 'Linux' ), eq(variables['SIM'], 'icarus')) 186 | 187 | - task: PublishTestResults@2 188 | condition: succeededOrFailed() 189 | inputs: 190 | testResultsFiles: "test-*.xml" 191 | testRunTitle: "Test results for Python $(python.version) on $(imageName) with $(SIM)" 192 | -------------------------------------------------------------------------------- /cocotb_test/simulator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import tempfile 4 | import re 5 | import cocotb 6 | import logging 7 | import shutil 8 | from xml.etree import cElementTree as ET 9 | import threading 10 | import signal 11 | import warnings 12 | import find_libpython 13 | import asyncio 14 | import sysconfig 15 | from cocotb_test.compat import cocotb_2x_or_newer, cocotb_config 16 | 17 | _magic_re = re.compile(r"([\\{}])") 18 | _space_re = re.compile(r"([\s])", re.ASCII) 19 | 20 | 21 | def as_tcl_value(value): 22 | # add '\' before special characters and spaces 23 | value = _magic_re.sub(r"\\\1", value) 24 | value = value.replace("\n", r"\n") 25 | value = _space_re.sub(r"\\\1", value) 26 | if value[0] == '"': 27 | value = "\\" + value 28 | 29 | return value 30 | 31 | # Return `variable` if not None, else return `default` 32 | def some_or(variable, default): 33 | if variable is None: 34 | return default 35 | else: 36 | return variable 37 | 38 | class Simulator: 39 | def __init__( 40 | self, 41 | toplevel, 42 | module, 43 | work_dir=None, 44 | python_search=None, 45 | toplevel_lang="verilog", 46 | verilog_sources=None, 47 | vhdl_sources=None, 48 | includes=None, 49 | defines=None, 50 | parameters=None, 51 | compile_args=None, 52 | vhdl_compile_args=None, 53 | verilog_compile_args=None, 54 | sim_args=None, 55 | extra_args=None, 56 | plus_args=None, 57 | force_compile=False, 58 | testcase=None, 59 | sim_build="sim_build", 60 | seed=None, 61 | extra_env=None, 62 | compile_only=False, 63 | waves=None, 64 | timescale=None, 65 | gui=False, 66 | simulation_args=None, 67 | **kwargs, 68 | ): 69 | 70 | self.sim_dir = os.path.abspath(sim_build) 71 | os.makedirs(self.sim_dir, exist_ok=True) 72 | 73 | self.logger = logging.getLogger("cocotb") 74 | self.logger.setLevel(logging.INFO) 75 | logging.basicConfig(format="%(levelname)s %(name)s: %(message)s") 76 | 77 | warnings.simplefilter("always", DeprecationWarning) 78 | 79 | self.lib_dir = os.path.join(os.path.dirname(cocotb.__file__), "libs") 80 | 81 | self.lib_ext = "so" 82 | if os.name == "nt": 83 | self.lib_ext = "dll" 84 | 85 | self.module = module # TODO: Auto discovery, try introspect ? 86 | 87 | self.work_dir = self.sim_dir 88 | 89 | if work_dir is not None: 90 | absworkdir = os.path.abspath(work_dir) 91 | if os.path.isdir(absworkdir): 92 | self.work_dir = absworkdir 93 | 94 | # Initialize from arguments 95 | self.python_search = some_or(python_search, []) 96 | self.toplevel = toplevel 97 | self.toplevel_lang = toplevel_lang 98 | 99 | self.verilog_sources = self.get_abs_paths(some_or(verilog_sources, [])) 100 | self.vhdl_sources = self.get_abs_paths(some_or(vhdl_sources, [])) 101 | self.includes = self.get_abs_paths(some_or(includes, [])) 102 | self.defines = some_or(defines, []) 103 | self.parameters = some_or(parameters, {}) 104 | self.compile_args = some_or(compile_args, []) 105 | self.vhdl_compile_args = some_or(vhdl_compile_args, []) 106 | self.verilog_compile_args = some_or(verilog_compile_args, []) 107 | self.extra_args = some_or(extra_args, []) 108 | self.simulation_args = some_or(sim_args, []) 109 | self.plus_args = some_or(plus_args, []) 110 | self.force_compile = force_compile 111 | self.compile_only = compile_only 112 | self.waves = bool(some_or(waves, int(os.getenv("WAVES", 0)))) 113 | 114 | # by copy since we modify 115 | self.env = dict(extra_env) if extra_env is not None else {} 116 | 117 | if testcase is not None: 118 | self.env["COCOTB_TEST_FILTER" if cocotb_2x_or_newer else "TESTCASE"] = testcase 119 | 120 | if seed is not None: 121 | self.env["COCOTB_RANDOM_SEED" if cocotb_2x_or_newer else "RANDOM_SEED"] = str(seed) 122 | 123 | if timescale is None or re.fullmatch("\\d+[npu]?s/\\d+[npu]?s", timescale): 124 | self.timescale = timescale 125 | else: 126 | raise ValueError("Invalid timescale: {}".format(timescale)) 127 | 128 | # Backwards compatibility 129 | if simulation_args is not None: 130 | self.simulation_args += simulation_args 131 | warnings.warn( 132 | "Using simulation_args is deprecated. Please use sim_args instead.", 133 | DeprecationWarning, 134 | stacklevel=2, 135 | ) 136 | 137 | if kwargs: 138 | warnings.warn( 139 | "Using kwargs is deprecated. Please explicitly declare or arguments instead.", 140 | DeprecationWarning, 141 | stacklevel=2, 142 | ) 143 | for arg in kwargs: 144 | setattr(self, arg, kwargs[arg]) 145 | 146 | self.gui = gui 147 | 148 | # format sources and toplevel 149 | self.format_input() 150 | 151 | # Catch SIGINT and SIGTERM 152 | self.old_sigint_h = signal.getsignal(signal.SIGINT) 153 | self.old_sigterm_h = signal.getsignal(signal.SIGTERM) 154 | 155 | # works only if main thread 156 | if threading.current_thread() is threading.main_thread(): 157 | signal.signal(signal.SIGINT, self.exit_gracefully) 158 | signal.signal(signal.SIGTERM, self.exit_gracefully) 159 | 160 | self.process = None 161 | 162 | def set_env(self): 163 | 164 | for e in os.environ: 165 | self.env[e] = os.environ[e] 166 | 167 | self.env["LIBPYTHON_LOC"] = find_libpython.find_libpython() 168 | 169 | self.env["PATH"] += os.pathsep + self.lib_dir 170 | self.env["PYGPI_PYTHON_BIN"] = sys.executable 171 | 172 | self.env["PYTHONPATH"] = os.pathsep.join(sys.path) 173 | for path in self.python_search: 174 | self.env["PYTHONPATH"] += os.pathsep + path 175 | 176 | self.env["PYTHONHOME"] = sysconfig.get_config_var("prefix") 177 | 178 | self.env["COCOTB_TOPLEVEL" if cocotb_2x_or_newer else "TOPLEVEL"] = self.toplevel_module 179 | self.env["COCOTB_TEST_MODULES" if cocotb_2x_or_newer else "MODULE"] = self.module 180 | 181 | if not os.path.exists(self.sim_dir): 182 | os.makedirs(self.sim_dir) 183 | 184 | def build_command(self): 185 | raise NotImplementedError() 186 | 187 | def run(self): 188 | 189 | __tracebackhide__ = True # Hide the traceback when using PyTest. 190 | 191 | # use temporary results file 192 | if not os.getenv("COCOTB_RESULTS_FILE"): 193 | tmp_results_file = tempfile.NamedTemporaryFile( 194 | prefix=self.sim_dir + os.path.sep, suffix="_results.xml", delete=False 195 | ) 196 | results_xml_file = tmp_results_file.name 197 | tmp_results_file.close() 198 | self.env["COCOTB_RESULTS_FILE"] = results_xml_file 199 | else: 200 | results_xml_file = os.getenv("COCOTB_RESULTS_FILE") 201 | 202 | self.set_env() 203 | cmds = self.build_command() 204 | self.execute(cmds) 205 | 206 | failed = 0 207 | 208 | if not self.compile_only: 209 | results_file_exist = os.path.isfile(results_xml_file) 210 | if not results_file_exist: 211 | raise SystemExit("ERROR: Simulation terminated abnormally. Cocotb results file not found.") 212 | 213 | tree = ET.parse(results_xml_file) 214 | for ts in tree.iter("testsuite"): 215 | for tc in ts.iter("testcase"): 216 | for _ in tc.iter("failure"): 217 | self.logger.error(f'Failed: {tc.get("classname")}::{tc.get("name")}') 218 | failed += 1 219 | 220 | if failed: 221 | raise SystemExit(f"FAILED {failed} tests.") 222 | 223 | self.logger.info(f"Results file: {results_xml_file}") 224 | 225 | return results_xml_file 226 | 227 | def get_include_commands(self, includes): 228 | raise NotImplementedError() 229 | 230 | def get_define_commands(self, defines): 231 | raise NotImplementedError() 232 | 233 | def get_parameter_commands(self, parameters): 234 | raise NotImplementedError() 235 | 236 | def normalize_paths(self, paths): 237 | paths_abs = [] 238 | for path in paths: 239 | if os.path.isabs(path): 240 | paths_abs.append(os.path.abspath(path)) 241 | else: 242 | paths_abs.append(os.path.abspath(os.path.join(os.getcwd(), path))) 243 | return paths_abs 244 | 245 | def get_abs_paths(self, paths): 246 | if isinstance(paths, list): 247 | return self.normalize_paths(paths) 248 | else: 249 | libs = dict() 250 | for lib, src in paths.items(): 251 | libs[lib] = self.normalize_paths(src) 252 | return libs 253 | 254 | async def _log_pipe(self, level, stream): 255 | line = bytearray() 256 | while not stream.at_eof(): 257 | try: 258 | line.extend(await stream.readuntil()) 259 | except asyncio.IncompleteReadError as e: 260 | line.extend(e.partial) 261 | except asyncio.LimitOverrunError as e: 262 | line.extend(await stream.read(e.consumed)) 263 | continue 264 | 265 | if line: 266 | self.logger.log(level, line.decode("utf-8").rstrip()) 267 | line.clear() 268 | 269 | async def _exec(self, cmds): 270 | 271 | p = await asyncio.create_subprocess_exec( 272 | *cmds, 273 | stdout=asyncio.subprocess.PIPE, 274 | stderr=asyncio.subprocess.PIPE, 275 | cwd=self.work_dir, 276 | env=self.env, 277 | ) 278 | 279 | self.process = p 280 | 281 | await asyncio.wait( 282 | [ 283 | asyncio.create_task(self._log_pipe(logging.INFO, p.stdout)), 284 | asyncio.create_task(self._log_pipe(logging.ERROR, p.stderr)), 285 | ] 286 | ) 287 | 288 | await p.wait() 289 | 290 | def execute(self, cmds): 291 | 292 | __tracebackhide__ = True # Hide the traceback when using PyTest. 293 | 294 | for cmd in cmds: 295 | self.logger.info("Running command: " + " ".join(cmd)) 296 | 297 | asyncio.run(self._exec(cmd)) 298 | 299 | if self.process.returncode: 300 | raise SystemExit(f"Process {cmd[0]} terminated with error {self.process.returncode}") 301 | 302 | self.process = None 303 | 304 | def outdated_list(self, output, dependencies): 305 | 306 | if not os.path.isfile(output): 307 | return True 308 | 309 | output_mtime = os.path.getmtime(output) 310 | 311 | dep_mtime = 0 312 | for file in dependencies: 313 | mtime = os.path.getmtime(file) 314 | if mtime > dep_mtime: 315 | dep_mtime = mtime 316 | 317 | if dep_mtime > output_mtime: 318 | return True 319 | 320 | return False 321 | 322 | def outdated(self, output, dependencies): 323 | if isinstance(dependencies, list): 324 | return self.outdated_list(output, dependencies) 325 | 326 | for sources in dependencies.values(): 327 | if self.outdated_list(output, sources): 328 | return True 329 | 330 | return False 331 | 332 | def exit_gracefully(self, signum, frame): 333 | pid = None 334 | if self.process is not None: 335 | pid = self.process.pid 336 | self.process.kill() 337 | self.process.wait() 338 | # Restore previous handlers 339 | signal.signal(signal.SIGINT, self.old_sigint_h) 340 | signal.signal(signal.SIGTERM, self.old_sigterm_h) 341 | assert False, f"Exiting pid: {str(pid)} with signum: {str(signum)}" 342 | 343 | @property 344 | def toplevel_module_list(self): 345 | """Return list of toplevel module names""" 346 | return [top.rsplit(".", 1)[-1] for top in self.toplevel] 347 | 348 | @property 349 | def toplevel_library_list(self): 350 | """Return list of library names of toplevel modules""" 351 | assert all(["." in top for top in self.toplevel]), "`self.toplevel` does not yet contain library information." 352 | return [top.split(".", 1)[0] for top in self.toplevel] 353 | 354 | @property 355 | def toplevel_module(self): 356 | """Return name of first toplevel module""" 357 | return self.toplevel_module_list[0] 358 | 359 | @property 360 | def toplevel_library(self): 361 | """Return name of library of first toplevel""" 362 | return self.toplevel_library_list[0] 363 | 364 | @property 365 | def vhdl_sources_flat(self): 366 | if not self.vhdl_sources: 367 | return [] 368 | assert len(self.vhdl_sources) == 1, "Flattened sources only available when using a single library name." 369 | return list(self.vhdl_sources.values())[0] 370 | 371 | @property 372 | def verilog_sources_flat(self): 373 | if not self.verilog_sources: 374 | return [] 375 | assert len(self.verilog_sources) == 1, "Flattened sources only available when using a single library name." 376 | return list(self.verilog_sources.values())[0] 377 | 378 | def format_input(self): 379 | """Format sources and toplevel strings.""" 380 | 381 | if not isinstance(self.toplevel, list): 382 | self.toplevel = [self.toplevel] 383 | 384 | if self.vhdl_sources: 385 | if isinstance(self.vhdl_sources, list): 386 | # create named library with first toplevel module as its name 387 | self.vhdl_sources = {f"{self.toplevel_module}": self.vhdl_sources} 388 | 389 | if self.verilog_sources: 390 | if isinstance(self.verilog_sources, list): 391 | # create named library with toplevel module as its name 392 | self.verilog_sources = {f"{self.toplevel_module}": self.verilog_sources} 393 | 394 | # format toplevel as `.`, if lib was not given 395 | for i, top in enumerate(self.toplevel): 396 | if not "." in top: 397 | self.toplevel[i] = ".".join((self.toplevel_module, top)) 398 | 399 | 400 | class Icarus(Simulator): 401 | def __init__(self, *argv, **kwargs): 402 | super().__init__(*argv, **kwargs) 403 | 404 | if self.vhdl_sources: 405 | raise ValueError("This simulator does not support VHDL") 406 | 407 | self.sim_file = os.path.join(self.sim_dir, f"{self.toplevel_module}.vvp") 408 | 409 | def get_include_commands(self, includes): 410 | return [f"-I{dir}" for dir in includes] 411 | 412 | def get_define_commands(self, defines): 413 | return [f"-D{define}" for define in defines] 414 | 415 | def get_parameter_commands(self, parameters): 416 | return [f"-P{self.toplevel_module}.{name}={str(value)}" for name, value in parameters.items()] 417 | 418 | def compile_command(self): 419 | 420 | compile_args = self.compile_args + self.extra_args + self.verilog_compile_args 421 | 422 | toplevel = [] 423 | for t in self.toplevel_module_list: 424 | toplevel += ["-s", t] 425 | 426 | cmd_compile = ( 427 | [ 428 | "iverilog", 429 | "-o", 430 | self.sim_file, 431 | "-D", 432 | "COCOTB_SIM=1", 433 | "-g2012", 434 | ] 435 | + toplevel 436 | + self.get_define_commands(self.defines) 437 | + self.get_include_commands(self.includes) 438 | + self.get_parameter_commands(self.parameters) 439 | + compile_args 440 | + self.verilog_sources_flat 441 | ) 442 | 443 | return cmd_compile 444 | 445 | def run_command(self): 446 | return ( 447 | ["vvp", "-M", self.lib_dir, "-m", cocotb_config.lib_name("vpi", "icarus")] 448 | + self.simulation_args 449 | + [self.sim_file] 450 | + self.plus_args 451 | ) 452 | 453 | def build_command(self): 454 | verilog_sources = self.verilog_sources_flat.copy() 455 | if self.waves: 456 | dump_mod_name = "iverilog_dump" 457 | dump_file_name = self.toplevel_module + ".fst" 458 | dump_mod_file_name = os.path.join(self.sim_dir, dump_mod_name + ".v") 459 | 460 | if not os.path.exists(dump_mod_file_name): 461 | with open(dump_mod_file_name, "w") as f: 462 | f.write("module iverilog_dump();\n") 463 | f.write("initial begin\n") 464 | f.write(' $dumpfile("%s");\n' % dump_file_name) 465 | f.write(" $dumpvars(0, %s);\n" % self.toplevel_module) 466 | f.write("end\n") 467 | f.write("endmodule\n") 468 | 469 | first_lib_src = next(iter(self.verilog_sources.items()))[1] 470 | first_lib_src += [dump_mod_file_name] 471 | 472 | self.compile_args.extend(["-s", dump_mod_name]) 473 | self.plus_args.append("-fst") 474 | 475 | if self.timescale: 476 | timescale_cmd_file = os.path.join(self.sim_dir, "timescale.f") 477 | with open(timescale_cmd_file, "w") as f: 478 | f.write(f"+timescale+{self.timescale}\n") 479 | self.compile_args.extend(["-f", timescale_cmd_file]) 480 | 481 | cmd = [] 482 | if self.outdated(self.sim_file, verilog_sources) or self.force_compile: 483 | cmd.append(self.compile_command()) 484 | else: 485 | self.logger.warning(f"Skipping compilation:{self.sim_file}") 486 | 487 | # TODO: check dependency? 488 | if not self.compile_only: 489 | cmd.append(self.run_command()) 490 | 491 | return cmd 492 | 493 | 494 | class Questa(Simulator): 495 | def get_include_commands(self, includes): 496 | return [f"+incdir+{as_tcl_value(dir)}" for dir in includes] 497 | 498 | def get_define_commands(self, defines): 499 | return [f"+define+{as_tcl_value(define)}" for define in defines] 500 | 501 | def get_parameter_commands(self, parameters): 502 | return [f"-g{name}={str(value)}" for name, value in parameters.items()] 503 | 504 | def do_script(self): 505 | do_script = "" 506 | if self.waves: 507 | do_script += "log -recursive /*;" 508 | if not self.gui: 509 | do_script += "run -all; quit" 510 | return do_script 511 | 512 | def build_command(self): 513 | 514 | cmd = [] 515 | 516 | do_script = self.do_script() 517 | 518 | if self.vhdl_sources: 519 | compile_args = self.compile_args + self.extra_args + self.vhdl_compile_args 520 | 521 | for lib, src in self.vhdl_sources.items(): 522 | cmd.append(["vlib", as_tcl_value(lib)]) 523 | cmd.append( 524 | ["vcom"] 525 | + ["-work", as_tcl_value(lib)] 526 | + compile_args 527 | + [as_tcl_value(v) for v in src] 528 | ) 529 | 530 | if self.verilog_sources: 531 | compile_args = self.compile_args + self.extra_args + self.verilog_compile_args 532 | if self.timescale: 533 | compile_args += ["-timescale", self.timescale] 534 | 535 | for lib, src in self.verilog_sources.items(): 536 | cmd.append(["vlib", as_tcl_value(lib)]) 537 | cmd.append( 538 | ["vlog"] 539 | + ([] if self.force_compile else ["-incr"]) 540 | + ["-work", as_tcl_value(lib)] 541 | + ["+define+COCOTB_SIM"] 542 | + ["-sv"] 543 | + self.get_define_commands(self.defines) 544 | + self.get_include_commands(self.includes) 545 | + compile_args 546 | + [as_tcl_value(v) for v in src] 547 | ) 548 | 549 | if not self.compile_only: 550 | if self.toplevel_lang == "vhdl": 551 | cmd.append( 552 | ["vsim"] 553 | + ["-gui" if self.gui else "-c"] 554 | + ["-onfinish", "stop" if self.gui else "exit"] 555 | + [ 556 | "-foreign", 557 | "cocotb_init " + as_tcl_value(cocotb_config.lib_name_path("fli", "questa")), 558 | ] 559 | + self.simulation_args 560 | + [as_tcl_value(v) for v in self.get_parameter_commands(self.parameters)] 561 | + self.toplevel 562 | + ["-do", do_script] 563 | ) 564 | if self.verilog_sources: 565 | self.env["GPI_EXTRA"] = str(cocotb_config.lib_name_path("vpi", "questa")) + ":cocotbvpi_entry_point" 566 | else: 567 | cmd.append( 568 | ["vsim"] 569 | + ["-gui" if self.gui else "-c"] 570 | + ["-onfinish", "stop" if self.gui else "exit"] 571 | + [ 572 | "-pli", 573 | as_tcl_value(cocotb_config.lib_name_path("vpi", "questa")), 574 | ] 575 | + self.simulation_args 576 | + [as_tcl_value(v) for v in self.get_parameter_commands(self.parameters)] 577 | + self.toplevel 578 | + [as_tcl_value(v) for v in self.plus_args] 579 | + ["-do", do_script] 580 | ) 581 | if self.vhdl_sources: 582 | self.env["GPI_EXTRA"] = str(cocotb_config.lib_name_path("fli", "questa")) + ":cocotbfli_entry_point" 583 | 584 | return cmd 585 | 586 | 587 | class Modelsim(Questa): 588 | # Understood to be the same as Questa - for now. 589 | pass 590 | 591 | 592 | class Ius(Simulator): 593 | def __init__(self, *argv, **kwargs): 594 | super().__init__(*argv, **kwargs) 595 | 596 | self.env["GPI_EXTRA"] = str(cocotb_config.lib_name_path("vhpi", "ius")) + ":cocotbvhpi_entry_point" 597 | 598 | def get_include_commands(self, includes): 599 | include_cmd = [] 600 | for dir in includes: 601 | include_cmd += ["-incdir", dir] 602 | 603 | return include_cmd 604 | 605 | def get_define_commands(self, defines): 606 | defines_cmd = [] 607 | for define in defines: 608 | defines_cmd += ["-define", define] 609 | 610 | return defines_cmd 611 | 612 | def get_parameter_commands(self, parameters): 613 | parameters_cmd = [] 614 | for name, value in parameters.items(): 615 | if self.toplevel_lang == "vhdl": 616 | parameters_cmd.append("-generic") 617 | parameters_cmd.append('"' + self.toplevel_module + "." + name + "=>" + str(value) + '"') 618 | else: 619 | parameters_cmd.append("-defparam") 620 | parameters_cmd.append('"' + self.toplevel_module + "." + name + "=" + str(value) + '"') 621 | 622 | return parameters_cmd 623 | 624 | def build_command(self): 625 | 626 | assert ( 627 | not self.verilog_compile_args and not self.vhdl_compile_args 628 | ), "'Ius' simulator does not allow HDL specific compile arguments." 629 | 630 | out_file = os.path.join(self.sim_dir, "INCA_libs", "history") 631 | 632 | cmd = [] 633 | 634 | if self.outdated(out_file, self.verilog_sources_flat + self.vhdl_sources_flat) or self.force_compile: 635 | cmd_elab = ( 636 | [ 637 | "irun", 638 | "-64", 639 | "-elaborate", 640 | "-define", 641 | "COCOTB_SIM=1", 642 | "-loadvpi", 643 | str(cocotb_config.lib_name_path("vpi", "ius")) + ":vlog_startup_routines_bootstrap", 644 | "-plinowarn", 645 | "-access", 646 | "+rwc", 647 | "-top", 648 | self.toplevel_module, 649 | ] 650 | + self.get_define_commands(self.defines) 651 | + self.get_include_commands(self.includes) 652 | + self.get_parameter_commands(self.parameters) 653 | + self.compile_args + self.extra_args 654 | + self.verilog_sources_flat 655 | + self.vhdl_sources_flat 656 | ) 657 | cmd.append(cmd_elab) 658 | 659 | else: 660 | self.logger.warning("Skipping compilation:" + out_file) 661 | 662 | if not self.compile_only: 663 | cmd_run = ( 664 | ["irun", "-64", "-R", ("-gui" if self.gui else "")] 665 | + self.simulation_args 666 | + self.get_parameter_commands(self.parameters) 667 | + self.plus_args 668 | ) 669 | cmd.append(cmd_run) 670 | 671 | return cmd 672 | 673 | 674 | class Xcelium(Simulator): 675 | def __init__(self, *argv, **kwargs): 676 | super().__init__(*argv, **kwargs) 677 | 678 | self.env["GPI_EXTRA"] = str(cocotb_config.lib_name_path("vhpi", "xcelium")) + ":cocotbvhpi_entry_point" 679 | 680 | def get_include_commands(self, includes): 681 | include_cmd = [] 682 | for dir in includes: 683 | include_cmd += ["-incdir", dir] 684 | 685 | return include_cmd 686 | 687 | def get_define_commands(self, defines): 688 | defines_cmd = [] 689 | for define in defines: 690 | defines_cmd += ["-define", define] 691 | 692 | return defines_cmd 693 | 694 | def get_parameter_commands(self, parameters): 695 | parameters_cmd = [] 696 | for name, value in parameters.items(): 697 | if self.toplevel_lang == "vhdl": 698 | parameters_cmd.extend(("-generic", '"' + self.toplevel_module + "." + name + "=>" + str(value) + '"')) 699 | 700 | else: 701 | parameters_cmd.extend(("-defparam", '"' + self.toplevel_module + "." + name + "=" + str(value) + '"')) 702 | 703 | return parameters_cmd 704 | 705 | def build_command(self): 706 | 707 | assert ( 708 | not self.verilog_compile_args and not self.vhdl_compile_args 709 | ), "'Xcelium' simulator does not allow HDL specific compile arguments." 710 | 711 | out_file = os.path.join(self.sim_dir, "INCA_libs", "history") 712 | 713 | cmd = [] 714 | 715 | if self.outdated(out_file, self.verilog_sources_flat + self.vhdl_sources_flat) or self.force_compile: 716 | 717 | cmd_elab = ( 718 | [ 719 | "xrun", 720 | "-64", 721 | "-elaborate", 722 | "-define", 723 | "COCOTB_SIM=1", 724 | "-loadvpi", 725 | str(cocotb_config.lib_name_path("vpi", "xcelium")) + ":vlog_startup_routines_bootstrap", 726 | "-plinowarn", 727 | "-access", 728 | "+rwc", 729 | "-top", 730 | self.toplevel_module, 731 | ] 732 | + self.get_define_commands(self.defines) 733 | + self.get_include_commands(self.includes) 734 | + self.get_parameter_commands(self.parameters) 735 | + self.compile_args + self.extra_args 736 | + self.verilog_sources_flat 737 | + self.vhdl_sources_flat 738 | ) 739 | if self.timescale: 740 | cmd_elab += ["-timescale", self.timescale] 741 | 742 | cmd.append(cmd_elab) 743 | 744 | else: 745 | self.logger.warning(f"Skipping compilation:{out_file}") 746 | 747 | if not self.compile_only: 748 | cmd_run = ( 749 | ["xrun", "-64", "-R", ("-gui" if self.gui else "")] 750 | + self.simulation_args 751 | + self.get_parameter_commands(self.parameters) 752 | + self.plus_args 753 | ) 754 | cmd.append(cmd_run) 755 | 756 | return cmd 757 | 758 | 759 | class Vcs(Simulator): 760 | def get_include_commands(self, includes): 761 | return [f"+incdir+{dir}" for dir in includes] 762 | 763 | def get_define_commands(self, defines): 764 | return [f"+define+{define}" for define in defines] 765 | 766 | def get_parameter_commands(self, parameters): 767 | return [f"-pvalue+{self.toplevel_module}/{name}={str(value)}" for name, value in parameters.items()] 768 | 769 | def build_command(self): 770 | 771 | pli_cmd = "acc+=rw,wn:*" 772 | 773 | cmd = [] 774 | 775 | do_file_path = os.path.join(self.sim_dir, "pli.tab") 776 | with open(do_file_path, "w") as pli_file: 777 | pli_file.write(pli_cmd) 778 | 779 | compile_args = self.compile_args + self.extra_args + self.verilog_compile_args 780 | debug_access = "-debug_access" 781 | if self.waves: 782 | debug_access += "+all+dmptf" 783 | compile_args += ["-kdb", "-debug_region+cell"] 784 | 785 | simv_path = os.path.join(self.sim_dir, self.module) 786 | cmd_build = ( 787 | [ 788 | "vcs", 789 | "-full64", 790 | "-sverilog", 791 | debug_access, 792 | "-P", 793 | "pli.tab", 794 | "+define+COCOTB_SIM=1", 795 | "-load", 796 | str(cocotb_config.lib_name_path("vpi", "vcs")), 797 | "-top", 798 | self.toplevel_module, 799 | ] 800 | + self.get_define_commands(self.defines) 801 | + self.get_include_commands(self.includes) 802 | + self.get_parameter_commands(self.parameters) 803 | + compile_args 804 | + self.verilog_sources_flat 805 | + ["-o", simv_path] 806 | ) 807 | if self.timescale: 808 | cmd_build += [f"-timescale={self.timescale}"] 809 | else: 810 | cmd_build += [f"-timescale=1ns/1ps"] 811 | cmd.append(cmd_build) 812 | 813 | if not self.compile_only: 814 | cmd_run = [ 815 | simv_path, 816 | "+define+COCOTB_SIM=1", 817 | ] + self.simulation_args 818 | if self.waves: 819 | ucli_do = os.path.join(self.sim_dir, f"{self.module}_ucli.do") 820 | with open(ucli_do, "w") as f: 821 | f.write(f"fsdbDumpfile {simv_path}.fsdb; fsdbDumpvars 0 {self.toplevel_module}; run; quit;") 822 | cmd_run += ["+fsdb+all=on", "-ucli", "-do", ucli_do] 823 | if self.gui: 824 | cmd_run.append("-gui") 825 | cmd.append(cmd_run) 826 | 827 | return cmd 828 | 829 | 830 | class Ghdl(Simulator): 831 | def get_parameter_commands(self, parameters): 832 | return [f"-g{name}={str(value)}" for name, value in parameters.items()] 833 | 834 | def build_command(self): 835 | 836 | ghdl_exec = shutil.which("ghdl") 837 | if ghdl_exec is None: 838 | raise ValueError("GHDL executable not found.") 839 | 840 | cmd = [] 841 | 842 | out_file = os.path.join(self.sim_dir, self.toplevel_module) 843 | compile_args = self.compile_args + self.extra_args + self.vhdl_compile_args 844 | 845 | if self.outdated(out_file, self.vhdl_sources) or self.force_compile: 846 | for lib, src in self.vhdl_sources.items(): 847 | cmd.append(["ghdl", "-i"] + compile_args + [f"--work={lib}"] + src) 848 | 849 | cmd_elaborate = ["ghdl", "-m", f"--work={self.toplevel_library}"] + compile_args + [self.toplevel_module] 850 | cmd.append(cmd_elaborate) 851 | 852 | if self.waves: 853 | self.simulation_args.append(f"--wave={self.toplevel_module}.ghw") 854 | 855 | self.env["PATH"] += os.pathsep + os.path.join(os.path.dirname(os.path.dirname(ghdl_exec)), "lib") 856 | 857 | cmd_run = ( 858 | ["ghdl", "-r", f"--work={self.toplevel_library}"] 859 | + compile_args 860 | + [self.toplevel_module] 861 | + ["--vpi=" + str(cocotb_config.lib_name_path("vpi", "ghdl"))] 862 | + self.simulation_args 863 | + self.get_parameter_commands(self.parameters) 864 | ) 865 | 866 | if not self.compile_only: 867 | cmd.append(cmd_run) 868 | 869 | return cmd 870 | 871 | 872 | class Nvc(Simulator): 873 | def get_parameter_commands(self, parameters): 874 | args = [] 875 | for name, value in parameters.items(): 876 | args += ["-g", f"{name}={str(value)}"] 877 | return args 878 | 879 | def build_command(self): 880 | 881 | nvc_exec = shutil.which("nvc") 882 | if nvc_exec is None: 883 | raise ValueError("NVC executable not found.") 884 | 885 | cmd = [] 886 | 887 | out_file = os.path.join(self.sim_dir, self.toplevel_module) 888 | compile_args = self.compile_args + self.vhdl_compile_args 889 | if self.outdated(out_file, self.vhdl_sources) or self.force_compile: 890 | for lib, src in self.vhdl_sources.items(): 891 | cmd.append(["nvc"] + self.extra_args + [f"--work={lib}", "-L", self.sim_dir, "-a"] + compile_args + src) 892 | 893 | cmd_elaborate = ["nvc"] + self.extra_args + [f"--work={self.toplevel_library}", "-L", self.sim_dir, "-e"] + compile_args + self.get_parameter_commands(self.parameters) + [self.toplevel_module] 894 | cmd.append(cmd_elaborate) 895 | 896 | if self.waves: 897 | self.simulation_args.append(f"--wave={self.toplevel_module}.fst") 898 | 899 | self.env["PATH"] += os.pathsep + os.path.join(os.path.dirname(os.path.dirname(nvc_exec)), "lib") 900 | 901 | cmd_run = ( 902 | ["nvc", f"--work={self.toplevel_library}"] 903 | + self.extra_args 904 | + ["-L", self.sim_dir, "--stderr=error"] 905 | + ["-r"] 906 | + [self.toplevel_module] 907 | + ["--load", str(cocotb_config.lib_name_path("vhpi", "nvc"))] 908 | + self.simulation_args 909 | ) 910 | 911 | if not self.compile_only: 912 | cmd.append(cmd_run) 913 | 914 | return cmd 915 | 916 | 917 | class Riviera(Simulator): 918 | def get_include_commands(self, includes): 919 | return ["+incdir+" + as_tcl_value(dir) for dir in includes] 920 | 921 | def get_define_commands(self, defines): 922 | return ["+define+" + as_tcl_value(define) for define in defines] 923 | 924 | def get_parameter_commands(self, parameters): 925 | return ["-g" + name + "=" + str(value) for name, value in parameters.items()] 926 | 927 | def build_command(self): 928 | 929 | self.rtl_library = self.toplevel_module 930 | 931 | do_script = "\nonerror {\n quit -code 1 \n} \n" 932 | 933 | out_file = os.path.join(self.sim_dir, self.rtl_library, self.rtl_library + ".lib") 934 | 935 | if self.outdated(out_file, self.verilog_sources_flat + self.vhdl_sources_flat) or self.force_compile: 936 | 937 | do_script += f"alib {as_tcl_value(self.rtl_library)} \n" 938 | 939 | if self.vhdl_sources: 940 | compile_args = self.compile_args + self.extra_args + self.vhdl_compile_args 941 | do_script += "acom -work {RTL_LIBRARY} {EXTRA_ARGS} {VHDL_SOURCES}\n".format( 942 | RTL_LIBRARY=as_tcl_value(self.rtl_library), 943 | VHDL_SOURCES=" ".join(as_tcl_value(v) for v in self.vhdl_sources_flat), 944 | EXTRA_ARGS=" ".join(as_tcl_value(v) for v in compile_args), 945 | ) 946 | 947 | if self.verilog_sources: 948 | compile_args = self.compile_args + self.extra_args + self.verilog_compile_args 949 | do_script += "alog -work {RTL_LIBRARY} +define+COCOTB_SIM -sv {DEFINES} {INCDIR} {EXTRA_ARGS} {VERILOG_SOURCES} \n".format( 950 | RTL_LIBRARY=as_tcl_value(self.rtl_library), 951 | VERILOG_SOURCES=" ".join(as_tcl_value(v) for v in self.verilog_sources_flat), 952 | DEFINES=" ".join(self.get_define_commands(self.defines)), 953 | INCDIR=" ".join(self.get_include_commands(self.includes)), 954 | EXTRA_ARGS=" ".join(as_tcl_value(v) for v in compile_args), 955 | ) 956 | else: 957 | self.logger.warning("Skipping compilation:" + out_file) 958 | 959 | if not self.compile_only: 960 | if self.toplevel_lang == "vhdl": 961 | do_script += "asim +access +w -interceptcoutput -O2 -loadvhpi {EXT_NAME} {EXTRA_ARGS} {RTL_LIBRARY}.{TOPLEVEL} \n".format( 962 | RTL_LIBRARY=as_tcl_value(self.rtl_library), 963 | TOPLEVEL=as_tcl_value(self.toplevel_module), 964 | EXT_NAME=as_tcl_value(cocotb_config.lib_name_path("vhpi", "riviera") + ":vhpi_startup_routines_bootstrap"), 965 | EXTRA_ARGS=" ".join( 966 | as_tcl_value(v) for v in (self.simulation_args + self.get_parameter_commands(self.parameters)) 967 | ), 968 | ) 969 | if self.verilog_sources: 970 | self.env["GPI_EXTRA"] = str(cocotb_config.lib_name_path("vpi", "riviera")) + ":cocotbvpi_entry_point" 971 | else: 972 | do_script += "asim +access +w -interceptcoutput -O2 -pli {EXT_NAME} {EXTRA_ARGS} {RTL_LIBRARY}.{TOPLEVEL} {PLUS_ARGS} \n".format( 973 | RTL_LIBRARY=as_tcl_value(self.rtl_library), 974 | TOPLEVEL=as_tcl_value(self.toplevel_module), 975 | EXT_NAME=as_tcl_value(cocotb_config.lib_name_path("vpi", "riviera")), 976 | EXTRA_ARGS=" ".join( 977 | as_tcl_value(v) for v in (self.simulation_args + self.get_parameter_commands(self.parameters)) 978 | ), 979 | PLUS_ARGS=" ".join(as_tcl_value(v) for v in self.plus_args), 980 | ) 981 | if self.vhdl_sources: 982 | self.env["GPI_EXTRA"] = str(cocotb_config.lib_name_path("vhpi", "riviera")) + ":cocotbvhpi_entry_point" 983 | 984 | if self.waves: 985 | do_script += "trace -recursive /*;" 986 | 987 | if not self.gui: 988 | do_script += "run -all \nexit" 989 | 990 | do_file = tempfile.NamedTemporaryFile(delete=False) 991 | do_file.write(do_script.encode()) 992 | do_file.close() 993 | 994 | command = "riviera" if self.gui else "vsimsa" 995 | return [[command] + ["-do"] + ["do"] + [do_file.name]] 996 | 997 | 998 | class Activehdl(Simulator): 999 | def get_include_commands(self, includes): 1000 | return [f"+incdir+{as_tcl_value(dir)}" for dir in includes] 1001 | 1002 | def get_define_commands(self, defines): 1003 | return [f"+define+{as_tcl_value(define)}" for define in defines] 1004 | 1005 | def get_parameter_commands(self, parameters): 1006 | return [f"-g{name}={str(value)}" for name, value in parameters.items()] 1007 | 1008 | def build_script_compile(self): 1009 | do_script = "" 1010 | 1011 | out_file = os.path.join(self.sim_dir, self.rtl_library, f"{self.rtl_library}.lib") 1012 | 1013 | if self.outdated(out_file, self.verilog_sources_flat + self.vhdl_sources_flat) or self.force_compile: 1014 | 1015 | do_script += f"alib {as_tcl_value(self.rtl_library)} \n" 1016 | 1017 | if self.vhdl_sources: 1018 | compile_args = self.compile_args + self.extra_args + self.vhdl_compile_args 1019 | do_script += "acom -work {RTL_LIBRARY} {EXTRA_ARGS} {VHDL_SOURCES}\n".format( 1020 | RTL_LIBRARY=as_tcl_value(self.rtl_library), 1021 | VHDL_SOURCES=" ".join(as_tcl_value(v) for v in self.vhdl_sources_flat), 1022 | EXTRA_ARGS=" ".join(as_tcl_value(v) for v in compile_args), 1023 | ) 1024 | 1025 | if self.verilog_sources: 1026 | compile_args = self.compile_args + self.extra_args + self.verilog_compile_args 1027 | do_script += "alog {RTL_LIBRARY} +define+COCOTB_SIM -sv {DEFINES} {INCDIR} {EXTRA_ARGS} {VERILOG_SOURCES} \n".format( 1028 | RTL_LIBRARY=as_tcl_value(self.rtl_library), 1029 | VERILOG_SOURCES=" ".join(as_tcl_value(v) for v in self.verilog_sources_flat), 1030 | DEFINES=" ".join(self.get_define_commands(self.defines)), 1031 | INCDIR=" ".join(self.get_include_commands(self.includes)), 1032 | EXTRA_ARGS=" ".join(as_tcl_value(v) for v in compile_args), 1033 | ) 1034 | else: 1035 | self.logger.warning(f"Skipping compilation:{out_file}") 1036 | 1037 | return do_script 1038 | 1039 | def build_script_sim(self): 1040 | 1041 | do_script = "" 1042 | 1043 | if self.toplevel_lang == "vhdl": 1044 | do_script += f"set worklib {as_tcl_value(self.rtl_library)}" + "\n" 1045 | do_script += ( 1046 | 'asim +access +w -interceptcoutput -O2 -loadvhpi "{EXT_NAME}" {EXTRA_ARGS} {TOPLEVEL} \n'.format( 1047 | RTL_LIBRARY=as_tcl_value(self.rtl_library), 1048 | TOPLEVEL=as_tcl_value(self.toplevel_module), 1049 | EXT_NAME=as_tcl_value( 1050 | cocotb_config.lib_name_path("vhpi", "activehdl") + ":vhpi_startup_routines_bootstrap" 1051 | ), 1052 | EXTRA_ARGS=" ".join(self.simulation_args + self.get_parameter_commands(self.parameters)), 1053 | ) 1054 | ) 1055 | if self.verilog_sources: 1056 | self.env["GPI_EXTRA"] = str(cocotb_config.lib_name_path("vpi", "activehdl")) + ":cocotbvpi_entry_point" 1057 | else: 1058 | do_script += 'asim +access +w -interceptcoutput -O2 -pli "{EXT_NAME}" {EXTRA_ARGS} {RTL_LIBRARY}.{TOPLEVEL} {PLUS_ARGS} \n'.format( 1059 | RTL_LIBRARY=as_tcl_value(self.rtl_library), 1060 | TOPLEVEL=as_tcl_value(self.toplevel_module), 1061 | EXT_NAME=as_tcl_value(cocotb_config.lib_name_path("vpi", "activehdl")), 1062 | EXTRA_ARGS=" ".join( 1063 | as_tcl_value(v) for v in (self.simulation_args + self.get_parameter_commands(self.parameters)) 1064 | ), 1065 | PLUS_ARGS=" ".join(as_tcl_value(v) for v in self.plus_args), 1066 | ) 1067 | if self.vhdl_sources: 1068 | self.env["GPI_EXTRA"] = str(cocotb_config.lib_name_path("vhpi", "activehdl")) + ":cocotbvpi_entry_point" 1069 | 1070 | if self.waves: 1071 | do_script += "trace -recursive /*;" 1072 | 1073 | return do_script 1074 | 1075 | def build_script_run(self): 1076 | 1077 | return "run -all \nexit" 1078 | 1079 | def build_command(self): 1080 | 1081 | self.rtl_library = self.toplevel_module 1082 | 1083 | do_script = "\nonerror {\n quit -code 1 \n} \n" 1084 | 1085 | do_script += self.build_script_compile() 1086 | if not self.compile_only: 1087 | do_script += self.build_script_sim() 1088 | do_script += self.build_script_run() 1089 | 1090 | do_file = tempfile.NamedTemporaryFile(delete=False) 1091 | do_file.write(do_script.encode()) 1092 | do_file.close() 1093 | 1094 | return [["vsimsa"] + ["-do"] + [do_file.name]] 1095 | 1096 | 1097 | class Verilator(Simulator): 1098 | def __init__(self, make_args=None, *argv, **kwargs): 1099 | super().__init__(*argv, **kwargs) 1100 | 1101 | if make_args is None: 1102 | make_args = [] 1103 | 1104 | self.make_args = make_args 1105 | 1106 | if self.vhdl_sources: 1107 | raise ValueError("This simulator does not support VHDL") 1108 | 1109 | def get_include_commands(self, includes): 1110 | return [f"-I{dir}" for dir in includes] 1111 | 1112 | def get_define_commands(self, defines): 1113 | return [f"-D{define}" for define in defines] 1114 | 1115 | def get_parameter_commands(self, parameters): 1116 | return [f"-G{name}={str(value)}" for name, value in parameters.items()] 1117 | 1118 | def build_command(self): 1119 | 1120 | cmd = [] 1121 | 1122 | out_file = os.path.join(self.sim_dir, self.toplevel_module) 1123 | verilator_cpp = os.path.join( 1124 | os.path.dirname(cocotb.__file__), 1125 | "share", 1126 | "lib", 1127 | "verilator", 1128 | "verilator.cpp", 1129 | ) 1130 | 1131 | verilator_exec = shutil.which("verilator") 1132 | if verilator_exec is None: 1133 | raise ValueError("Verilator executable not found.") 1134 | 1135 | compile_args = self.compile_args + self.extra_args + self.verilog_compile_args 1136 | 1137 | if self.waves: 1138 | compile_args += ["--trace-fst", "--trace-structs"] 1139 | 1140 | if self.timescale: 1141 | compile_args += ["--timescale", self.timescale] 1142 | 1143 | cmd.append( 1144 | [ 1145 | "perl", 1146 | verilator_exec, 1147 | "-cc", 1148 | "--exe", 1149 | "-Mdir", 1150 | self.sim_dir, 1151 | "-DCOCOTB_SIM=1", 1152 | "--top-module", 1153 | self.toplevel_module, 1154 | "--vpi", 1155 | "--public-flat-rw", 1156 | "--prefix", 1157 | "Vtop", 1158 | "-o", 1159 | self.toplevel_module, 1160 | "-LDFLAGS", 1161 | "-Wl,-rpath,{LIB_DIR} -L{LIB_DIR} -lcocotbvpi_verilator".format(LIB_DIR=self.lib_dir), 1162 | ] 1163 | + compile_args 1164 | + self.get_define_commands(self.defines) 1165 | + self.get_include_commands(self.includes) 1166 | + self.get_parameter_commands(self.parameters) 1167 | + [verilator_cpp] 1168 | + self.verilog_sources_flat 1169 | ) 1170 | 1171 | cmd.append(["make", "-C", self.sim_dir, "-f", "Vtop.mk"] + self.make_args) 1172 | 1173 | if not self.compile_only: 1174 | cmd.append([out_file] + self.plus_args) 1175 | 1176 | return cmd 1177 | 1178 | 1179 | def run(simulator=None, **kwargs): 1180 | 1181 | __tracebackhide__ = True # Hide the traceback when using PyTest. 1182 | 1183 | sim_env = os.getenv("SIM") 1184 | 1185 | # priority, highest first, is: env, kwarg, "icarus" 1186 | if sim_env is None: 1187 | sim_env = "icarus" if simulator is None else simulator 1188 | elif simulator is not None: 1189 | logging.warning(f"'SIM={sim_env}' overrides kwarg 'simulator={simulator}'") 1190 | 1191 | supported_sim = [ 1192 | "icarus", 1193 | "questa", 1194 | "modelsim", 1195 | "ius", 1196 | "xcelium", 1197 | "vcs", 1198 | "ghdl", 1199 | "nvc", 1200 | "riviera", 1201 | "activehdl", 1202 | "verilator", 1203 | ] 1204 | 1205 | if sim_env not in supported_sim: 1206 | raise NotImplementedError("Set SIM variable. Supported: " + ", ".join(supported_sim)) 1207 | 1208 | if sim_env == "icarus": 1209 | sim = Icarus(**kwargs) 1210 | elif sim_env == "questa": 1211 | sim = Questa(**kwargs) 1212 | elif sim_env == "modelsim": 1213 | sim = Modelsim(**kwargs) 1214 | elif sim_env == "ius": 1215 | sim = Ius(**kwargs) 1216 | elif sim_env == "xcelium": 1217 | sim = Xcelium(**kwargs) 1218 | elif sim_env == "vcs": 1219 | sim = Vcs(**kwargs) 1220 | elif sim_env == "ghdl": 1221 | sim = Ghdl(**kwargs) 1222 | elif sim_env == "nvc": 1223 | sim = Nvc(**kwargs) 1224 | elif sim_env == "riviera": 1225 | sim = Riviera(**kwargs) 1226 | elif sim_env == "activehdl": 1227 | sim = Activehdl(**kwargs) 1228 | elif sim_env == "verilator": 1229 | sim = Verilator(**kwargs) 1230 | 1231 | return sim.run() 1232 | 1233 | 1234 | def clean(recursive=False): 1235 | dir = os.getcwd() 1236 | 1237 | def rm_clean(): 1238 | sim_build_dir = os.path.join(dir, "sim_build") 1239 | if os.path.isdir(sim_build_dir): 1240 | print("Removing:", sim_build_dir) 1241 | shutil.rmtree(sim_build_dir, ignore_errors=True) 1242 | 1243 | rm_clean() 1244 | 1245 | if recursive: 1246 | for dir, _, _ in os.walk(dir): 1247 | rm_clean() 1248 | --------------------------------------------------------------------------------