@ -0,0 +1,4 @@ | |||||
#!/bin/bash | |||||
python3 -m pip install -r requirements.txt | |||||
python3 -m pip install cocotbext-wishbone/ |
@ -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 |
@ -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 |
@ -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", |
@ -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 |
@ -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() | |||||
@ -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 |
@ -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) |