. , , . , Verilog, SystemVerilog VHDL. , Bash/Makefile/Tcl. , GUI , , , .. , Python, , bash- .
, . VUnit. , , . , , , . , " " "not invented here".
, , , . - , .
, HDL . :
. , , , .
GUI. , , .
. GUI , (), .
-/. , . , HDL.
. .
. , ( , .).
. / . .
. , .
CI. CI ( , " " ..).
, , - , , .
:
( );
( include);
;
, ( );
( );
, ..
Python, Simulator
, run()
, ? , Icarus Verilog, Modelsim Vivado Simulator, subprocess
. CliArgs
, argparse
, . , . sim.py.
, , - , Python, sim.py
.
, . An FPGA Implementation of a Fixed-Point Square Root Operation.
:
$ tree -a -I .git
.
βββ .github
β βββ workflows # Github Actions
β βββ icarus-test.yml # Icarus Verilog github
β βββ modelsim-test.yml # Modelsim github
βββ .gitignore
βββ LICENSE.txt
βββ README.md
βββ sim #
β βββ conftest.py
β βββ sim.py
β βββ test_sqrt.py
βββ src #
βββ beh #
β βββ sqrt.py
βββ rtl # HDL
β βββ sqrt.v
βββ tb # HDL
βββ tb_sqrt.sv
tb_sqrt.sv
: , "" $sqrt()
, , , .
, , , , ( HDL sim.py
). sim
. , .
test_sqrt.py
.
#!/usr/bin/env python3
from sim import Simulator
sim = Simulator(name='icarus', gui=True, cwd='work')
sim.incdirs += ["../src/tb", "../src/rtl", sim.cwd]
sim.sources += ["../src/rtl/sqrt.v", "../src/tb/tb_sqrt.sv"]
sim.top = "tb_sqrt"
sim.setup()
sim.run()
Icarus GTKWave . . , . - sim.setup()
work
( , ) (sim.run()
).
:
chmod +x test_sqrt.py
./test_sqrt.py
GTKWave.
GUI
, . CliArgs
. , .
#!/usr/bin/env python3
from sim import Simulator, CliArgs
def test(tmpdir, defines, simtool, gui):
sim = Simulator(name=simtool, gui=gui, cwd=tmpdir)
sim.incdirs += ["../src/tb", "../src/rtl", sim.cwd]
sim.sources += ["../src/rtl/sqrt.v", "../src/tb/tb_sqrt.sv"]
sim.defines += defines
sim.top = "tb_sqrt"
sim.setup()
sim.run()
if __name__ == '__main__':
# run script with key -h to see help
args = CliArgs(default_test="test").parse()
test(tmpdir='work', simtool=args.simtool, gui=args.gui, defines=args.defines)
:
$ ./test_sqrt.py -h
usage: test_sqrt.py [-h] [-t <name>] [-s <name>] [-b] [-d <def> [<def> ...]]
optional arguments:
-h, --help show this help message and exit
-t <name> test <name>; default is 'test'
-s <name> simulation tool <name>; default is 'icarus'
-b enable batch mode (no GUI)
-d <def> [<def> ...] define <name>; option can be used multiple times
:
$ ./test_sqrt.py -b
Run Icarus (cwd=/space/projects/pyhdlsim/simtmp/work)
TOP_NAME=tb_sqrt SIM
iverilog -I /space/projects/pyhdlsim/src/tb -I /space/projects/pyhdlsim/src/rtl -I /space/projects/pyhdlsim/simtmp/work -D TOP_NAME=tb_sqrt -D SIM -g2005-sv -s tb_sqrt -o worklib.vvp /space/projects/pyhdlsim/src/rtl/sqrt.v /space/projects/pyhdlsim/src/tb/tb_sqrt.sv
vvp worklib.vvp -lxt2
LXT2 info: dumpfile dump.vcd opened for output.
Test started. Will push 8 words to DUT.
!@# TEST PASSED #@!
:
#
./test_sqrt.py -s modelsim -b
# GUI
./test_sqrt.py -s modelsim
, , , :
$ ./test_sqrt.py -b -d ITER_N=42
Run Icarus (cwd=/space/projects/pyhdlsim/simtmp/work)
TOP_NAME=tb_sqrt SIM
iverilog -I /space/projects/pyhdlsim/src/tb -I /space/projects/pyhdlsim/src/rtl -I /space/projects/pyhdlsim/simtmp/work -D TOP_NAME=tb_sqrt -D SIM -g2005-sv -s tb_sqrt -o worklib.vvp /space/projects/pyhdlsim/src/rtl/sqrt.v /space/projects/pyhdlsim/src/tb/tb_sqrt.sv
vvp worklib.vvp -lxt2
LXT2 info: dumpfile dump.vcd opened for output.
Test started. Will push 42 words to DUT.
!@# TEST PASSED #@!
-/
, . , Verilog , Python. - Python, . src/beh/sqrt.py
. nrsqrt()
.
, , , test_sv
. test_py
, nrsqrt()
.
#!/usr/bin/env python3
from sim import Simulator, CliArgs, path_join, write_memfile
import random
import sys
sys.path.append('../src/beh')
from sqrt import nrsqrt
def create_sim(cwd, simtool, gui, defines):
sim = Simulator(name=simtool, gui=gui, cwd=cwd)
sim.incdirs += ["../src/tb", "../src/rtl", cwd]
sim.sources += ["../src/rtl/sqrt.v", "../src/tb/tb_sqrt.sv"]
sim.defines += defines
sim.top = "tb_sqrt"
return sim
def test_sv(tmpdir, defines, simtool, gui):
sim = create_sim(tmpdir, simtool, gui, defines)
sim.setup()
sim.run()
def test_py(tmpdir, defines, simtool, gui=False, pytest_run=True):
# prepare simulator
sim = create_sim(tmpdir, simtool, gui, defines)
sim.setup()
# prepare model data
try:
din_width = int(sim.get_define('DIN_W'))
except TypeError:
din_width = 32
iterations = 100
stimuli = [random.randrange(2 ** din_width) for _ in range(iterations)]
golden = [nrsqrt(d, din_width) for d in stimuli]
write_memfile(path_join(tmpdir, 'stimuli.mem'), stimuli)
write_memfile(path_join(tmpdir, 'golden.mem'), golden)
sim.defines += ['ITER_N=%d' % iterations]
sim.defines += ['PYMODEL', 'PYMODEL_STIMULI="stimuli.mem"', 'PYMODEL_GOLDEN="golden.mem"']
# run simulation
sim.run()
if __name__ == '__main__':
args = CliArgs(default_test="test_sv").parse()
try:
globals()[args.test](tmpdir='work', simtool=args.simtool, gui=args.gui, defines=args.defines)
except KeyError:
print("There is no test with name '%s'!" % args.test)
, , :
#
./test_sqrt.py -t test_py
.
2 , 202, , . pytest.
- pytest.
, pytest test* : , , , .
, (
assert
).
(fixtures). ,
test_a(a)
.
conftest.py
, .
:
pytest
- , ;
pytest -v
- e ;
pytest -rP
- stdout , ;
pytest test_sqrt.py::test_sv
- .
pytest . pytest. simtool
defines
. , . gui
pytest_run
. , .. pytest , .
, , pytest_run
, pytest, .
tmpdir
- , , . .. - sim
.
- pytest, .. test_.
, is_passed
. , !@# TEST PASSED #@!
stdout. , , , . , . stdout sim.stdout
.
#!/usr/bin/env python3
import pytest
from sim import Simulator, CliArgs, path_join, write_memfile
import random
import sys
sys.path.append('../src/beh')
from sqrt import nrsqrt
@pytest.fixture()
def defines():
return []
@pytest.fixture
def simtool():
return 'icarus'
def create_sim(cwd, simtool, gui, defines):
sim = Simulator(name=simtool, gui=gui, cwd=cwd, passed_marker='!@# TEST PASSED #@!')
sim.incdirs += ["../src/tb", "../src/rtl", cwd]
sim.sources += ["../src/rtl/sqrt.v", "../src/tb/tb_sqrt.sv"]
sim.defines += defines
sim.top = "tb_sqrt"
return sim
def test_sv(tmpdir, defines, simtool, gui=False, pytest_run=True):
sim = create_sim(tmpdir, simtool, gui, defines)
sim.setup()
sim.run()
if pytest_run:
assert sim.is_passed
def test_py(tmpdir, defines, simtool, gui=False, pytest_run=True):
# prepare simulator
sim = create_sim(tmpdir, simtool, gui, defines)
sim.setup()
# prepare model data
try:
din_width = int(sim.get_define('DIN_W'))
except TypeError:
din_width = 32
iterations = 100
stimuli = [random.randrange(2 ** din_width) for _ in range(iterations)]
golden = [nrsqrt(d, din_width) for d in stimuli]
write_memfile(path_join(tmpdir, 'stimuli.mem'), stimuli)
write_memfile(path_join(tmpdir, 'golden.mem'), golden)
sim.defines += ['ITER_N=%d' % iterations]
sim.defines += ['PYMODEL', 'PYMODEL_STIMULI="stimuli.mem"', 'PYMODEL_GOLDEN="golden.mem"']
# run simulation
sim.run()
if pytest_run:
assert sim.is_passed
if __name__ == '__main__':
args = CliArgs(default_test="test_sv").parse()
try:
globals()[args.test](tmpdir='work', simtool=args.simtool, gui=args.gui, defines=args.defines, pytest_run=False)
except KeyError:
print("There is no test with name '%s'!" % args.test)
:
$ pytest
========== test session starts ===========
platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmp
plugins: xdist-2.2.0, forked-1.3.0
collected 2 items
test_sqrt.py .. [100%]
=========== 2 passed in 0.08s ============
$ pytest -v
========== test session starts ===========
platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmp
plugins: xdist-2.2.0, forked-1.3.0
collected 2 items
test_sqrt.py::test_sv PASSED [ 50%]
test_sqrt.py::test_py PASSED [100%]
=========== 2 passed in 0.08s ============
. , N , , . pytest.
, defines
:
#
@pytest.fixture()
def defines():
return []
#
@pytest.fixture(params=[[], ['DIN_W=16'], ['DIN_W=18'], ['DIN_W=25'], ['DIN_W=32']])
def defines(request):
return request.param
5 . :
$ pytest -v
================== test session starts ==================
platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmp
plugins: xdist-2.2.0, forked-1.3.0
collected 10 items
test_sqrt.py::test_sv[defines0] PASSED [ 10%]
test_sqrt.py::test_sv[defines1] PASSED [ 20%]
test_sqrt.py::test_sv[defines2] PASSED [ 30%]
test_sqrt.py::test_sv[defines3] PASSED [ 40%]
test_sqrt.py::test_sv[defines4] PASSED [ 50%]
test_sqrt.py::test_py[defines0] PASSED [ 60%]
test_sqrt.py::test_py[defines1] PASSED [ 70%]
test_sqrt.py::test_py[defines2] PASSED [ 80%]
test_sqrt.py::test_py[defines3] PASSED [ 90%]
test_sqrt.py::test_py[defines4] PASSED [100%]
================== 10 passed in 0.28s ===================
, 5 .
. :
python3 -m pip install pytest-xdist
, , 4:
# auto, pytest
pytest -n 4
, :
def test_slow(tmpdir, defines, simtool, gui=False, pytest_run=True):
sim = create_sim(tmpdir, simtool, gui, defines)
sim.defines += ['ITER_N=500000']
sim.setup()
sim.run()
if pytest_run:
assert sim.is_passed
( 3*5=15):
$ pytest
=================== test session starts ====================
platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmp
plugins: xdist-2.2.0, forked-1.3.0
collected 15 items
test_sqrt.py ............... [100%]
============== 15 passed in 242.74s (0:04:02) ==============
$ pytest -n auto
=================== test session starts ====================
platform linux -- Python 3.8.5, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: /space/projects/misc/habr-publications/pyhdlsim/pyhdlsim/simtmp
plugins: xdist-2.2.0, forked-1.3.0
gw0 [15] / gw1 [15] / gw2 [15] / gw3 [15]
............... [100%]
============== 15 passed in 145.66s (0:02:25) ==============
, , .
, pytest -s
. pytest. , - simtool
.
conftest.py
, pytest. sim.py
:
def pytest_addoption(parser):
parser.addoption("--sim", action="store", default="icarus")
test_sqrt.py
simtool
:
@pytest.fixture
def simtool(pytestconfig):
return pytestconfig.getoption("sim")
:
pytest --sim modelsim -n auto
CI. Github Actions + (Modelsim | Icarus)
(CI). .github/workflows/icarus-test.yml
.github/workflows/modelsim-test.yml
. Github Actions - , Github. , .
Icarus Verilog:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-xdist
sudo apt-get install iverilog
- name: Test code
working-directory: ./sim
run: |
pytest -n auto
Modelsim Intel Starter Pack:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-xdist
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install -y libc6:i386 libxtst6:i386 libncurses5:i386 libxft2:i386 libstdc++6:i386 libc6-dev-i386 lib32z1 libqt5xml5 liblzma-dev
wget https://download.altera.com/akdlm/software/acdsinst/20.1std/711/ib_installers/ModelSimSetup-20.1.0.711-linux.run
chmod +x ModelSimSetup-20.1.0.711-linux.run
./ModelSimSetup-20.1.0.711-linux.run --mode unattended --accept_eula 1 --installdir $HOME/ModelSim-20.1.0 --unattendedmodeui none
echo "$HOME/ModelSim-20.1.0/modelsim_ase/bin" >> $GITHUB_PATH
- name: Test code
working-directory: ./sim
run: |
pytest -n auto --sim modelsim
Modelsim. - ! Ubuntu/Fedora (, , Quartus+Modelsim 19.1 Fedora 29).
:
, 1.3GB Modelsim ( , !), Icarus.
Docker- Modelsim, , , , .
In general, I really liked the way of organizing simulation and testing using Python, it's like a breath of fresh air after Bash, which I most often used before. And I hope that someone described will be useful too.
All final versions of the scripts are in the pyhdlsim repository on GitHub .