├── .github └── workflows │ └── regression-tests.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── cocotbext └── uart │ ├── __init__.py │ ├── uart.py │ └── version.py ├── setup.cfg ├── setup.py └── tests └── uart ├── Makefile ├── test_uart.py └── test_uart.v /.github/workflows/regression-tests.yml: -------------------------------------------------------------------------------- 1 | name: Regression Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Python ${{matrix.python-version}} 8 | runs-on: ubuntu-20.04 9 | 10 | strategy: 11 | matrix: 12 | python-version: [3.6, 3.7, 3.8, 3.9] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | 22 | - name: Install Icarus Verilog 23 | run: | 24 | sudo apt install -y --no-install-recommends iverilog 25 | 26 | - name: Install Python dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install tox tox-gh-actions 30 | 31 | - name: Test with tox 32 | run: tox 33 | 34 | - name: Upload coverage to codecov 35 | run: | 36 | pip install codecov 37 | codecov 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Alex Forencich 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | recursive-include tests Makefile test_*.py test_*.v 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UART interface modules for Cocotb 2 | 3 | [![Build Status](https://github.com/alexforencich/cocotbext-uart/workflows/Regression%20Tests/badge.svg?branch=master)](https://github.com/alexforencich/cocotbext-uart/actions/) 4 | [![codecov](https://codecov.io/gh/alexforencich/cocotbext-uart/branch/master/graph/badge.svg)](https://codecov.io/gh/alexforencich/cocotbext-uart) 5 | [![PyPI version](https://badge.fury.io/py/cocotbext-uart.svg)](https://pypi.org/project/cocotbext-uart) 6 | 7 | GitHub repository: https://github.com/alexforencich/cocotbext-uart 8 | 9 | ## Introduction 10 | 11 | UART simulation models for [cocotb](https://github.com/cocotb/cocotb). 12 | 13 | ## Installation 14 | 15 | Installation from pip (release version, stable): 16 | 17 | $ pip install cocotbext-uart 18 | 19 | Installation from git (latest development version, potentially unstable): 20 | 21 | $ pip install https://github.com/alexforencich/cocotbext-uart/archive/master.zip 22 | 23 | Installation for active development: 24 | 25 | $ git clone https://github.com/alexforencich/cocotbext-uart 26 | $ pip install -e cocotbext-uart 27 | 28 | ## Documentation and usage examples 29 | 30 | See the `tests` directory and [verilog-uart](https://github.com/alexforencich/verilog-uart) for complete testbenches using these modules. 31 | 32 | ### UART 33 | 34 | The `UartSource` and `UartSink` classes can be used to drive, receive, and monitor asynchronous serial data. 35 | 36 | To use these modules, import the one you need and connect it to the DUT: 37 | 38 | from cocotbext.uart import UartSource, UartSink 39 | 40 | uart_source = UartSource(dut.rxd, baud=115200, bits=8) 41 | uart_sink = UartSink(dut.rxd, baud=115200, bits=8) 42 | 43 | To send data into a design with a `UartSource`, call `write()` or `write_nowait()`. Accepted data types are iterables of ints, including lists, bytes, bytearrays, etc. Optionally, call `wait()` to wait for the transmit operation to complete. Example: 44 | 45 | await uart_source.send(b'test data') 46 | # wait for operation to complete (optional) 47 | await uart_source.wait() 48 | 49 | To receive data with a `UartSink`, call `read()` or `read_nowait()`. Optionally call `wait()` to wait for new receive data. `read()` will block until at least 1 data byte is available. Both `read()` and `read_nowait()` will return up to _count_ bytes from the receive queue, or the entire contents of the receive queue if not specified. 50 | 51 | data = await uart_sink.recv() 52 | 53 | #### Constructor parameters: 54 | 55 | * _data_: data signal 56 | * _baud_: baud rate in bits per second (optional, default 9600) 57 | * _bits_: bits per byte (optional, default 8) 58 | * _stop_bits_: length of stop bit in bit times (optional, default 1) 59 | 60 | #### Attributes: 61 | 62 | * _baud_: baud rate in bits per second 63 | * _bits_: bits per byte 64 | * _stop_bits_: length of stop bit in bit times 65 | 66 | #### Methods 67 | 68 | * `write(data)`: send _data_ (blocking) (source) 69 | * `write_nowait(data)`: send _data_ (non-blocking) (source) 70 | * `read(count)`: read _count_ bytes from buffer (blocking) (sink) 71 | * `read_nowait(count)`: read _count_ bytes from buffer (non-blocking) (sink) 72 | * `count()`: returns the number of items in the queue (all) 73 | * `empty()`: returns _True_ if the queue is empty (all) 74 | * `idle()`: returns _True_ if no transfer is in progress (all) or if the queue is not empty (source) 75 | * `clear()`: drop all data in queue (all) 76 | * `wait()`: wait for idle (source) 77 | * `wait(timeout=0, timeout_unit='ns')`: wait for data received (sink) 78 | -------------------------------------------------------------------------------- /cocotbext/uart/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Copyright (c) 2020 Alex Forencich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | """ 24 | 25 | from .version import __version__ 26 | 27 | from .uart import UartSource, UartSink 28 | -------------------------------------------------------------------------------- /cocotbext/uart/uart.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Copyright (c) 2020 Alex Forencich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | """ 24 | 25 | import logging 26 | 27 | import cocotb 28 | from cocotb.queue import Queue 29 | from cocotb.triggers import FallingEdge, Timer, First, Event 30 | 31 | from .version import __version__ 32 | 33 | 34 | class UartSource: 35 | def __init__(self, data, baud=9600, bits=8, stop_bits=1, *args, **kwargs): 36 | self.log = logging.getLogger(f"cocotb.{data._path}") 37 | self._data = data 38 | self._baud = baud 39 | self._bits = bits 40 | self._stop_bits = stop_bits 41 | 42 | self.log.info("UART source") 43 | self.log.info("cocotbext-uart version %s", __version__) 44 | self.log.info("Copyright (c) 2020 Alex Forencich") 45 | self.log.info("https://github.com/alexforencich/cocotbext-uart") 46 | 47 | super().__init__(*args, **kwargs) 48 | 49 | self.active = False 50 | self.queue = Queue() 51 | 52 | self._idle = Event() 53 | self._idle.set() 54 | 55 | self._data.setimmediatevalue(1) 56 | 57 | self.log.info("UART source configuration:") 58 | self.log.info(" Baud rate: %d bps", self._baud) 59 | self.log.info(" Byte size: %d bits", self._bits) 60 | self.log.info(" Stop bits: %f bits", self._stop_bits) 61 | 62 | self._run_cr = None 63 | self._restart() 64 | 65 | def _restart(self): 66 | if self._run_cr is not None: 67 | self._run_cr.kill() 68 | self._run_cr = cocotb.fork(self._run(self._data, self._baud, self._bits, self._stop_bits)) 69 | 70 | @property 71 | def baud(self): 72 | return self._baud 73 | 74 | @baud.setter 75 | def baud(self, value): 76 | self.baud = value 77 | self._restart() 78 | 79 | @property 80 | def bits(self): 81 | return self._bits 82 | 83 | @bits.setter 84 | def bits(self, value): 85 | self.bits = value 86 | self._restart() 87 | 88 | @property 89 | def stop_bits(self): 90 | return self._stop_bits 91 | 92 | @stop_bits.setter 93 | def stop_bits(self, value): 94 | self.stop_bits = value 95 | self._restart() 96 | 97 | async def write(self, data): 98 | for b in data: 99 | await self.queue.put(int(b)) 100 | self._idle.clear() 101 | 102 | def write_nowait(self, data): 103 | for b in data: 104 | self.queue.put_nowait(int(b)) 105 | self._idle.clear() 106 | 107 | def count(self): 108 | return self.queue.qsize() 109 | 110 | def empty(self): 111 | return self.queue.empty() 112 | 113 | def idle(self): 114 | return self.empty() and not self.active 115 | 116 | def clear(self): 117 | while not self.queue.empty(): 118 | frame = self.queue.get_nowait() 119 | 120 | async def wait(self): 121 | await self._idle.wait() 122 | 123 | async def _run(self, data, baud, bits, stop_bits): 124 | self.active = False 125 | 126 | bit_t = Timer(int(1e9/self.baud), 'ns') 127 | stop_bit_t = Timer(int(1e9/self.baud*stop_bits), 'ns') 128 | 129 | while True: 130 | if self.empty(): 131 | self.active = False 132 | self._idle.set() 133 | 134 | b = await self.queue.get() 135 | self.active = True 136 | 137 | self.log.info("Write byte 0x%02x", b) 138 | 139 | # start bit 140 | data.value = 0 141 | await bit_t 142 | 143 | # data bits 144 | for k in range(self.bits): 145 | data.value = b & 1 146 | b >>= 1 147 | await bit_t 148 | 149 | # stop bit 150 | data.value = 1 151 | await stop_bit_t 152 | 153 | 154 | class UartSink: 155 | 156 | def __init__(self, data, baud=9600, bits=8, stop_bits=1, *args, **kwargs): 157 | self.log = logging.getLogger(f"cocotb.{data._path}") 158 | self._data = data 159 | self._baud = baud 160 | self._bits = bits 161 | self._stop_bits = stop_bits 162 | 163 | self.log.info("UART sink") 164 | self.log.info("cocotbext-uart version %s", __version__) 165 | self.log.info("Copyright (c) 2020 Alex Forencich") 166 | self.log.info("https://github.com/alexforencich/cocotbext-uart") 167 | 168 | super().__init__(*args, **kwargs) 169 | 170 | self.active = False 171 | self.queue = Queue() 172 | self.sync = Event() 173 | 174 | self.log.info("UART sink configuration:") 175 | self.log.info(" Baud rate: %d bps", self._baud) 176 | self.log.info(" Byte size: %d bits", self._bits) 177 | self.log.info(" Stop bits: %f bits", self._stop_bits) 178 | 179 | self._run_cr = None 180 | self._restart() 181 | 182 | def _restart(self): 183 | if self._run_cr is not None: 184 | self._run_cr.kill() 185 | self._run_cr = cocotb.fork(self._run(self._data, self._baud, self._bits, self._stop_bits)) 186 | 187 | @property 188 | def baud(self): 189 | return self._baud 190 | 191 | @baud.setter 192 | def baud(self, value): 193 | self.baud = value 194 | self._restart() 195 | 196 | @property 197 | def bits(self): 198 | return self._bits 199 | 200 | @bits.setter 201 | def bits(self, value): 202 | self.bits = value 203 | self._restart() 204 | 205 | @property 206 | def stop_bits(self): 207 | return self._stop_bits 208 | 209 | @stop_bits.setter 210 | def stop_bits(self, value): 211 | self.stop_bits = value 212 | self._restart() 213 | 214 | async def read(self, count=-1): 215 | while self.empty(): 216 | self.sync.clear() 217 | await self.sync.wait() 218 | return self.read_nowait(count) 219 | 220 | def read_nowait(self, count=-1): 221 | if count < 0: 222 | count = self.queue.qsize() 223 | if self.bits == 8: 224 | data = bytearray() 225 | else: 226 | data = [] 227 | for k in range(count): 228 | data.append(self.queue.get_nowait()) 229 | return data 230 | 231 | def count(self): 232 | return self.queue.qsize() 233 | 234 | def empty(self): 235 | return self.queue.empty() 236 | 237 | def idle(self): 238 | return not self.active 239 | 240 | def clear(self): 241 | while not self.queue.empty(): 242 | frame = self.queue.get_nowait() 243 | 244 | async def wait(self, timeout=0, timeout_unit='ns'): 245 | if not self.empty(): 246 | return 247 | self.sync.clear() 248 | if timeout: 249 | await First(self.sync.wait(), Timer(timeout, timeout_unit)) 250 | else: 251 | await self.sync.wait() 252 | 253 | async def _run(self, data, baud, bits, stop_bits): 254 | self.active = False 255 | 256 | half_bit_t = Timer(int(1e9/self.baud/2), 'ns') 257 | bit_t = Timer(int(1e9/self.baud), 'ns') 258 | stop_bit_t = Timer(int(1e9/self.baud*stop_bits), 'ns') 259 | 260 | while True: 261 | await FallingEdge(data) 262 | 263 | self.active = True 264 | 265 | # start bit 266 | await half_bit_t 267 | 268 | # data bits 269 | b = 0 270 | for k in range(bits): 271 | await bit_t 272 | b |= bool(data.value.integer) << k 273 | 274 | # stop bit 275 | await stop_bit_t 276 | 277 | self.log.info("Read byte 0x%02x", b) 278 | 279 | self.queue.put_nowait(b) 280 | self.sync.set() 281 | 282 | self.active = False 283 | -------------------------------------------------------------------------------- /cocotbext/uart/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.2" 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # package information 2 | [metadata] 3 | name = cocotbext-uart 4 | version = attr: cocotbext.uart.version.__version__ 5 | description = UART modules for cocotb 6 | keywords = uart, cocotb 7 | author = Alex Forencich 8 | author_email = alex@alexforencich.com 9 | license = MIT 10 | url = https://github.com/alexforencich/cocotbext-uart 11 | project_urls = 12 | Bug Tracker = https://github.com/alexforencich/cocotbext-uart/issues 13 | Source Code = https://github.com/alexforencich/cocotbext-uart 14 | download_url = https://github.com/alexforencich/cocotbext-uart/tarball/master 15 | long_description = file: README.md 16 | long-description-content-type = text/markdown 17 | platforms = any 18 | classifiers = 19 | Development Status :: 3 - Alpha 20 | Framework :: cocotb 21 | License :: OSI Approved :: MIT License 22 | Operating System :: OS Independent 23 | Programming Language :: Python :: 3 24 | Topic :: Scientific/Engineering :: Electronic Design Automation (EDA) 25 | 26 | [options] 27 | packages = find_namespace: 28 | python_requires = >=3.6 29 | install_requires = 30 | cocotb 31 | 32 | [options.extras_require] 33 | test = 34 | pytest 35 | cocotb-test 36 | 37 | [options.packages.find] 38 | include = cocotbext.* 39 | 40 | # pytest configuration 41 | [tool:pytest] 42 | testpaths = 43 | tests 44 | addopts = 45 | --import-mode importlib 46 | 47 | # tox configuration 48 | [tox:tox] 49 | envlist = py36, py37, py38, py39 50 | 51 | [gh-actions] 52 | python = 53 | 3.6: py36 54 | 3.7: py37 55 | 3.8: py38 56 | 3.9: py39 57 | 58 | [testenv] 59 | setenv = 60 | COVERAGE=1 61 | 62 | deps = 63 | pytest 64 | pytest-xdist 65 | cocotb-test 66 | coverage 67 | pytest-cov 68 | 69 | commands = 70 | pytest --cov=cocotbext --cov=tests --cov-branch -n auto 71 | bash -c 'find . -type f -name "\.coverage" | xargs coverage combine --append' 72 | 73 | whitelist_externals = 74 | bash 75 | 76 | # combine if paths are different 77 | [coverage:paths] 78 | source = 79 | cocotbext/ 80 | /*/cocotbext 81 | 82 | # do not report dependencies 83 | [coverage:report] 84 | omit = 85 | .tox/* -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | setup() 3 | -------------------------------------------------------------------------------- /tests/uart/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Alex Forencich 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | TOPLEVEL_LANG = verilog 22 | 23 | SIM ?= icarus 24 | WAVES ?= 0 25 | 26 | COCOTB_HDL_TIMEUNIT = 1ns 27 | COCOTB_HDL_TIMEPRECISION = 1ns 28 | 29 | DUT = test_uart 30 | TOPLEVEL = $(DUT) 31 | MODULE = $(DUT) 32 | VERILOG_SOURCES += $(DUT).v 33 | 34 | ifeq ($(SIM), icarus) 35 | PLUSARGS += -fst 36 | 37 | ifeq ($(WAVES), 1) 38 | VERILOG_SOURCES += iverilog_dump.v 39 | COMPILE_ARGS += -s iverilog_dump 40 | endif 41 | else ifeq ($(SIM), verilator) 42 | COMPILE_ARGS += -Wno-SELRANGE -Wno-WIDTH 43 | 44 | ifeq ($(WAVES), 1) 45 | COMPILE_ARGS += --trace-fst 46 | endif 47 | endif 48 | 49 | include $(shell cocotb-config --makefiles)/Makefile.sim 50 | 51 | iverilog_dump.v: 52 | echo 'module iverilog_dump();' > $@ 53 | echo 'initial begin' >> $@ 54 | echo ' $$dumpfile("$(TOPLEVEL).fst");' >> $@ 55 | echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ 56 | echo 'end' >> $@ 57 | echo 'endmodule' >> $@ 58 | 59 | clean:: 60 | @rm -rf iverilog_dump.v 61 | @rm -rf dump.fst $(TOPLEVEL).fst 62 | -------------------------------------------------------------------------------- /tests/uart/test_uart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | 4 | Copyright (c) 2020 Alex Forencich 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | """ 25 | 26 | import itertools 27 | import logging 28 | import os 29 | 30 | import cocotb_test.simulator 31 | 32 | import cocotb 33 | from cocotb.triggers import Timer 34 | from cocotb.regression import TestFactory 35 | 36 | from cocotbext.uart import UartSource, UartSink 37 | 38 | 39 | class TB: 40 | def __init__(self, dut): 41 | self.dut = dut 42 | 43 | self.log = logging.getLogger("cocotb.tb") 44 | self.log.setLevel(logging.DEBUG) 45 | 46 | self.source = UartSource(dut.data, baud=115200) 47 | self.sink = UartSink(dut.data, baud=115200) 48 | 49 | 50 | async def run_test(dut, payload_lengths=None, payload_data=None): 51 | 52 | tb = TB(dut) 53 | 54 | await Timer(10, 'us') 55 | 56 | for test_data in [payload_data(x) for x in payload_lengths()]: 57 | 58 | await tb.source.write(test_data) 59 | 60 | rx_data = bytearray() 61 | 62 | while len(rx_data) < len(test_data): 63 | rx_data.extend(await tb.sink.read()) 64 | 65 | tb.log.info("Read data: %s", rx_data) 66 | 67 | assert tb.sink.empty() 68 | 69 | await Timer(100, 'us') 70 | 71 | 72 | def prbs31(state=0x7fffffff): 73 | while True: 74 | for i in range(8): 75 | if bool(state & 0x08000000) ^ bool(state & 0x40000000): 76 | state = ((state & 0x3fffffff) << 1) | 1 77 | else: 78 | state = (state & 0x3fffffff) << 1 79 | yield state & 0xff 80 | 81 | 82 | def size_list(): 83 | return list(range(1, 16)) + [128] 84 | 85 | 86 | def incrementing_payload(length): 87 | return bytearray(itertools.islice(itertools.cycle(range(256)), length)) 88 | 89 | 90 | def prbs_payload(length): 91 | gen = prbs31() 92 | return bytearray([next(gen) for x in range(length)]) 93 | 94 | 95 | if cocotb.SIM_NAME: 96 | 97 | factory = TestFactory(run_test) 98 | factory.add_option("payload_lengths", [size_list]) 99 | factory.add_option("payload_data", [incrementing_payload, prbs_payload]) 100 | factory.generate_tests() 101 | 102 | 103 | # cocotb-test 104 | 105 | tests_dir = os.path.dirname(__file__) 106 | 107 | 108 | def test_uart(request): 109 | dut = "test_uart" 110 | module = os.path.splitext(os.path.basename(__file__))[0] 111 | toplevel = dut 112 | 113 | verilog_sources = [ 114 | os.path.join(tests_dir, f"{dut}.v"), 115 | ] 116 | 117 | parameters = {} 118 | 119 | extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()} 120 | 121 | sim_build = os.path.join(tests_dir, "sim_build", 122 | request.node.name.replace('[', '-').replace(']', '')) 123 | 124 | cocotb_test.simulator.run( 125 | python_search=[tests_dir], 126 | verilog_sources=verilog_sources, 127 | toplevel=toplevel, 128 | module=module, 129 | parameters=parameters, 130 | sim_build=sim_build, 131 | extra_env=extra_env, 132 | ) 133 | -------------------------------------------------------------------------------- /tests/uart/test_uart.v: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2020 Alex Forencich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | */ 24 | 25 | // Language: Verilog 2001 26 | 27 | `timescale 1ns / 1ns 28 | 29 | /* 30 | * UART test 31 | */ 32 | module test_uart 33 | ( 34 | inout wire data 35 | ); 36 | 37 | endmodule 38 | --------------------------------------------------------------------------------