diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b5265c2 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,52 @@ +name: Software tests + +on: push + +jobs: + tests: + name: Test + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v2 + - name: Set up Anaconda + uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: 3.9 + - name: Install Conda dependencies + shell: bash -l {0} + run: | + conda info -a + conda install pytest numpy psutil qtpy pyyaml pyzmq pytables + - name: Install basil + shell: bash -l {0} + run: | + pip install basil-daq>=3.0.0 + - name: Install Python dependencies + shell: bash -l {0} + run: | + pip install cocotb cocotb_bus xvfbwrapper pyqt5 + - name: Install iverilog + run: | + sudo apt-get install -qq libhdf5-serial-dev libxkbcommon-x11-0 + sudo apt-get install '^libxcb.*-dev' + # sudo apt-get -y install iverilog-daily + sudo apt-get install gperf + git clone https://github.com/steveicarus/iverilog.git + cd iverilog; autoconf; ./configure; make; sudo make install; cd .. + - name: Install EUDAQ + shell: bash -l {0} + run: | + source tests/setup_eudaq.sh + - name: Install package + shell: bash -l {0} + run: | + pip install -e . + - name: Test + shell: bash -l {0} + run: | + cd tests + pytest -s -v + diff --git a/.gitignore b/.gitignore index eb04cfa..53e89c0 100644 --- a/.gitignore +++ b/.gitignore @@ -76,4 +76,6 @@ output_data *.lprof *.history -. \ No newline at end of file + +sim_build +results.xml diff --git a/README.md b/README.md index 66091dd..9626ca3 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,10 @@ Example: pytlu -t 10000 -c 10000 -oe CH1 --timeout 2 ``` +## Test beam usage + +A detailed description of the TLU can be found [here](https://www.eudet.org/e26/e28/e42441/e57298/EUDET-MEMO-2009-04.pdf). Do not forget to adjust the trigger threshold using the small screw on the front side of the TLU (counter clockwise increases the threshold). + ## EUDAQ integration Pytlu can connect to the data acquisition framework [EUDAQ 1](https://github.com/eudaq/eudaq/tree/v1.x-dev), which is the common run control software used at *pixel test beams*. For the installation of EUDAQ 1.x please follow this [wiki](https://telescopes.desy.de/EUDAQ). To use the EUDAQ libraries within pytlu a [python wrapper](https://github.com/eudaq/eudaq/blob/v1.x-dev/python/PyEUDAQWrapper.py) is used. This wrapper is not build with default settings, thus the following cmake option must be specified when building EUDAQ `-DBUILD_python=ON`. @@ -150,8 +154,8 @@ If you did not add the EUDAQ directory to the `PYTHONPATH` explicitly after inst pytlu_eudaq --path /home/user/git/eudaq ``` -### Debugging and testing -#### Replay feature +## Debugging and testing +### Replay feature It is possible to replay a recorded pytlu raw data file with correct timing to test the system. This allows development and debugging without hardware. To replay a [pytlu raw data file](https://github.com/SiLab-Bonn/pytlu/blob/development/data/tlu_example_data.h5) one has to type: ``` diff --git a/firmware/src/tlu.v b/firmware/src/tlu.v index a460d71..f831357 100644 --- a/firmware/src/tlu.v +++ b/firmware/src/tlu.v @@ -15,8 +15,11 @@ `include "utils/ddr_des.v" `include "utils/cdc_syncfifo.v" `include "utils/generic_fifo.v" +`include "utils/ramb_8_to_n.v" `include "gpio/gpio.v" +`include "gpio/gpio_core.v" + `include "i2c/i2c.v" `include "i2c/i2c_core.v" diff --git a/pytlu/online_monitor/pytlu_receiver.py b/pytlu/online_monitor/pytlu_receiver.py index 5bdcc6e..a7c2fc4 100644 --- a/pytlu/online_monitor/pytlu_receiver.py +++ b/pytlu/online_monitor/pytlu_receiver.py @@ -1,14 +1,11 @@ import time -from PyQt5 import Qt +from PyQt5 import QtWidgets import pyqtgraph as pg -from pyqtgraph.Qt import QtGui from pyqtgraph.dockarea import DockArea, Dock -import pyqtgraph.ptime as ptime from online_monitor.utils import utils from online_monitor.receiver.receiver import Receiver -from zmq.utils import jsonapi class PyTLU(Receiver): @@ -26,17 +23,17 @@ def setup_widgets(self, parent, name): dock_area.addDock(dock_status, 'top') # Status dock on top - cw = QtGui.QWidget() + cw = QtWidgets.QWidget() cw.setStyleSheet("QWidget {background-color:white}") - layout = QtGui.QGridLayout() + layout = QtWidgets.QGridLayout() cw.setLayout(layout) - self.rate_label = QtGui.QLabel("Readout Rate\n0 Hz") - self.timestamp_label = QtGui.QLabel("Data Timestamp\n") - self.plot_delay_label = QtGui.QLabel("Plot Delay\n") - self.spin_box = Qt.QSpinBox(value=0) + self.rate_label = QtWidgets.QLabel("Readout Rate\n0 Hz") + self.timestamp_label = QtWidgets.QLabel("Data Timestamp\n") + self.plot_delay_label = QtWidgets.QLabel("Plot Delay\n") + self.spin_box = QtWidgets.QSpinBox(value=0) self.spin_box.setMaximum(1000000) self.spin_box.setSuffix(" Readouts") - self.reset_button = QtGui.QPushButton('Reset') + self.reset_button = QtWidgets.QPushButton('Reset') layout.addWidget(self.timestamp_label, 0, 0, 0, 1) layout.addWidget(self.plot_delay_label, 0, 1, 0, 1) layout.addWidget(self.rate_label, 0, 2, 0, 1) @@ -104,6 +101,6 @@ def handle_data_if_active(self, data): # set timestamp, plot delay and readour rate self.rate_label.setText("Readout Rate\n%d Hz" % data['fps']) self.timestamp_label.setText("Data Timestamp\n%s" % time.asctime(time.localtime(data['timestamp_stop']))) - now = ptime.time() + now = time.time() self.plot_delay = self.plot_delay * 0.9 + (now - data['timestamp_stop']) * 0.1 self.plot_delay_label.setText("Plot Delay\n%s" % 'not realtime' if abs(self.plot_delay) > 5 else "%1.2f ms" % (self.plot_delay * 1.e3)) diff --git a/requirements.txt b/requirements.txt index 86d6867..4359836 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ basil_daq>=3.0.0 -online_monitor>=0.4.1<0.5 +online-monitor>=0.6 numpy psutil qtpy diff --git a/setup.py b/setup.py index c4d8ab1..57aeb9a 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,6 @@ packages=find_packages(), include_package_data=True, install_requires=install_requires, - setup_requires=['online_monitor>=0.4.1<0.5'], entry_points={ 'console_scripts': [ 'pytlu = pytlu.tlu:main', diff --git a/tests/StreamDriver.py b/tests/StreamDriver.py index 994a4f6..ecf6f14 100644 --- a/tests/StreamDriver.py +++ b/tests/StreamDriver.py @@ -12,10 +12,9 @@ import cocotb from cocotb.binary import BinaryValue -from cocotb.triggers import RisingEdge, ReadOnly, Timer -from cocotb.drivers import BusDriver -from cocotb.result import ReturnValue +from cocotb.triggers import RisingEdge, Timer from cocotb.clock import Clock +from cocotb_bus.drivers import BusDriver class StreamDriver(BusDriver): @@ -25,14 +24,14 @@ class StreamDriver(BusDriver): _optional_signals = ["BUS_BYTE_ACCESS"] def __init__(self, entity): - BusDriver.__init__(self, entity, "", entity.BUS_CLK) + BusDriver.__init__(self, entity, "", entity.BUS_CLK, case_insensitive=False) # Create an appropriately sized high-impedence value - self._high_impedence = BinaryValue(bits=len(self.bus.BUS_DATA)) + self._high_impedence = BinaryValue(n_bits=len(self.bus.BUS_DATA)) self._high_impedence.binstr = "Z" * len(self.bus.BUS_DATA) # Create an appropriately sized high-impedence value - self._x = BinaryValue(bits=len(self.bus.BUS_ADD)) + self._x = BinaryValue(n_bits=len(self.bus.BUS_ADD)) self._x.binstr = "x" * len(self.bus.BUS_ADD) self._has_byte_acces = False @@ -40,22 +39,22 @@ def __init__(self, entity): self.BASE_ADDRESS_STREAM = 0x0001000000000000 # Kick off a clock generator - cocotb.fork(Clock(self.clock, 20830).start()) + cocotb.fork(Clock(self.clock, 20000).start()) @cocotb.coroutine def init(self): # Defaults - self.bus.BUS_RST <= 1 - self.bus.BUS_RD <= 0 - self.bus.BUS_WR <= 0 - self.bus.BUS_ADD <= self._x - self.bus.BUS_DATA <= self._high_impedence - self.bus.STREAM_READY <= 0 + self.bus.BUS_RST.value = 1 + self.bus.BUS_RD.value = 0 + self.bus.BUS_WR.value = 0 + self.bus.BUS_ADD.value = self._x + self.bus.BUS_DATA.value = self._high_impedence + self.bus.STREAM_READY.value = 0 for _ in range(8): yield RisingEdge(self.clock) - self.bus.BUS_RST <= 0 + self.bus.BUS_RST.value = 0 for _ in range(2): yield RisingEdge(self.clock) @@ -75,20 +74,20 @@ def read(self, address, size): if address >= self.BASE_ADDRESS_STREAM: result = yield self.read_stream(address, size) else: - self.bus.BUS_DATA <= self._high_impedence - self.bus.BUS_ADD <= self._x - self.bus.BUS_RD <= 0 + self.bus.BUS_DATA.value = self._high_impedence + self.bus.BUS_ADD.value = self._x + self.bus.BUS_RD.value = 0 yield RisingEdge(self.clock) byte = 0 while(byte <= size): if(byte == size): - self.bus.BUS_RD <= 0 + self.bus.BUS_RD.value = 0 else: - self.bus.BUS_RD <= 1 + self.bus.BUS_RD.value = 1 - self.bus.BUS_ADD <= address + byte + self.bus.BUS_ADD.value = address + byte yield RisingEdge(self.clock) @@ -113,38 +112,38 @@ def read(self, address, size): else: byte += 1 - self.bus.BUS_ADD <= self._x - self.bus.BUS_DATA <= self._high_impedence + self.bus.BUS_ADD.value = self._x + self.bus.BUS_DATA.value = self._high_impedence yield RisingEdge(self.clock) - raise ReturnValue(result) + return result @cocotb.coroutine def write(self, address, data): - self.bus.BUS_ADD <= self._x - self.bus.BUS_DATA <= self._high_impedence - self.bus.BUS_WR <= 0 + self.bus.BUS_ADD.value = self._x + self.bus.BUS_DATA.value = self._high_impedence + self.bus.BUS_WR.value = 0 yield RisingEdge(self.clock) for index, byte in enumerate(data): - self.bus.BUS_DATA <= byte - self.bus.BUS_WR <= 1 - self.bus.BUS_ADD <= address + index + self.bus.BUS_DATA.value = byte + self.bus.BUS_WR.value = 1 + self.bus.BUS_ADD.value = address + index yield Timer(1) # This is hack for iverilog - self.bus.BUS_DATA <= byte - self.bus.BUS_WR <= 1 - self.bus.BUS_ADD <= address + index + self.bus.BUS_DATA.value = byte + self.bus.BUS_WR.value = 1 + self.bus.BUS_ADD.value = address + index yield RisingEdge(self.clock) if(self._has_byte_acces and self.bus.BUS_BYTE_ACCESS.value.integer == 0): raise NotImplementedError("BUS_BYTE_ACCESS for write to be implemented.") - self.bus.BUS_DATA <= self._high_impedence - self.bus.BUS_ADD <= self._x - self.bus.BUS_WR <= 0 + self.bus.BUS_DATA.value = self._high_impedence + self.bus.BUS_ADD.value = self._x + self.bus.BUS_WR.value = 0 yield RisingEdge(self.clock) @@ -153,7 +152,7 @@ def read_stream(self, address, size): result = [] yield RisingEdge(self.clock) - self.bus.STREAM_READY <= 1 + self.bus.STREAM_READY.value = 1 for _ in range(size // 2): @@ -169,8 +168,7 @@ def read_stream(self, address, size): yield RisingEdge(self.clock) yield RisingEdge(self.clock) yield RisingEdge(self.clock) - self.bus.STREAM_READY <= 0 + self.bus.STREAM_READY.value = 0 yield RisingEdge(self.clock) - raise ReturnValue(result) - + return result diff --git a/tests/test_Sim.py b/tests/test_Sim.py index 8c3f772..aa00b8e 100644 --- a/tests/test_Sim.py +++ b/tests/test_Sim.py @@ -25,7 +25,7 @@ def setUp(self): include_dirs=(root_dir, root_dir + "/firmware/src", root_dir + "/tests")) with open(root_dir + '/pytlu/tlu.yaml', 'r') as f: - cnfg = yaml.load(f) + cnfg = yaml.safe_load(f) cnfg['transfer_layer'][0]['type'] = 'SiSim' cnfg['hw_drivers'].append({'name': 'SEQ_GEN_TB', 'type': 'seq_gen', 'interface': 'intf', 'base_addr': 0xc000}) cnfg['hw_drivers'].append({'name': 'TLU_TB', 'type': 'tlu', 'interface': 'intf', 'base_addr': 0xf000}) @@ -362,7 +362,7 @@ def test_tlu_veto(self): while not self.dut['test_pulser'].is_ready: pass - expected_vetoed_triggers = 29 # 29 triggers will not be accepted due to veto signal + expected_vetoed_triggers = 28 # 28 triggers will not be accepted due to veto signal self.check_data(how_many_triggers - expected_vetoed_triggers) def tearDown(self):