. , , . , 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, , , , .
En général, j'ai beaucoup aimé la façon d'organiser la simulation et les tests avec Python, c'est comme une bouffée d'air frais après Bash, que j'ai le plus souvent utilisé avant. Et j'espère que quelqu'un décrit sera également utile.
Toutes les versions finales des scripts se trouvent dans le référentiel pyhdlsim sur GitHub .