diff --git a/README.md b/README.md index d0d2c02..5e619c8 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Have fun! ``` $ git clone https://git.goodcleanfun.de/tmeissner/cocotb_with_ghdl.git $ cd cocotb_with_ghdl -$ git clone https://git.goodcleanfun.de/tmeissner/libvhdl.git +$ ./env-setup.sh $ docker run --rm -ti --volume=$(pwd):/build -e DISPLAY=$DISPLAY \ --volume /tmp/.X11-unix:/tmp/.X11-unix hdlc/sim:scipy /bin/bash $ ./docker-setup.sh @@ -110,4 +110,13 @@ make[1]: Leaving directory '/build/tests ### UART -Simple tests of UART transmitter & receiver of the *libvhdl* project \ No newline at end of file +Simple tests of UART transmitter & receiver of the *libvhdl* project + +* `make DUT=uarttx` or `make` +* `make DUT=uartrx` + +### Wishbone + +Simple tests of Wishbone slave of the *libvhdl* project + +* `make DUT=wishbone` diff --git a/docker-setup.sh b/docker-setup.sh index f39aef3..0e311ae 100755 --- a/docker-setup.sh +++ b/docker-setup.sh @@ -1,4 +1,4 @@ #!/bin/bash python3 -m pip install -r requirements.txt -#python3 -m pip install cocotbext-wishbone/ +python3 -m pip install cocotbext-wishbone/ diff --git a/tests/Makefile b/tests/Makefile index 6d1cbe7..0fe4826 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -11,7 +11,9 @@ else ifeq (${DUT}, uartrx) else ifeq (${DUT}, wishbone) MODULE := tb_wishbone TOPLEVEL := wishboneslavee - SIM_ARGS := -gSimulation=true + SIM_ARGS := -gSimulation=true \ + -gAddressWidth=8 \ + -gDataWidth=16 else $(error ${DUT} not available) endif @@ -45,4 +47,4 @@ results: .PHONY: clean clean:: - rm -rf *.o __pycache__ uarttx uartrx results + rm -rf *.o __pycache__ uarttx uartrx wishboneslavee results diff --git a/tests/Sram.py b/tests/Sram.py new file mode 100644 index 0000000..eb67d9d --- /dev/null +++ b/tests/Sram.py @@ -0,0 +1,125 @@ +import logging +import cocotb +from cocotb.utils import get_sim_time +from cocotb.triggers import FallingEdge, RisingEdge, Timer, ReadOnly + + +class Sram: + + def __init__(self, clk, wen, ren, adr, din, dout, mem, *args, **kwargs): + self._version = "0.0.1" + + self.log = logging.getLogger(f"cocotb.{clk._path}") + + self._clk = clk + self._wen = wen + self._ren = ren + self._adr = adr + self._din = din + self._dout = dout + self._mem = mem + + self._clkedge = RisingEdge(self._clk) + + +class SramRead(Sram): + + def __init__(self, clk, ren, adr, din, mem, *args, **kwargs): + super().__init__(clk, None, ren, adr, din, None, mem, *args, **kwargs) + + self.log.info("SRAM read") + self.log.info(" cocotbext-sram version %s", self._version) + self.log.info(" Copyright (c) 2022 Torsten Meissner") + + self._active = None + self._restart() + + def _restart(self): + self.log.debug("SramRead._restart()") + if self._active is not None: + self._active.kill() + # Schedule SRAM read to run concurrently + self._active = cocotb.start_soon(self._read()) + + async def _read(self): + self.log.debug("SramRead._read()") + while True: + await self._clkedge + if self._ren.value == 1: + _data = self._mem[str(self._adr.value)] + self._din.value = _data + self.log.info("Read data: %s from adr: %s", hex(_data), hex(self._adr.value)) + + +class SramWrite(Sram): + + def __init__(self, clk, wen, adr, dout, mem, *args, **kwargs): + super().__init__(clk, wen, None, adr, None, dout, mem, *args, **kwargs) + + self.log.info("SRAM write") + self.log.info(" cocotbext-sram version %s", self._version) + self.log.info(" Copyright (c) 2022 Torsten Meissner") + + self._active = None + self._restart() + + def _restart(self): + self.log.debug("SramWrite._restart()") + if self._active is not None: + self._active.kill() + # Schedule SRAM write to run concurrently + self._active = cocotb.start_soon(self._write()) + + async def _write(self): + self.log.debug("SramWrite._write()") + while True: + await self._clkedge + if self._wen.value == 1: + self._mem[str(self._adr.value)] = self._dout.value + self.log.info("Wrote data: %s to adr: %s", hex(self._dout.value), hex(self._adr.value)) + + +class SramMonitor(Sram): + + def __init__(self, clk, wen, ren, adr, din, dout, *args, **kwargs): + super().__init__(clk, wen, ren, adr, din, dout, None, *args, **kwargs) + + self.log.info("SRAM monitor") + self.log.info(" cocotbext-sram version %s", self._version) + self.log.info(" Copyright (c) 2022 Torsten Meissner") + + self._active = None + self._transactions = {} + self._restart() + + def _restart(self): + self.log.debug("SramMonitor._restart()") + if self._active is not None: + self._active.kill() + # Schedule SRAM read to run concurrently + self._active = cocotb.start_soon(self._read()) + + async def _read(self): + self.log.debug("SramMonitor._read()") + while True: + await self._clkedge + if self._wen.value: + self._transactions[str(get_sim_time('ns'))] = { + "type" : "write", + "adr" : str(self._adr.value), + "data" : str(self._dout.value)} + elif self._ren.value: + await self._clkedge + await ReadOnly() + self._transactions[str(get_sim_time('ns'))] = { + "type" : "read", + "adr" : str(self._adr.value), + "data" : str(self._din.value)} + + @property + def transactions(self, index=None): + if index: + key = list(self._transactions.keys())[index] + return {key: self._transactions[key]} + else: + return self._transactions diff --git a/tests/tb_wishbone.py b/tests/tb_wishbone.py new file mode 100644 index 0000000..9c2ce72 --- /dev/null +++ b/tests/tb_wishbone.py @@ -0,0 +1,79 @@ +# test_uart.py + +import logging +import random +import cocotb +import pprint +from collections import defaultdict +from Sram import SramRead, SramWrite, SramMonitor +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, RisingEdge, Timer, ReadOnly +from cocotbext.wishbone.driver import WishboneMaster, WBOp + + +# Reset coroutine +async def reset_dut(reset_n, duration_ns): + reset_n.value = 1 + await Timer(duration_ns, units="ns") + reset_n.value = 0 + + +@cocotb.test() +async def test_wishbone(dut): + """ First simple test """ + + clkedge = RisingEdge(dut.wbclk_i) + + # Connect reset + reset = dut.wbrst_i + + + # Create empty SRAM memory + memory = defaultdict() + + mem_read = SramRead(dut.wbclk_i, dut.localren_o, + dut.localadress_o, dut.localdata_i, memory); + mem_write = SramWrite(dut.wbclk_i, dut.localwen_o, + dut.localadress_o, dut.localdata_o, memory); + sram_monitor = SramMonitor(dut.wbclk_i, dut.localwen_o, dut.localren_o, + dut.localadress_o, dut.localdata_i, dut.localdata_o); + + wbmaster = WishboneMaster(dut, "", dut.wbclk_i, + width=16, # size of data bus + timeout=10, # in clock cycle number + signals_dict={"cyc": "wbcyc_i", + "stb": "wbstb_i", + "we": "wbwe_i", + "adr": "wbadr_i", + "datwr":"wbdat_i", + "datrd":"wbdat_o", + "ack": "wback_o" }) + + # Drive input defaults (setimmediatevalue to avoid x asserts) + dut.wbcyc_i.setimmediatevalue(0) + dut.wbstb_i.setimmediatevalue(0) + dut.wbwe_i.setimmediatevalue(0) + dut.wbadr_i.setimmediatevalue(0) + dut.wbdat_i.setimmediatevalue(0) + + clock = Clock(dut.wbclk_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, 100) + dut._log.info("Released reset") + + # Test 10 Wishbone transmissions + for i in range(10): + await clkedge + adr = random.randint(0, 255) + data = random.randint(0, 2**16-1) + await wbmaster.send_cycle([WBOp(adr=adr, dat=data)]) + rec = await wbmaster.send_cycle([WBOp(adr=adr)]) + + # Example to print transactions collected by SRAM monitor + with open('results/sram_transactions.log', 'w', encoding='utf-8') as f: + f.write((f"{'Time':7}{'Type':7}{'Adr':11}{'Data'}\n")) + for k, v in sram_monitor.transactions.items(): + f.write((f"{k:7}{v['type']:7}{v['adr']:11}{v['data']} \n")) \ No newline at end of file