From f49a46399a9895b6d7ff98bc083d8ac6af9d4129 Mon Sep 17 00:00:00 2001 From: tmeissner Date: Mon, 7 Feb 2022 13:05:50 +0100 Subject: [PATCH] initial commit of 1st simple UART tests --- docker-setup.sh | 4 ++ env-setup.sh | 11 ++++ requirements.txt | 17 +++++++ setup.py.patch | 23 +++++++++ tests/Makefile | 48 +++++++++++++++++ tests/Uart.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++ tests/Vai.py | 85 +++++++++++++++++++++++++++++++ tests/tb_uart.py | 94 ++++++++++++++++++++++++++++++++++ 8 files changed, 412 insertions(+) create mode 100755 docker-setup.sh create mode 100755 env-setup.sh create mode 100644 requirements.txt create mode 100644 setup.py.patch create mode 100644 tests/Makefile create mode 100644 tests/Uart.py create mode 100644 tests/Vai.py create mode 100644 tests/tb_uart.py diff --git a/docker-setup.sh b/docker-setup.sh new file mode 100755 index 0000000..0e311ae --- /dev/null +++ b/docker-setup.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +python3 -m pip install -r requirements.txt +python3 -m pip install cocotbext-wishbone/ diff --git a/env-setup.sh b/env-setup.sh new file mode 100755 index 0000000..6e00337 --- /dev/null +++ b/env-setup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ ! -d cocotbext-wishbone/.git ]; then + git clone https://github.com/wallento/cocotbext-wishbone.git + cd cocotbext-wishbone && patch < ../setup.py.patch + cd .. +fi + +if [ ! -d libvhdl/.git ]; then + git clone https://github.com/tmeissner/libvhdl.git +fi diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ebee7d1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,17 @@ +# basic bus model +cocotb-bus + +# bus functional models +cocotbext-axi +cocotbext-pcie +cocotbext-eth +cocotbext-uart +cocotbext-spi + +# verification libraries +cocotb-coverage +pyvsc +pyuvm + +# utilities +wavedrom diff --git a/setup.py.patch b/setup.py.patch new file mode 100644 index 0000000..9273f8a --- /dev/null +++ b/setup.py.patch @@ -0,0 +1,23 @@ +--- setup.py 2022-02-07 11:35:49.632921471 +0100 ++++ setup.py.new 2022-02-07 11:40:11.084930778 +0100 +@@ -5,19 +5,13 @@ + + setuptools.setup( + name="cocotbext-wishbone", +- use_scm_version={ +- "relative_to": __file__, +- "write_to": "cocotbext/wishbone/version.py", +- }, ++ version="0.2.1", + author="Staf Verhaegen, Mathias Kreider", + author_email="staf@stafverhaegen.be, m.kreider@gsi.de", + description="Cocotb Wishbone modules", + long_description=long_description, + packages=["cocotbext.wishbone"], + install_requires=['cocotb>=1.6.0', 'cocotb_bus'], +- setup_requires=[ +- 'setuptools_scm', +- ], + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: BSD License", diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..6d1cbe7 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,48 @@ +# Default test +DUT ?= uarttx + +# Test related variables +ifeq (${DUT}, uarttx) + MODULE := tb_uart + TOPLEVEL := ${DUT} +else ifeq (${DUT}, uartrx) + MODULE := tb_uart + TOPLEVEL := ${DUT} +else ifeq (${DUT}, wishbone) + MODULE := tb_wishbone + TOPLEVEL := wishboneslavee + SIM_ARGS := -gSimulation=true +else + $(error ${DUT} not available) +endif + + +# Simulator (GHDL) & RTL related +SIM := ghdl +TOPLEVEL_LANG := vhdl +VHDL_SOURCES_libvhdl := ../libvhdl/common/UtilsP.vhd +VHDL_SOURCES := ../libvhdl/syn/*.vhd +SIM_BUILD := work +COMPILE_ARGS := --std=08 +SIM_ARGS += \ + --wave=results/${TOPLEVEL}.ghw \ + --psl-report=results/${TOPLEVEL}_psl.json \ + --vpi-trace=results/${TOPLEVEL}_vpi.log + +# Cocotb related +TESTCASE := test_${DUT} +COCOTB_LOG_LEVEL := DEBUG +CUSTOM_COMPILE_DEPS := results +COCOTB_RESULTS_FILE := results/${TOPLEVEL}.xml + + +include $(shell cocotb-config --makefiles)/Makefile.sim + + +results: + mkdir -p results + + +.PHONY: clean +clean:: + rm -rf *.o __pycache__ uarttx uartrx results diff --git a/tests/Uart.py b/tests/Uart.py new file mode 100644 index 0000000..01aa4d1 --- /dev/null +++ b/tests/Uart.py @@ -0,0 +1,130 @@ +import logging +from cocotb.triggers import FallingEdge, RisingEdge, Timer, ReadOnly + + +class Uart: + """UART base class""" + + def __init__(self, txrx, clock, div, bits, parity, *args, **kwargs): + self._version = "0.0.1" + + self.log = logging.getLogger(f"cocotb.{txrx._path}") + + self._txrx = txrx + self._clock = clock + self._div = div + self._bits = bits + self._par = parity + + self._clkedge = RisingEdge(self._clock) + + async def _wait_cycle(self): + for x in range(self._div): + await self._clkedge + + @staticmethod + def odd_parity(data): + parity = True + while data: + parity = not parity + data = data & (data - 1) + return int(parity) + + +class UartReceiver(Uart): + + def __init__(self, txrx, clock, div, bits, parity, *args, **kwargs): + super().__init__(txrx, clock, div, bits, parity, *args, **kwargs) + + self.log.info("UART receiver") + self.log.info(" cocotbext-uart version %s", self._version) + self.log.info(" Copyright (c) 2022 Torsten Meissner") + + async def receive(self): + """Receive and return one UART frame""" + + # Wait for frame start + await FallingEdge(self._txrx) + + # Consume start bit + await self._get_start_bit() + + # Receive data bits + self._rec = 0 + for x in range(self._bits): + await self._wait_cycle() + await ReadOnly() + self._rec |= bool(self._txrx.value.integer) << x + + if self._par: + # Consume parity bit + await self._get_parity_bit() + + # Consume stop bit + await self._get_stop_bit() + + self.log.info("Received data: %s", hex(self._rec)) + return self._rec + + async def _get_start_bit(self): + """Consume and check start bit""" + for x in range(int(self._div/2)): + await self._clkedge + await ReadOnly() + if self._txrx.value == 1: + self.log.warning("Start bit set") + + async def _get_stop_bit(self): + """Consume and check stop bit""" + await self._wait_cycle() + await ReadOnly() + if self._txrx.value == 0: + self.log.warning("Stop bit not set") + + async def _get_parity_bit(self): + """Consume and check parity bit""" + await self._wait_cycle() + await ReadOnly() + if self.odd_parity(self._rec) != self._txrx.value: + self.log.warning("Parity wrong") + + +class UartDriver(Uart): + + def __init__(self, txrx, clock, div, bits, parity, *args, **kwargs): + super().__init__(txrx, clock, div, bits, parity, *args, **kwargs) + + self.log.info("UART sender") + self.log.info(" cocotbext-uart version %s", self._version) + self.log.info(" Copyright (c) 2022 Torsten Meissner") + + # Drive input defaults (setimmediatevalue to avoid x asserts) + self._txrx.setimmediatevalue(1) + + async def send(self, data): + """Send one UART frame""" + + self._data = data; + + self.log.info("Sending data: %s", hex(self._data)) + + # Send start bit + await self._send_bit(0) + + # Send data bits + for x in range(self._bits): + self._txrx.value = (self._data >> x) & 1 + await self._wait_cycle() + + + if self._par: + # Send parity bit + await self._send_bit(self.odd_parity(self._data)) + + # Consume stop bit + await self._send_bit(1) + + async def _send_bit(self, data): + self._txrx.value = data + await self._wait_cycle() + diff --git a/tests/Vai.py b/tests/Vai.py new file mode 100644 index 0000000..20b71b4 --- /dev/null +++ b/tests/Vai.py @@ -0,0 +1,85 @@ +import logging +from cocotb.triggers import FallingEdge, RisingEdge, Timer, ReadOnly + + +class Vai: + """VAI base class""" + + def __init__(self, clock, data, valid, accept, *args, **kwargs): + self._version = "0.0.1" + + self.log = logging.getLogger(f"cocotb.{data._path}") + + self._data = data + self._valid = valid + self._accept = accept + self._clock = clock + + self._clkedge = RisingEdge(self._clock) + + +class VaiDriver(Vai): + """Valid-Accept Driver""" + + def __init__(self, clock, data, valid, accept, *args, **kwargs): + super().__init__(clock, data, valid, accept, *args, **kwargs) + + self.log.info("Valid-accept driver") + self.log.info(" cocotbext-vai version %s", self._version) + self.log.info(" Copyright (c) 2022 Torsten Meissner") + + # Drive input defaults (setimmediatevalue to avoid x asserts) + self._data.setimmediatevalue(0) + self._valid.setimmediatevalue(0) + + async def send(self, data, sync=True): + if sync: + await self._clkedge + + self.log.info("Sending data: %s", hex(data)) + self._valid.value = 1 + self._data.value = data + + while True: + await ReadOnly() + if self._accept.value: + break + await self._clkedge + await self._clkedge + + self._valid.value = 0 + + + +class VaiReceiver(Vai): + """Valid-Accept Receiver""" + + def __init__(self, clock, data, valid, accept, *args, **kwargs): + super().__init__(clock, data, valid, accept, *args, **kwargs) + + self.log.info("Valid-accept receiver") + self.log.info(" cocotbext-vai version %s", self._version) + self.log.info(" Copyright (c) 2022 Torsten Meissner") + + # Drive input defaults (setimmediatevalue to avoid x asserts) + self._accept.setimmediatevalue(0) + + async def receive(self, sync=True): + if sync: + await self._clkedge + + while True: + await ReadOnly() + if self._valid.value: + break + await self._clkedge + + await self._clkedge + self._accept.value = 1 + _rec = self._data.value + self.log.info("Received data: %s", hex(_rec)) + + await self._clkedge + self._accept.value = 0 + + return _rec diff --git a/tests/tb_uart.py b/tests/tb_uart.py new file mode 100644 index 0000000..165f5f8 --- /dev/null +++ b/tests/tb_uart.py @@ -0,0 +1,94 @@ +# test_uart.py + +import logging +import random +import cocotb +import wavedrom +from Uart import UartDriver, UartReceiver +from Vai import VaiDriver, VaiReceiver +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, RisingEdge, Timer, ReadOnly +from cocotb.wavedrom import Wavedrom, trace + + + +# Reset coroutine +async def reset_dut(reset_n, duration_ns): + reset_n.value = 0 + await Timer(duration_ns, units="ns") + reset_n.value = 1 + + +def wave2svg(wave, file): + svg = wavedrom.render(wave) + svg.saveas(file) + + +@cocotb.test() +async def test_uarttx(dut): + """ First simple test """ + + clkedge = RisingEdge(dut.clk_i) + + # Connect reset + reset_n = dut.reset_n_i + + # Instantiate VAI driver + vai_driver = VaiDriver(dut.clk_i, dut.data_i, dut.valid_i, dut.accept_o) + # Instantiate UART receiver + uart_receiver = UartReceiver(dut.tx_o, dut.clk_i, 10, 8, True); + + # Drive input defaults (setimmediatevalue to avoid x asserts) + dut.data_i.setimmediatevalue(0) + dut.valid_i.setimmediatevalue(0) + + clock = Clock(dut.clk_i, 10, units="ns") # Create a 10 ns period clock + cocotb.start_soon(clock.start()) # Start the clock + + # Execution will block until reset_dut has completed + dut._log.info("Hold reset") + await reset_dut(reset_n, 100) + dut._log.info("Released reset") + + # Test 10 UART transmissions + for i in range(10): + await clkedge + val = random.randint(0, 255) + await vai_driver.send(val) + rec = await uart_receiver.receive(); + assert rec == val, "UART sent data was incorrect on the {}th cycle".format(i) + + +@cocotb.test() +async def test_uartrx(dut): + """ First simple test """ + + clkedge = RisingEdge(dut.clk_i) + + # Connect reset + reset_n = dut.reset_n_i + + # Instantiate UART driver + uart_driver = UartDriver(dut.rx_i, dut.clk_i, 10, 8, True); + # Instantiate VAI receiver + vai_receiver = VaiReceiver(dut.clk_i, dut.data_o, dut.valid_o, dut.accept_i) + + # Drive input defaults (setimmediatevalue to avoid x asserts) + dut.rx_i.setimmediatevalue(1) + dut.accept_i.setimmediatevalue(0) + + clock = Clock(dut.clk_i, 10, units="ns") # Create a 1 us period clock + cocotb.start_soon(clock.start()) # Start the clock + + # Execution will block until reset_dut has completed + dut._log.info("Hold reset") + await reset_dut(reset_n, 100) + dut._log.info("Released reset") + + # Test 10 UART transmissions + for i in range(10): + await clkedge + val = random.randint(0, 255) + await uart_driver.send(val) + rec = await vai_receiver.receive(); + assert rec == val, "UART received data was incorrect on the {}th cycle".format(i)