├── __init__.py ├── TriState.py ├── LICENSE ├── .gitignore ├── Flow.py ├── Spi.py ├── Phase.py ├── ClockDomain.py ├── Apb3.py ├── Scorboard.py ├── Stream.py ├── Axi4.py ├── misc.py └── AhbLite3.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TriState.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Tristate 3 | # 4 | class TriState: 5 | 6 | #========================================================================== 7 | # Constructor 8 | #========================================================================== 9 | def __init__(self, dut, name): 10 | 11 | # interface 12 | self.read = dut.__getattr__(name + "_read") 13 | self.write = dut.__getattr__(name + "_write") 14 | self.writeEnable = dut.__getattr__(name + "_writeEnable") 15 | 16 | 17 | class TriStateOutput: 18 | 19 | #========================================================================== 20 | # Constructor 21 | #========================================================================== 22 | def __init__(self, dut, name): 23 | 24 | # interface 25 | self.write = dut.__getattr__(name + "_write") 26 | self.writeEnable = dut.__getattr__(name + "_writeEnable") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 SpinalHDL 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /Flow.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import RisingEdge, Event 3 | from cocotblib.misc import Bundle 4 | 5 | 6 | ############################################################################### 7 | # Flow 8 | # 9 | class Flow: 10 | 11 | #========================================================================== 12 | # Constructor 13 | #========================================================================== 14 | def __init__(self, dut, name): 15 | 16 | # interface 17 | self.valid = dut.__getattr__(name + "_valid") 18 | self.payload = Bundle(dut,name + "_payload") 19 | 20 | # Event 21 | self.event_valid = Event() 22 | 23 | 24 | #========================================================================== 25 | # Start to monitor the valid signal 26 | #========================================================================== 27 | def startMonitoringValid(self, clk): 28 | self.clk = clk 29 | self.fork_valid = cocotb.fork(self.monitor_valid()) 30 | 31 | 32 | #========================================================================== 33 | # Stop monitoring 34 | #========================================================================== 35 | def stopMonitoring(self): 36 | self.fork_valid.kill() 37 | 38 | 39 | #========================================================================== 40 | # Monitor the valid signal 41 | #========================================================================== 42 | @cocotb.coroutine 43 | def monitor_valid(self): 44 | while True: 45 | yield RisingEdge(self.clk) 46 | if int(self.valid) == 1: 47 | self.event_valid.set( self.payload ) 48 | -------------------------------------------------------------------------------- /Spi.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import cocotb 4 | from cocotb.decorators import coroutine 5 | from cocotb.result import TestFailure, ReturnValue 6 | from cocotb.triggers import RisingEdge, Edge, Timer 7 | 8 | from cocotblib.TriState import TriStateOutput 9 | from cocotblib.misc import log2Up, BoolRandomizer, assertEquals, testBit 10 | 11 | 12 | class SpiMaster: 13 | def __init__(self, dut, name): 14 | self.sclk = dut.__getattr__(name + "_sclk") 15 | self.mosi = dut.__getattr__(name + "_mosi") 16 | self.miso = dut.__getattr__(name + "_miso") 17 | self.ss = dut.__getattr__(name + "_ss") 18 | 19 | 20 | 21 | 22 | 23 | class SpiSlave: 24 | def __init__(self, dut, name): 25 | self.sclk = dut.__getattr__(name + "_sclk") 26 | self.mosi = dut.__getattr__(name + "_mosi") 27 | self.miso = TriStateOutput(dut,name + "_miso") 28 | self.ss = dut.__getattr__(name + "_ss") 29 | 30 | 31 | 32 | 33 | 34 | class SpiSlaveMaster: 35 | def __init__(self, spi): 36 | self.spi = spi 37 | self.cpol = False 38 | self.cpha = False 39 | self.baudPeriode = 1000 40 | self.dataWidth = 8 41 | 42 | def init(self, cpol, cpha, baudrate, dataWidth = 8): 43 | self.spi.ss <= True 44 | self.cpol = cpol 45 | self.cpha = cpha 46 | self.baudPeriode = baudrate 47 | self.dataWidth = dataWidth 48 | self.spi.sclk <= cpol 49 | 50 | @coroutine 51 | def enable(self): 52 | self.spi.ss <= False 53 | yield Timer(self.baudPeriode) 54 | 55 | @coroutine 56 | def disable(self): 57 | yield Timer(self.baudPeriode) 58 | self.spi.ss <= True 59 | yield Timer(self.baudPeriode) 60 | 61 | @coroutine 62 | def exchange(self, masterData): 63 | buffer = "" 64 | if not self.cpha: 65 | for i in range(self.dataWidth): 66 | self.spi.mosi <= testBit(masterData, self.dataWidth - 1 - i) 67 | yield Timer(self.baudPeriode >> 1) 68 | buffer = buffer + str(self.spi.miso.write) if bool(self.spi.miso.writeEnable) else "x" 69 | self.spi.sclk <= (not self.cpol) 70 | yield Timer(self.baudPeriode >> 1) 71 | self.spi.sclk <= (self.cpol) 72 | else: 73 | for i in range(self.dataWidth): 74 | self.spi.mosi <= testBit(masterData, self.dataWidth -1 - i) 75 | self.spi.sclk <= (not self.cpol) 76 | yield Timer(self.baudPeriode >> 1) 77 | buffer = buffer + str(self.spi.miso.write) if bool(self.spi.miso.writeEnable) else "x" 78 | self.spi.sclk <= (self.cpol) 79 | yield Timer(self.baudPeriode >> 1) 80 | 81 | raise ReturnValue(buffer) 82 | 83 | @coroutine 84 | def exchangeCheck(self, masterData, slaveData): 85 | c = self.exchange(masterData) 86 | yield c 87 | assert slaveData == int(c.retval,2) -------------------------------------------------------------------------------- /Phase.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.result import TestFailure, TestError 3 | from cocotb.triggers import Timer 4 | 5 | PHASE_NULL = 0 6 | PHASE_SIM = 100 7 | PHASE_WAIT_TASKS_END = 200 8 | PHASE_CHECK_SCORBOARDS = 300 9 | PHASE_DONE = 400 10 | 11 | 12 | class Infrastructure: 13 | def __init__(self,name,parent): 14 | self.name = name 15 | self.parent = parent 16 | if parent != None: 17 | parent.addChild(self) 18 | self.children = [] 19 | 20 | def getPhase(self): 21 | return self.parent.getPhase() 22 | 23 | def startPhase(self, phase): 24 | error = False 25 | for child in self.children: 26 | child.startPhase(phase) 27 | 28 | def hasEnoughSim(self): 29 | return True 30 | 31 | def canPhaseProgress(self, phase): 32 | for child in self.children: 33 | if not child.canPhaseProgress(phase): 34 | return False 35 | if phase == PHASE_SIM: 36 | return self.hasEnoughSim() 37 | return True 38 | 39 | def endPhase(self, phase): 40 | for child in self.children: 41 | child.endPhase(phase) 42 | 43 | def addChild(self,child): 44 | if child not in self.children: 45 | self.children.append(child) 46 | 47 | def getPath(self): 48 | if self.parent != None: 49 | return self.parent.getPath() + "/" + self.name 50 | else: 51 | return self.name 52 | 53 | 54 | class PhaseManager(Infrastructure): 55 | def __init__(self): 56 | Infrastructure.__init__(self, None, None) 57 | self.phase = PHASE_NULL 58 | self.name = "top" 59 | self.waitTasksEndTime = 0 60 | # setSimManager(self) 61 | 62 | def setWaitTasksEndTime(self,value): 63 | self.waitTasksEndTime = value 64 | 65 | @cocotb.coroutine 66 | def waitChild(self): 67 | while True: 68 | if self.canPhaseProgress(self.phase): 69 | break 70 | yield Timer(10000) 71 | 72 | def getPhase(self): 73 | return self.phase 74 | 75 | def switchPhase(self,phase): 76 | for infra in self.children: 77 | infra.endPhase(self.phase) 78 | self.phase = phase 79 | for infra in self.children: 80 | infra.startPhase(self.phase) 81 | 82 | @cocotb.coroutine 83 | def run(self): 84 | self.switchPhase(PHASE_SIM) 85 | yield self.waitChild() 86 | self.switchPhase(PHASE_WAIT_TASKS_END) 87 | yield self.waitChild() 88 | yield Timer(self.waitTasksEndTime) 89 | self.switchPhase(PHASE_CHECK_SCORBOARDS) 90 | self.switchPhase(PHASE_DONE) 91 | 92 | # _simManager = None 93 | # 94 | # def getSimManager(): 95 | # return _simManager 96 | # 97 | # def setSimManager(that): 98 | # global _simManager 99 | # _simManager = that 100 | # 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /ClockDomain.py: -------------------------------------------------------------------------------- 1 | import cocotb 2 | from cocotb.triggers import Timer, RisingEdge, Event 3 | 4 | 5 | ############################################################################### 6 | # The different kind of reset active level 7 | # 8 | class RESET_ACTIVE_LEVEL: 9 | HIGH = 1 10 | LOW = 0 11 | 12 | 13 | ############################################################################### 14 | # Clock 15 | # 16 | # Usage : 17 | # 18 | # # Create a clock with a reset active high 19 | # clockDomain = ClockDomain(dut.clk, 400, dut.reset, RESET_ACTIVE_LEVEL.HIGH) 20 | # cocobt.fork( clockDomain.start() ) 21 | # 22 | class ClockDomain: 23 | 24 | 25 | ########################################################################## 26 | # Constructor 27 | # 28 | # @param clk : Clock generated 29 | # @param halfPeriod : Half period time 30 | # @param reset : Reset generated 31 | # @param resetactiveLevel : Reset active low or high 32 | def __init__(self, clk, halfPeriod, reset=None, resetActiveLevel=RESET_ACTIVE_LEVEL.LOW): 33 | 34 | self.halfPeriod = halfPeriod 35 | 36 | self.clk = clk 37 | self.reset = reset 38 | self.typeReset = resetActiveLevel 39 | 40 | self.event_endReset = Event() 41 | 42 | 43 | ########################################################################## 44 | # Generate the clock signals 45 | @cocotb.coroutine 46 | def start(self): 47 | 48 | self.fork_gen = cocotb.fork(self._clkGen()) 49 | if self.reset != None : 50 | cocotb.fork(self._waitEndReset()) 51 | 52 | if self.reset: 53 | self.reset <= self.typeReset 54 | 55 | yield Timer(self.halfPeriod * 5) 56 | 57 | if self.reset: 58 | self.reset <= int(1 if self.typeReset == RESET_ACTIVE_LEVEL.LOW else 0) 59 | 60 | 61 | ########################################################################## 62 | # Stop all processes 63 | def stop(self): 64 | 65 | self.fork_gen.kill() 66 | 67 | 68 | ########################################################################## 69 | # Generate the clk 70 | @cocotb.coroutine 71 | def _clkGen(self): 72 | while True: 73 | self.clk <= 0 74 | yield Timer(self.halfPeriod) 75 | self.clk <= 1 76 | yield Timer(self.halfPeriod) 77 | 78 | 79 | ########################################################################## 80 | # Wait the end of the reset 81 | @cocotb.coroutine 82 | def _waitEndReset(self): 83 | while True: 84 | yield RisingEdge(self.clk) 85 | valueReset = int(1 if self.typeReset == RESET_ACTIVE_LEVEL.LOW else 0) 86 | if int(self.reset) == valueReset: 87 | self.event_endReset.set() 88 | break; 89 | 90 | 91 | ########################################################################## 92 | # Display the frequency of the clock domain 93 | def __str__(self): 94 | return self.__class__.__name__ + "(%3.1fMHz)" % self.frequency 95 | -------------------------------------------------------------------------------- /Apb3.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import cocotb 4 | from cocotb.decorators import coroutine 5 | from cocotb.result import TestFailure, ReturnValue 6 | from cocotb.triggers import RisingEdge, Edge 7 | 8 | from cocotblib.misc import log2Up, BoolRandomizer, assertEquals, waitClockedCond, randSignal 9 | 10 | 11 | class Apb3: 12 | def __init__(self, dut, name, clk = None): 13 | self.clk = clk 14 | self.PADDR = dut.__getattr__(name + "_PADDR") 15 | self.PSEL = dut.__getattr__(name + "_PSEL") 16 | self.PENABLE = dut.__getattr__(name + "_PENABLE") 17 | self.PREADY = dut.__getattr__(name + "_PREADY") 18 | self.PWRITE = dut.__getattr__(name + "_PWRITE") 19 | self.PWDATA = dut.__getattr__(name + "_PWDATA") 20 | self.PRDATA = dut.__getattr__(name + "_PRDATA") 21 | 22 | def idle(self): 23 | self.PSEL <= 0 24 | 25 | @coroutine 26 | def delay(self, cycle): 27 | for i in range(cycle): 28 | yield RisingEdge(self.clk) 29 | 30 | @coroutine 31 | def write(self, address, data, sel = 1): 32 | self.PADDR <= address 33 | self.PSEL <= sel 34 | self.PENABLE <= False 35 | self.PWRITE <= True 36 | self.PWDATA <= data 37 | yield RisingEdge(self.clk) 38 | self.PENABLE <= True 39 | yield waitClockedCond(self.clk, lambda : self.PREADY == True) 40 | randSignal(self.PADDR) 41 | self.PSEL <= 0 42 | randSignal(self.PENABLE) 43 | randSignal(self.PWRITE) 44 | randSignal(self.PWDATA) 45 | 46 | @coroutine 47 | def writeMasked(self, address, data, mask, sel = 1): 48 | readThread = self.read(address,sel) 49 | yield readThread 50 | yield self.write(address,(readThread.retval & ~mask) | (data & mask),sel) 51 | 52 | @coroutine 53 | def read(self, address, sel=1): 54 | self.PADDR <= address 55 | self.PSEL <= sel 56 | self.PENABLE <= False 57 | self.PWRITE <= False 58 | randSignal(self.PWDATA) 59 | yield RisingEdge(self.clk) 60 | self.PENABLE <= True 61 | yield waitClockedCond(self.clk, lambda: self.PREADY == True) 62 | randSignal(self.PADDR) 63 | self.PSEL <= 0 64 | randSignal(self.PENABLE) 65 | randSignal(self.PWRITE) 66 | raise ReturnValue(int(self.PRDATA)) 67 | 68 | 69 | @coroutine 70 | def readAssert(self, address, data, sel=1): 71 | readThread = self.read(address,sel) 72 | yield readThread 73 | assertEquals(int(readThread.retval), data," APB readAssert failure") 74 | 75 | @coroutine 76 | def readAssertMasked(self, address, data, mask, sel=1): 77 | readThread = self.read(address,sel) 78 | yield readThread 79 | assertEquals(int(readThread.retval) & mask, data," APB readAssert failure") 80 | 81 | @coroutine 82 | def pull(self, address, dataValue, dataMask, sel=1): 83 | while True: 84 | readThread = self.read(address, sel) 85 | yield readThread 86 | if (int(readThread.retval) & dataMask) == dataValue: 87 | break -------------------------------------------------------------------------------- /Scorboard.py: -------------------------------------------------------------------------------- 1 | from queue import Queue 2 | 3 | import cocotb 4 | from cocotb.result import TestFailure 5 | 6 | from cocotblib.Phase import Infrastructure, PHASE_CHECK_SCORBOARDS 7 | 8 | 9 | class ScorboardInOrder(Infrastructure): 10 | def __init__(self,name,parent): 11 | Infrastructure.__init__(self,name,parent) 12 | self.refs = Queue() 13 | self.uuts = Queue() 14 | self.refsCounter = 0 15 | self.uutsCounter = 0 16 | 17 | def refPush(self,ref): 18 | self.refs.put(ref) 19 | self.refsCounter += 1 20 | self.update() 21 | 22 | def uutPush(self,uut): 23 | self.uuts.put(uut) 24 | self.uutsCounter += 1 25 | self.update() 26 | 27 | def update(self): 28 | if (not self.refs.empty()) and (not self.uuts.empty()): 29 | ref = self.refs.get() 30 | uut = self.uuts.get() 31 | 32 | self.match(uut,ref) 33 | 34 | 35 | def match(self,uut,ref): 36 | if not uut.equalRef(ref): 37 | cocotb.log.error("Missmatch detected in " + self.getPath()) 38 | uut.assertEqualRef(ref) 39 | 40 | def startPhase(self, phase): 41 | Infrastructure.startPhase(self, phase) 42 | if phase == PHASE_CHECK_SCORBOARDS: 43 | if (not self.refs.empty()) or (not self.uuts.empty()): 44 | error = self.getPath() + " has some remaining transaction :\n" 45 | for e in self.refs.queue: 46 | error += "REF:\n" + str(e) + "\n" 47 | 48 | for e in self.uuts.queue: 49 | error += "UUT:\n" + str(e) + "\n" 50 | 51 | cocotb.log.error(error) 52 | 53 | 54 | def endPhase(self, phase): 55 | Infrastructure.endPhase(self, phase) 56 | if phase == PHASE_CHECK_SCORBOARDS: 57 | if (not self.refs.empty()) or (not self.uuts.empty()): 58 | raise TestFailure("Scoreboard not empty") 59 | 60 | 61 | class ScorboardOutOfOrder(Infrastructure): 62 | def __init__(self,name,parent): 63 | Infrastructure.__init__(self,name,parent) 64 | self.refsDic = {} 65 | self.uutsDic = {} 66 | self.listeners = [] 67 | 68 | 69 | def addListener(self,func): 70 | self.listeners.append(func) 71 | 72 | def refPush(self,ref,oooid): 73 | if oooid not in self.refsDic: 74 | self.refsDic[oooid] = Queue() 75 | self.refsDic[oooid].put(ref) 76 | self.update(oooid) 77 | 78 | def uutPush(self, uut, oooid): 79 | if oooid not in self.uutsDic: 80 | self.uutsDic[oooid] = Queue() 81 | self.uutsDic[oooid].put(uut) 82 | self.update(oooid) 83 | 84 | def update(self,oooid): 85 | if oooid in self.uutsDic and oooid in self.refsDic: 86 | refs = self.refsDic[oooid] 87 | uuts = self.uutsDic[oooid] 88 | 89 | ref = refs.get() 90 | uut = uuts.get() 91 | 92 | self.match(uut,ref) 93 | 94 | #Clean 95 | if refs.empty(): 96 | self.refsDic.pop(oooid) 97 | if uuts.empty(): 98 | self.uutsDic.pop(oooid) 99 | 100 | 101 | def match(self,uut,ref): 102 | equal = uut.equalRef(ref) 103 | for l in self.listeners: 104 | l(uut,ref,equal) 105 | 106 | if not equal: 107 | cocotb.log.error("Missmatch detected in " + self.getPath()) 108 | uut.assertEqualRef(ref) 109 | 110 | def startPhase(self, phase): 111 | Infrastructure.startPhase(self, phase) 112 | if phase == PHASE_CHECK_SCORBOARDS: 113 | if len(self.refsDic) != 0 or len(self.uutsDic) != 0: 114 | error = self.getPath() + " has some remaining transaction :\n" 115 | for l in self.refsDic.values(): 116 | for e in l.queue: 117 | error += "REF:\n" + str(e) + "\n" 118 | 119 | for l in self.uutsDic.values(): 120 | for e in l.queue: 121 | error += "UUT:\n" + str(e) + "\n" 122 | 123 | cocotb.log.error(error) 124 | 125 | 126 | def endPhase(self, phase): 127 | Infrastructure.endPhase(self, phase) 128 | if phase == PHASE_CHECK_SCORBOARDS: 129 | if len(self.refsDic) != 0 or len(self.uutsDic) != 0: 130 | raise TestFailure("Scoreboard not empty") 131 | 132 | -------------------------------------------------------------------------------- /Stream.py: -------------------------------------------------------------------------------- 1 | 2 | import cocotb 3 | import types 4 | from cocotb.result import TestFailure 5 | from cocotb.triggers import RisingEdge, Timer, Event 6 | from cocotblib.Phase import Infrastructure, PHASE_WAIT_TASKS_END 7 | from cocotblib.Scorboard import ScorboardInOrder 8 | 9 | from cocotblib.misc import Bundle, BoolRandomizer 10 | 11 | 12 | class Stream: 13 | def __init__(self, dut, name): 14 | self.valid = dut.__getattr__(name + "_valid") 15 | self.ready = dut.__getattr__(name + "_ready") 16 | self.payload = Bundle(dut,name + "_payload") 17 | # Event 18 | self.event_ready = Event() 19 | self.event_valid = Event() 20 | 21 | def startMonitoringReady(self, clk): 22 | self.clk = clk 23 | self.fork_ready = cocotb.fork(self.monitor_ready()) 24 | 25 | def startMonitoringValid(self, clk): 26 | self.clk = clk 27 | self.fork_valid = cocotb.fork(self.monitor_valid()) 28 | 29 | def stopMonitoring(self): 30 | self.fork_ready.kill() 31 | self.fork_valid.kill() 32 | 33 | @cocotb.coroutine 34 | def monitor_ready(self): 35 | while True: 36 | yield RisingEdge(self.clk) 37 | if int(self.ready) == 1: 38 | self.event_ready.set( self.payload ) 39 | 40 | @cocotb.coroutine 41 | def monitor_valid(self): 42 | while True: 43 | yield RisingEdge(self.clk) 44 | if int(self.valid) == 1: 45 | self.event_valid.set( self.payload ) 46 | 47 | 48 | class Transaction(object): 49 | def __init__(self): 50 | object.__setattr__(self,"_nameToElement",{}) 51 | 52 | def __setattr__(self, key, value): 53 | # print("set " + key) 54 | if key[0] != '_': 55 | self._nameToElement[key] = value 56 | object.__setattr__(self,key,value) 57 | 58 | def equalRef(self,ref): 59 | # if(len(self._nameToElement) != len(ref._nameToElement)): 60 | # return False 61 | for name in self._nameToElement: 62 | refValue = getattr(ref,name) 63 | if refValue != None and self._nameToElement[name] != getattr(ref,name): 64 | return False 65 | return True 66 | 67 | def assertEqualRef(self,ref): 68 | if not self.equalRef(ref): 69 | raise TestFailure("\nFAIL transaction not equal\ntransaction =>\n%s\nref =>\n%s\n\n" % (self,ref)) 70 | 71 | 72 | def __str__(self): 73 | buffer = "" 74 | biggerName = 0 75 | for n in self._nameToElement: 76 | if len(n) > biggerName: 77 | biggerName = len(n) 78 | for name in self._nameToElement: 79 | e = self._nameToElement[name] 80 | buffer += "%s %s: 0x%x\n" % (name," "*(biggerName-len(name)), 0 if e == None else e) 81 | return buffer 82 | 83 | # Transaction = type('Transaction', (object,), {}) 84 | 85 | class StreamDriverMaster: 86 | def __init__(self,stream,transactor,clk,reset): 87 | self.stream = stream 88 | self.clk = clk 89 | self.reset = reset 90 | self.transactor = transactor 91 | 92 | cocotb.fork(self.stim()) 93 | 94 | @cocotb.coroutine 95 | def stim(self): 96 | stream = self.stream 97 | stream.valid <= 0 98 | while True: 99 | yield RisingEdge(self.clk) 100 | if int(stream.valid) == 1 and int(stream.ready) == 1: 101 | stream.valid <= 0 102 | for i in range(nextDelay): 103 | yield RisingEdge(self.clk) 104 | 105 | if self.transactor != None and (int(stream.valid) == 0 or int(stream.ready) == 1): 106 | if isinstance(self.transactor,types.GeneratorType): 107 | trans = next(self.transactor) 108 | else: 109 | trans = self.transactor() 110 | if trans != None: 111 | if hasattr(trans,"nextDelay"): 112 | nextDelay = trans.nextDelay 113 | else: 114 | nextDelay = 0 115 | stream.valid <= 1 116 | 117 | for name in stream.payload.nameToElement: 118 | if hasattr(trans,name) == False: 119 | raise Exception("Missing element in bundle :" + name) 120 | e = stream.payload.nameToElement[name] <= getattr(trans,name) 121 | 122 | 123 | 124 | class StreamDriverSlave: 125 | def __init__(self,stream,clk,reset): 126 | self.stream = stream 127 | self.clk = clk 128 | self.reset = reset 129 | self.randomizer = BoolRandomizer() 130 | cocotb.fork(self.stim()) 131 | 132 | @cocotb.coroutine 133 | def stim(self): 134 | stream = self.stream 135 | stream.ready <= 1 136 | while True: 137 | yield RisingEdge(self.clk) 138 | stream.ready <= self.randomizer.get() 139 | 140 | 141 | def TransactionFromBundle(bundle): 142 | trans = Transaction() 143 | for name in bundle.nameToElement: 144 | setattr(trans,name, int(bundle.nameToElement[name])) 145 | return trans 146 | 147 | 148 | class StreamMonitor: 149 | def __init__(self,stream,callback,clk,reset): 150 | self.stream = stream 151 | self.callback = callback 152 | self.clk = clk 153 | self.reset = reset 154 | cocotb.fork(self.stim()) 155 | 156 | @cocotb.coroutine 157 | def stim(self): 158 | stream = self.stream 159 | while True: 160 | yield RisingEdge(self.clk) 161 | if int(stream.valid) == 1 and int(stream.ready) == 1: 162 | trans = TransactionFromBundle(stream.payload) 163 | yield Timer(1) 164 | self.callback(trans) 165 | 166 | 167 | 168 | 169 | class StreamFifoTester(Infrastructure): 170 | def __init__(self,name,parent,pushStream,popStream,transactionGenerator,dutCounterTarget,clk,reset): 171 | Infrastructure.__init__(self,name,parent) 172 | self.pushStream = pushStream 173 | self.popStream = popStream 174 | self.clk = clk 175 | self.reset = reset 176 | self.dutCounter = 0 177 | self.closeIt = False 178 | self.transactionGenerator = transactionGenerator 179 | self.dutCounterTarget = dutCounterTarget 180 | self.pushRandomizer = BoolRandomizer() 181 | self.scoreboard = ScorboardInOrder("scoreboard", self) 182 | 183 | def createInfrastructure(self): 184 | StreamDriverMaster(self.pushStream, self.genPush, self.clk, self.reset) 185 | StreamDriverSlave(self.popStream, self.clk, self.reset) 186 | StreamMonitor(self.popStream, self.onUut, self.clk, self.reset) 187 | StreamMonitor(self.pushStream, self.onRef, self.clk, self.reset) 188 | 189 | def startPhase(self, phase): 190 | Infrastructure.startPhase(self, phase) 191 | if phase == PHASE_WAIT_TASKS_END: 192 | self.closeIt = True 193 | 194 | def genPush(self): 195 | if not self.closeIt and self.pushRandomizer.get(): 196 | return self.transactionGenerator() 197 | 198 | def onUut(self, uut): 199 | self.dutCounter += 1 200 | self.scoreboard.uutPush(uut) 201 | 202 | def onRef(self, uut): 203 | self.scoreboard.refPush(uut) 204 | 205 | def canPhaseProgress(self, phase): 206 | return self.dutCounter > self.dutCounterTarget 207 | 208 | 209 | -------------------------------------------------------------------------------- /Axi4.py: -------------------------------------------------------------------------------- 1 | import random 2 | from queue import Queue 3 | 4 | from cocotblib.Phase import PHASE_SIM, Infrastructure 5 | from cocotblib.Scorboard import ScorboardOutOfOrder 6 | from cocotblib.misc import BoolRandomizer, log2Up, randBits 7 | 8 | from cocotblib.Stream import Stream, Transaction, StreamDriverSlave, StreamDriverMaster, StreamMonitor 9 | 10 | 11 | class Axi4: 12 | def __init__(self,dut,name): 13 | self.ar = Stream(dut,name + "_ar") 14 | self.r = Stream(dut, name + "_r") 15 | self.aw = Stream(dut, name + "_aw") 16 | self.w = Stream(dut, name + "_w") 17 | self.b = Stream(dut, name + "_b") 18 | 19 | class Axi4ReadOnly: 20 | def __init__(self,dut,name): 21 | self.ar = Stream(dut,name + "_ar") 22 | self.r = Stream(dut, name + "_r") 23 | 24 | class Axi4WriteOnly: 25 | def __init__(self,dut,name): 26 | self.aw = Stream(dut, name + "_aw") 27 | self.w = Stream(dut, name + "_w") 28 | self.b = Stream(dut, name + "_b") 29 | 30 | class Axi4Shared: 31 | def __init__(self,dut,name): 32 | self.arw = Stream(dut,name + "_arw") 33 | self.r = Stream(dut, name + "_r") 34 | self.w = Stream(dut, name + "_w") 35 | self.b = Stream(dut, name + "_b") 36 | 37 | 38 | def Axi4AddrIncr(address,burst,len,size): 39 | if burst == 0: 40 | return address 41 | if burst == 1: 42 | return address + (1 << size) 43 | if burst == 2: 44 | burstSize = (1 << size) * (len+1) 45 | burstMask = burstSize-1 46 | base = (address + (1 << size)) & burstMask 47 | return (address & ~burstMask) | base 48 | 49 | 50 | 51 | class Axi4SharedMemoryChecker(Infrastructure): 52 | def __init__(self,name,parent,axi,addressWidth,clk,reset): 53 | Infrastructure.__init__(self,name,parent) 54 | self.axi = axi 55 | self.idWidth = len(axi.arw.payload.hid) 56 | self.addressWidth = addressWidth 57 | self.ram = bytearray(b'\x00' * ((1 << addressWidth)*len(axi.w.payload.data)//8)) 58 | self.doReadWriteCmdRand = BoolRandomizer() 59 | self.readWriteRand = BoolRandomizer() 60 | self.writeDataRand = BoolRandomizer() 61 | self.writeRspScoreboard = ScorboardOutOfOrder("writeRspScoreboard", self) 62 | self.readRspScoreboard = ScorboardOutOfOrder("readRspScoreboard", self) 63 | self.writeRspScoreboard.addListener(self.freeReservatedAddresses) 64 | self.readRspScoreboard.addListener(self.freeReservatedAddresses) 65 | self.cmdTasks = Queue() 66 | self.writeTasks = Queue() 67 | self.nonZeroReadRspCounter = 0 68 | self.nonZeroReadRspCounterTarget = 1000 69 | self.reservedAddresses = {} 70 | self.dataWidth = len(axi.w.payload.data) 71 | StreamDriverSlave(axi.r, clk, reset) 72 | StreamDriverSlave(axi.b, clk, reset) 73 | StreamDriverMaster(axi.arw, self.genReadWriteCmd, clk, reset) 74 | StreamDriverMaster(axi.w, self.genWriteData, clk, reset) 75 | StreamMonitor(axi.r, self.onReadRsp, clk, reset) 76 | StreamMonitor(axi.b, self.onWriteRsp, clk, reset) 77 | axi.w.payload.last <= 0 78 | axi.r.payload.last <= 0 79 | 80 | def freeReservatedAddresses(self,uut,ref,equal): 81 | self.reservedAddresses.pop(ref,None) 82 | 83 | def isAddressRangeBusy(self,start,end): 84 | for r in self.reservedAddresses.values(): 85 | if start < r[1] and end > r[0]: 86 | return True 87 | return False 88 | 89 | def genRandomeAddress(self): 90 | return randBits(self.addressWidth) 91 | 92 | def genNewCmd(self): 93 | cmd = Transaction() 94 | cmd.hid = randBits(self.idWidth) # Each master can use 4 id 95 | cmd.region = randBits(4) 96 | cmd.len = randBits(4) 97 | cmd.size = random.randint(0,log2Up(self.dataWidth//8)) 98 | cmd.burst = random.randint(0,2) 99 | if cmd.burst == 2: 100 | cmd.len = random.choice([2,4,8,16])-1 101 | else: 102 | cmd.len = randBits(4) + (16 if random.random() < 0.1 else 0) + (32 if random.random() < 0.02 else 0) 103 | cmd.lock = randBits(1) 104 | cmd.cache = randBits(4) 105 | cmd.qos = randBits(4) 106 | cmd.prot = randBits(3) 107 | 108 | byteCount = (1 << cmd.size)*(cmd.len + 1) 109 | while(True): 110 | cmd.addr = self.genRandomeAddress() & ~((1 << cmd.size)-1) 111 | if cmd.burst == 1: 112 | if cmd.addr + byteCount >= (1<> s) & 1 == 1: 142 | self.ram[(beatAddr & ~(self.dataWidth//8-1)) + s] = (dataTrans.data >> (s*8)) & 0xFF 143 | beatAddr = Axi4AddrIncr(beatAddr,cmd.burst,cmd.len,cmd.size) 144 | 145 | writeRsp = Transaction() 146 | writeRsp.resp = 0 147 | writeRsp.hid = cmd.hid 148 | 149 | self.reservedAddresses[writeRsp] = [start,end] 150 | self.writeRspScoreboard.refPush(writeRsp,writeRsp.hid) 151 | else: 152 | cmd.write = 0 153 | 154 | beatAddr = cmd.addr 155 | for s in range(cmd.len + 1): 156 | readRsp = Transaction() 157 | addrBase = beatAddr & ~(self.dataWidth//8-1) 158 | readRsp.data = 0 159 | for i in range(self.dataWidth // 8): 160 | readRsp.data |= self.ram[addrBase + i] << (i*8) 161 | readRsp.resp = 0 162 | readRsp.last = 1 if cmd.len == s else 0 163 | readRsp.hid = cmd.hid 164 | if readRsp.last == 1: 165 | self.reservedAddresses[readRsp] = [start, end] 166 | self.readRspScoreboard.refPush(readRsp, readRsp.hid) 167 | beatAddr = Axi4AddrIncr(beatAddr, cmd.burst, cmd.len, cmd.size) 168 | 169 | self.cmdTasks.put(cmd) 170 | # print(str(len(self.cmdTasks.queue)) + " " + str(len(self.writeTasks.queue))) 171 | 172 | 173 | def genReadWriteCmd(self): 174 | if self.doReadWriteCmdRand.get(): 175 | while self.cmdTasks.empty(): 176 | if self.getPhase() != PHASE_SIM: 177 | return None 178 | self.genNewCmd() 179 | return self.cmdTasks.get() 180 | 181 | def genWriteData(self): 182 | if self.writeDataRand.get(): 183 | while self.writeTasks.empty(): 184 | if self.getPhase() != PHASE_SIM: 185 | return None 186 | self.genNewCmd() 187 | return self.writeTasks.get() 188 | 189 | def onWriteRsp(self,trans): 190 | self.writeRspScoreboard.uutPush(trans,trans.hid) 191 | 192 | def onReadRsp(self, trans): 193 | self.readRspScoreboard.uutPush(trans, trans.hid) 194 | if trans.data != 0: 195 | self.nonZeroReadRspCounter += 1 196 | if self.nonZeroReadRspCounter % 50 == 0: 197 | print("progress=" + str(self.nonZeroReadRspCounter)) 198 | 199 | # override 200 | def hasEnoughSim(self): 201 | return self.nonZeroReadRspCounter > self.nonZeroReadRspCounterTarget 202 | -------------------------------------------------------------------------------- /misc.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import cocotb 4 | from cocotb.binary import BinaryValue 5 | from cocotb.decorators import coroutine 6 | from cocotb.result import TestFailure 7 | from cocotb.triggers import Timer, RisingEdge 8 | 9 | 10 | def cocotbXHack(): 11 | if hasattr(BinaryValue,"_resolve_to_0"): 12 | # cocotb <= 1.4.0 13 | BinaryValue._resolve_to_0 = BinaryValue._resolve_to_0 + BinaryValue._resolve_to_error 14 | BinaryValue._resolve_to_error = "" 15 | elif hasattr(cocotb.binary, "resolve_x_to"): 16 | if hasattr(cocotb.binary, "_ResolveXToValue"): 17 | # cocotb 1.9.0+ 18 | cocotb.binary.resolve_x_to = cocotb.binary._ResolveXToValue.ZEROS 19 | else: 20 | # cocotb 1.5.0 ~ 1.8.x 21 | cocotb.binary.resolve_x_to = "ZEROS" 22 | cocotb.binary._resolve_table = cocotb.binary._ResolveTable() 23 | 24 | def log2Up(value): 25 | return value.bit_length()-1 26 | 27 | def randInt(min,max): 28 | return random.randint(min, max) 29 | 30 | def randBool(): 31 | return bool(random.getrandbits(1)) 32 | 33 | def randBits(width): 34 | return random.getrandbits(width) 35 | 36 | def randSignal(that): 37 | that <= random.getrandbits(len(that)) 38 | 39 | def randBoolSignal(that,prob): 40 | that <= (random.random() < prob) 41 | 42 | 43 | @coroutine 44 | def clockedWaitTrue(clk,that): 45 | while True: 46 | yield RisingEdge(clk) 47 | if that == True: 48 | break 49 | 50 | def assertEquals(a, b, name): 51 | if int(a) != int(b): 52 | raise TestFailure("FAIL %s %d != %d" % (name,int(a),int(b))) 53 | 54 | def truncUInt(value, signal): 55 | if isinstance( signal, int ): 56 | return value & ((1 << signal)-1) 57 | else: 58 | return value & ((1 << len(signal)) - 1) 59 | 60 | def truncSInt(value, signal): 61 | if isinstance( signal, int ): 62 | bitCount = signal 63 | else: 64 | bitCount = len(signal) 65 | masked = value & ((1 << bitCount)-1) 66 | if (masked & (1 << bitCount-1)) != 0: 67 | return - (1 << bitCount) + masked 68 | else: 69 | return masked 70 | 71 | 72 | def setBit(v, index, x): 73 | mask = 1 << index 74 | v &= ~mask 75 | if x: 76 | v |= mask 77 | return v 78 | 79 | def testBit(int_type, offset): 80 | mask = 1 << offset 81 | return (int_type & mask) != 0 82 | 83 | def uint(signal): 84 | return signal.value.integer 85 | 86 | def sint(signal): 87 | return signal.value.signed_integer 88 | 89 | 90 | @cocotb.coroutine 91 | def ClockDomainAsyncReset(clk,reset,period = 1000): 92 | if reset: 93 | reset <= 1 94 | clk <= 0 95 | yield Timer(period) 96 | if reset: 97 | reset <= 0 98 | while True: 99 | clk <= 0 100 | yield Timer(period/2) 101 | clk <= 1 102 | yield Timer(period/2) 103 | 104 | @cocotb.coroutine 105 | def SimulationTimeout(duration): 106 | yield Timer(duration) 107 | raise TestFailure("Simulation timeout") 108 | 109 | 110 | import time 111 | @cocotb.coroutine 112 | def simulationSpeedPrinter(clk): 113 | counter = 0 114 | lastTime = time.time() 115 | while True: 116 | yield RisingEdge(clk) 117 | counter += 1 118 | thisTime = time.time() 119 | if thisTime - lastTime >= 1.0: 120 | lastTime = thisTime 121 | print("Sim speed : %f khz" %(counter/1000.0)) 122 | counter = 0 123 | 124 | 125 | 126 | class BoolRandomizer: 127 | def __init__(self): 128 | self.prob = 0.5 129 | self.counter = 0 130 | self.probLow = 0.1 131 | self.probHigh = 0.9 132 | 133 | def get(self): 134 | self.counter += 1 135 | if self.counter == 100: 136 | self.counter = 0 137 | self.prob = random.uniform(self.probLow, self.probHigh) 138 | return random.random() < self.prob 139 | 140 | 141 | 142 | # class Stream: 143 | # def __init__(self,name,dut): 144 | # self.valid = getattr(dut, name + "_valid") 145 | # self.ready = getattr(dut, name + "_ready") 146 | # payloads = [a for a in dut if a._name.startswith(name + "_payload")] 147 | # if len(payloads) == 1 and payloads[0]._name == name + "_payload": 148 | # self.payload = payloads[0] 149 | 150 | 151 | 152 | MyObject = type('MyObject', (object,), {}) 153 | 154 | @cocotb.coroutine 155 | def StreamRandomizer(streamName, onNew,handle, dut, clk): 156 | validRandomizer = BoolRandomizer() 157 | valid = getattr(dut, streamName + "_valid") 158 | ready = getattr(dut, streamName + "_ready") 159 | payloads = [a for a in dut if a._name.startswith(streamName + "_payload")] 160 | 161 | valid <= 0 162 | while True: 163 | yield RisingEdge(clk) 164 | if int(ready) == 1: 165 | valid <= 0 166 | 167 | if int(valid) == 0 or int(ready) == 1: 168 | if validRandomizer.get(): 169 | valid <= 1 170 | for e in payloads: 171 | randSignal(e) 172 | yield Timer(1) 173 | if len(payloads) == 1 and payloads[0]._name == streamName + "_payload": 174 | payload = int(payloads[0]) 175 | else: 176 | payload = MyObject() 177 | for e in payloads: 178 | payload.__setattr__(e._name[len(streamName + "_payload_"):], int(e)) 179 | if onNew: 180 | onNew(payload,handle) 181 | 182 | @cocotb.coroutine 183 | def FlowRandomizer(streamName, onNew,handle, dut, clk): 184 | validRandomizer = BoolRandomizer() 185 | valid = getattr(dut, streamName + "_valid") 186 | payloads = [a for a in dut if a._name.startswith(streamName + "_payload")] 187 | 188 | valid <= 0 189 | while True: 190 | yield RisingEdge(clk) 191 | if validRandomizer.get(): 192 | valid <= 1 193 | for e in payloads: 194 | randSignal(e) 195 | yield Timer(1) 196 | if len(payloads) == 1 and payloads[0]._name == streamName + "_payload": 197 | payload = int(payloads[0]) 198 | else: 199 | payload = MyObject() 200 | for e in payloads: 201 | payload.__setattr__(e._name[len(streamName + "_payload_"):], int(e)) 202 | if onNew: 203 | onNew(payload,handle) 204 | else: 205 | valid <= 0 206 | 207 | @cocotb.coroutine 208 | def StreamReader(streamName, onTransaction, handle, dut, clk): 209 | validRandomizer = BoolRandomizer() 210 | valid = getattr(dut, streamName + "_valid") 211 | ready = getattr(dut, streamName + "_ready") 212 | payloads = [a for a in dut if a._name.startswith(streamName + "_payload")] 213 | 214 | ready <= 0 215 | while True: 216 | yield RisingEdge(clk) 217 | ready <= validRandomizer.get() 218 | if int(valid) == 1 and int(ready) == 1: 219 | if len(payloads) == 1 and payloads[0]._name == streamName + "_payload": 220 | payload = int(payloads[0]) 221 | else: 222 | payload = MyObject() 223 | for e in payloads: 224 | payload.__setattr__(e._name[len(streamName + "_payload_"):], int(e)) 225 | 226 | if onTransaction: 227 | onTransaction(payload,handle) 228 | 229 | 230 | 231 | class Bundle: 232 | def __init__(self,dut,name): 233 | self.nameToElement = {} 234 | self.elements = [a for a in dut if (a._name.lower().startswith(name.lower() + "_") and not a._name.lower().endswith("_readablebuffer"))] 235 | 236 | for e in [a for a in dut if a._name == name]: 237 | self.elements.append(e) 238 | 239 | for element in self.elements: 240 | # print("append " + element._name + " with name : " + element._name[len(name) + 1:]) 241 | if len(name) == len(element._name): 242 | eName = "itself" 243 | else: 244 | eName = element._name[len(name) + 1:] 245 | 246 | if eName == "id": 247 | eName = "hid" 248 | self.nameToElement[eName] = element 249 | 250 | def __getattr__(self, name): 251 | if name not in self.nameToElement: 252 | for e in self.nameToElement: 253 | print(e) 254 | return self.nameToElement[name] 255 | 256 | 257 | 258 | def readIHex(path, callback,context): 259 | with open(path) as f: 260 | offset = 0 261 | for line in f: 262 | if len(line) > 0: 263 | assert line[0] == ':' 264 | byteCount = int(line[1:3], 16) 265 | nextAddr = int(line[3:7], 16) + offset 266 | key = int(line[7:9], 16) 267 | if key == 0: 268 | array = [int(line[9 + i * 2:11 + i * 2], 16) for i in range(0, byteCount)] 269 | callback(nextAddr,array,context) 270 | elif key == 2: 271 | offset = int(line[9:13], 16) 272 | else: 273 | pass 274 | 275 | 276 | 277 | @coroutine 278 | def TriggerAndCond(trigger, cond): 279 | while(True): 280 | yield trigger 281 | if cond: 282 | break 283 | 284 | 285 | @coroutine 286 | def waitClockedCond(clk, cond): 287 | while(True): 288 | yield RisingEdge(clk) 289 | if cond(): 290 | break 291 | 292 | 293 | 294 | @coroutine 295 | def TimerClk(clk, count): 296 | for i in range(count): 297 | yield RisingEdge(clk) -------------------------------------------------------------------------------- /AhbLite3.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import cocotb 4 | from cocotb.result import TestFailure 5 | from cocotb.triggers import RisingEdge, Edge 6 | 7 | from cocotblib.misc import log2Up, BoolRandomizer, assertEquals 8 | 9 | 10 | def AhbLite3MasterIdle(ahb): 11 | ahb.HADDR <= 0 12 | ahb.HWRITE <= 0 13 | ahb.HSIZE <= 0 14 | ahb.HBURST <= 0 15 | ahb.HPROT <= 0 16 | ahb.HTRANS <= 0 17 | ahb.HMASTLOCK <= 0 18 | ahb.HWDATA <= 0 19 | 20 | 21 | 22 | class AhbLite3Transaction: 23 | def __init__(self): 24 | self.HADDR = 0 25 | self.HWRITE = 0 26 | self.HSIZE = 0 27 | self.HBURST = 0 28 | self.HPROT = 0 29 | self.HTRANS = 0 30 | self.HMASTLOCK = 0 31 | self.HWDATA = 0 32 | 33 | class AhbLite3TraficGenerator: 34 | def __init__(self,addressWidth,dataWidth): 35 | self.addressWidth = addressWidth 36 | self.dataWidth = dataWidth 37 | def genRandomAddress(self): 38 | return random.randint(0,(1 << self.addressWidth)-1) 39 | 40 | def getTransactions(self): 41 | if random.random() < 0.8: 42 | trans = AhbLite3Transaction() 43 | return [trans] 44 | else: 45 | OneKiB = 1 << 10 # this pesky 1 KiB wall a burst must not cross 46 | hSize = random.randint(0,log2Up(self.dataWidth//8)) 47 | bytesPerBeat = 1 << hSize 48 | maxBurst = 5 if hSize == 7 else 7 # a full-width 1024 bit bus can only burst up to 8 beats for not crossing a 1 KiB boundary 49 | burst = random.randint(0,maxBurst) 50 | write = random.random() < 0.5 51 | prot = random.randint(0,15) 52 | address = self.genRandomAddress() & ~(bytesPerBeat-1) 53 | 54 | incrUnspecified = burst == 1 55 | incrFixed = burst != 1 and burst & 1 == 1 56 | wrapFixed = burst & 1 == 0 57 | 58 | if incrUnspecified: 59 | maxBeats = (OneKiB - (address % OneKiB)) // bytesPerBeat 60 | burstBeats = random.randint(1,maxBeats) 61 | else: 62 | burstCase = burst >> 1 63 | burstBeats = [1,4,8,16][burstCase] 64 | 65 | burstBytes = bytesPerBeat*burstBeats 66 | 67 | while incrFixed and ((address % OneKiB) + burstBytes) > OneKiB: 68 | address = address - bytesPerBeat 69 | 70 | addressBase = address - address % burstBytes # for wrapFixed bursts 71 | 72 | buffer = [] 73 | for beat in range(burstBeats): 74 | if beat > 0: 75 | busyProp = random.random() - 0.8 76 | for busyBeat in range(int(busyProp/0.05)): 77 | trans = AhbLite3Transaction() 78 | trans.HWRITE = write 79 | trans.HSIZE = hSize 80 | trans.HBURST = burst 81 | trans.HPROT = prot 82 | trans.HADDR = address 83 | trans.HTRANS = 1 # BUSY 84 | trans.HWDATA = random.randint(0,(1 << self.dataWidth)-1) 85 | buffer.append(trans) 86 | trans = AhbLite3Transaction() 87 | trans.HWRITE = write 88 | trans.HSIZE = hSize 89 | trans.HBURST = burst 90 | trans.HPROT = prot 91 | trans.HADDR = address 92 | trans.HTRANS = 2 if beat == 0 else 3 # first beat is NONSEQ, others are SEQ 93 | trans.HWDATA = random.randint(0,(1 << self.dataWidth)-1) 94 | address += bytesPerBeat 95 | if wrapFixed and (address == addressBase + burstBytes): 96 | address = addressBase 97 | buffer.append(trans) 98 | return buffer 99 | 100 | class AhbLite3MasterDriver: 101 | def __init__(self,ahb,transactor,clk,reset): 102 | self.ahb = ahb 103 | self.clk = clk 104 | self.reset = reset 105 | self.transactor = transactor 106 | cocotb.fork(self.stim()) 107 | 108 | @cocotb.coroutine 109 | def stim(self): 110 | ahb = self.ahb 111 | ahb.HADDR <= 0 112 | ahb.HWRITE <= 0 113 | ahb.HSIZE <= 0 114 | ahb.HBURST <= 0 115 | ahb.HPROT <= 0 116 | ahb.HTRANS <= 0 117 | ahb.HMASTLOCK <= 0 118 | ahb.HWDATA <= 0 119 | HWDATAbuffer = 0 120 | while True: 121 | for trans in self.transactor.getTransactions(): 122 | yield RisingEdge(self.clk) 123 | while int(self.ahb.HREADY) == 0: 124 | yield RisingEdge(self.clk) 125 | 126 | ahb.HADDR <= trans.HADDR 127 | ahb.HWRITE <= trans.HWRITE 128 | ahb.HSIZE <= trans.HSIZE 129 | ahb.HBURST <= trans.HBURST 130 | ahb.HPROT <= trans.HPROT 131 | ahb.HTRANS <= trans.HTRANS 132 | ahb.HMASTLOCK <= trans.HMASTLOCK 133 | ahb.HWDATA <= HWDATAbuffer 134 | HWDATAbuffer = trans.HWDATA 135 | 136 | class AhbLite3Terminaison: 137 | def __init__(self,ahb,clk,reset): 138 | self.ahb = ahb 139 | self.clk = clk 140 | self.reset = reset 141 | self.randomHREADY = True 142 | cocotb.fork(self.stim()) 143 | cocotb.fork(self.combEvent()) 144 | 145 | @cocotb.coroutine 146 | def stim(self): 147 | randomizer = BoolRandomizer() 148 | self.ahb.HREADY <= 1 149 | self.ahb.HSEL <= 1 150 | while True: 151 | yield RisingEdge(self.clk) 152 | self.randomHREADY = randomizer.get() 153 | self.doComb() 154 | 155 | @cocotb.coroutine 156 | def combEvent(self): 157 | while True: 158 | yield Edge(self.ahb.HREADYOUT) 159 | self.doComb() 160 | 161 | def doComb(self): 162 | self.ahb.HREADY <= (self.randomHREADY and (int(self.ahb.HREADYOUT) == 1)) 163 | 164 | 165 | class AhbLite3MasterReadChecker: 166 | def __init__(self,ahb,buffer,clk,reset): 167 | self.ahb = ahb 168 | self.clk = clk 169 | self.reset = reset 170 | self.buffer = buffer 171 | self.counter = 0 172 | cocotb.fork(self.stim()) 173 | 174 | @cocotb.coroutine 175 | def stim(self): 176 | ahb = self.ahb 177 | readIncoming = False 178 | while True: 179 | yield RisingEdge(self.clk) 180 | if int(self.ahb.HREADY) == 1: 181 | if readIncoming: 182 | if self.buffer.empty(): 183 | raise TestFailure("Empty buffer ??? ") 184 | 185 | bufferData = self.buffer.get() 186 | for i in range(byteOffset,byteOffset + size): 187 | assertEquals((int(ahb.HRDATA) >> (i*8)) & 0xFF,(bufferData >> (i*8)) & 0xFF,"AHB master read checker faild %x " %(int(ahb.HADDR)) ) 188 | 189 | self.counter += 1 190 | # cocotb.log.info("POP " + str(self.buffer.qsize())) 191 | 192 | readIncoming = int(ahb.HTRANS) >= 2 and int(ahb.HWRITE) == 0 193 | size = 1 << int(ahb.HSIZE) 194 | byteOffset = int(ahb.HADDR) % (len(ahb.HWDATA) // 8) 195 | 196 | 197 | 198 | class AhbLite3SlaveMemory: 199 | def __init__(self,ahb,base,size,clk,reset): 200 | self.ahb = ahb 201 | self.clk = clk 202 | self.reset = reset 203 | self.base = base 204 | self.size = size 205 | self.ram = bytearray(b'\x00' * size) 206 | 207 | cocotb.fork(self.stim()) 208 | cocotb.fork(self.stimReady()) 209 | 210 | @cocotb.coroutine 211 | def stimReady(self): 212 | randomizer = BoolRandomizer() 213 | self.ahb.HREADYOUT <= 1 214 | busy = False 215 | while True: 216 | yield RisingEdge(self.clk) 217 | if int(self.ahb.HREADY) == 1: 218 | busyNew = int(self.ahb.HTRANS) >= 2 219 | else: 220 | busyNew = busy 221 | if (busy or busyNew) and int(self.ahb.HREADYOUT) == 0 and int(self.ahb.HREADY) == 1: 222 | raise TestFailure("HREADYOUT == 0 but HREADY == 1 ??? " + self.ahb.HREADY._name) 223 | busy = busyNew 224 | if (busy): 225 | self.ahb.HREADYOUT <= randomizer.get() # make some random delay for NONSEQ and SEQ requests 226 | else: 227 | self.ahb.HREADYOUT <= 1 # IDLE and BUSY require 0 WS 228 | 229 | @cocotb.coroutine 230 | def stim(self): 231 | ahb = self.ahb 232 | ahb.HREADYOUT <= 1 233 | ahb.HRESP <= 0 234 | ahb.HRDATA <= 0 235 | valid = 0 236 | while True: 237 | yield RisingEdge(self.clk) 238 | while int(self.ahb.HREADY) == 0: 239 | yield RisingEdge(self.clk) 240 | 241 | if valid == 1: 242 | if trans >= 2: 243 | if write == 1: 244 | for idx in range(size): 245 | self.ram[address-self.base + idx] = (int(ahb.HWDATA) >> (8*(addressOffset + idx))) & 0xFF 246 | # print("write %x with %x" % (address + idx,(int(ahb.HWDATA) >> (8*(addressOffset + idx))) & 0xFF)) 247 | 248 | valid = int(ahb.HSEL) 249 | trans = int(ahb.HTRANS) 250 | write = int(ahb.HWRITE) 251 | size = 1 << int(ahb.HSIZE) 252 | address = int(ahb.HADDR) 253 | addressOffset = address % (len(ahb.HWDATA)//8) 254 | 255 | ahb.HRDATA <= 0 256 | if valid == 1: 257 | if trans >= 2: 258 | if write == 0: 259 | data = 0 260 | for idx in range(size): 261 | data |= self.ram[address-self.base + idx] << (8*(addressOffset + idx)) 262 | # print("read %x with %x" % (address + idx, self.ram[address-self.base + idx])) 263 | # print(str(data)) 264 | ahb.HRDATA <= int(data) 265 | --------------------------------------------------------------------------------