Examples of using cocotb for functional verification of VHDL designs with GHDL.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

497 lines
12 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. ---
  2. title: Using Python for Verification of Digital Systems
  3. subtitle: QZ 2021
  4. author:
  5. - Torsten Meißner
  6. - torsten.meissner@secunet.com
  7. date: February 2022
  8. ...
  9. # Overview
  10. * Introduction
  11. * Functional Verification
  12. * Co-Simulation
  13. * Cocotb
  14. * Python Packages
  15. * Live Demo
  16. * Summary
  17. # Introduction
  18. ## FPGA-Workflow
  19. 1. Specification
  20. 2. Design entry
  21. 3. **Verification**
  22. 4. Synthesis
  23. 5. Device Mapping
  24. 6. Place & Route
  25. 7. Static Timing Analysis
  26. 8. Programming file generation
  27. # Introduction
  28. ## Design Entry
  29. 1. Schematic Entry
  30. 2. **Hardware Description Languages (RTL)**
  31. * **(System)Verilog**
  32. * **VHDL**
  33. 3. High level languages
  34. * System C
  35. * Bluespec
  36. * Chisel
  37. * nmigen
  38. # Functional Verification
  39. 1. **Functional Verification**
  40. * **Simulation**
  41. * Emulation
  42. 2. Formal Verification
  43. * Property checking
  44. * Equivalence checking
  45. 3. Lab Tests
  46. * Target platform
  47. * Logic Analyzer
  48. * Oscilloscope
  49. # Functional Verification
  50. ## Simulation
  51. * Executing of design description in a simulator
  52. * Test benches as infrastructure (HDL, C, etc.)
  53. * Reference models (HDL, C, etc.)
  54. * Directed & Random tests
  55. * Code & functional Coverage
  56. * Assertion Based Verification (PSL, SVA)
  57. * Verification Frameworks (UVM, OSVVM, vUnit, etc.)
  58. * Co-Simulation
  59. # HDL Simulation
  60. ![](images/vhdl_sim.png)
  61. # Co-Simulation
  62. - Simulation with access to/from external program code
  63. - Linked per shared library
  64. ## HDL Programming Interfaces
  65. - VHDL Procedural Interface (VHPI)
  66. - Verilog Procedural Interface (VPI)
  67. - Proprietary interfaces (FLI)
  68. - Access data in VHDL models in the simulator
  69. ## Features
  70. - Static VHDL Design Data (Traverse hierarchy etc.)
  71. - Dynamic VHDL Objects (R/W values of VHDL objects)
  72. - Interaction and control (Callbacks as comm. mechanism between simulator user code)
  73. - Foreign model instantiation and intercommunication
  74. # Co-Simulation with SW reference Model
  75. ![](images/vhdl_cosim.png)
  76. HDL testbench controls program flow
  77. # Cocotb
  78. - COroutine based COsimulation TestBench environment
  79. - Verifying HDL designs with Python
  80. - HDL normally only used for design, not the testbench
  81. - Simulator only used to execute DUT RTL description
  82. - Supports many simulators (Free & proprietary)
  83. - Free & open-source, active community
  84. ##
  85. - High-level, multi-paradigm language
  86. - Writing Python is fast - **very productive** language.
  87. - **Easy interfacing** to other languages from Python
  88. - **Huge library** of existing code to re-use
  89. - **Interpreted** - tests can be edited and re-run w/o recompiling the design
  90. - **Popular** - far more engineers know Python than Verilog / VHDL
  91. - Working and reliable packet manager (PyPI)
  92. # Cocotb Co-Simulation
  93. ![](images/cocotb_cosim.png)
  94. Python testbench controls program flow
  95. # Cocotb Design Interaction
  96. ## Accessing Design
  97. - *dut* as handle to toplevel instantiation
  98. - Access to toplevel and other signals with dot-notation
  99. ~~~~ {.python .stretch}
  100. # Reference to toplevel clock input
  101. clk = dut.clk_i
  102. # Reference to signal in sub-unit
  103. cpu_pc = dut.cpu.regfile.pc
  104. ~~~~
  105. ## Read / Write Values from Signals
  106. - Via handle's *value* property
  107. - Direct R/W access through the hierarchy
  108. ~~~~ {.python .stretch}
  109. # Via value property
  110. valid = dut.valid_i.value
  111. if valid.value == 0:
  112. valid.value = 1
  113. # Direct access through hierarchy
  114. if dut.reset_i.value == 1:
  115. dut.cpu.regfile.pc.value = 0
  116. ~~~~
  117. # Cocotb concurrent & sequential execution
  118. ## *async*: Mark Functions & Methods as Coroutines
  119. ~~~~ {.python .stretch}
  120. async def reset(signal, time,):
  121. signal.value = 0
  122. # Block execution, wait for simulator time advances by 100 ns
  123. await Timer(time, units='ns') # cocotb built-in class
  124. signal.value = 1
  125. ~~~~
  126. ## *await*: Wait for other Coroutines or Simulator
  127. - Block on another coroutines execution
  128. - Pass control of execution back to simulator, allowing simulation time to advance
  129. ~~~~ {.python .stretch}
  130. print("Hold reset")
  131. await reset(dut.reset_i)
  132. print("Released reset")
  133. ~~~~
  134. # Cocotb concurrent execution
  135. ## *start()*
  136. 1. Schedules the new coroutine to be executed concurrently
  137. 2. Yields control to allow the new task (& any other pending tasks) to run
  138. 3. Resumes the calling task
  139. ~~~~ {.python .stretch}
  140. await cocotb.start(reset(dut.reset_i, 100)
  141. await Timer(90, units='ns')
  142. print(f"Reset is still active: {dut.reset_i.value}")
  143. await Timer(15, units='ns')
  144. print(f"Reset has gone inactive: {dut.reset_i.value}")
  145. ~~~~
  146. ## *start_soon()*:
  147. - Schedules the new coroutine for future execution, after the calling task yields control
  148. ~~~~ {.python .stretch}
  149. clock = Clock(dut.clk_i, 10, units="ns") # Create a clock, cocotb built-in class
  150. cocotb.start_soon(clock.start()) # Start the clock concurrently
  151. ~~~~
  152. # Cocotb test functions
  153. ## *@cocotb.test()* Decorator
  154. - Mark a callable which returns a coroutine as a test
  155. - Provides a test timeout
  156. - Allows to mark tests as skipped or expecting errors or failures
  157. - Tests are evaluated in the order of their definition in a test module
  158. ~~~~ {.python .stretch}
  159. @cocotb.test()
  160. async def test_aes_init(dut):
  161. """ Test AES initialization """
  162. ...
  163. @cocotb.test()
  164. async def test_aes_enc(dut):
  165. """ Test AES encryption """
  166. ...
  167. # This test is skipped from execution
  168. @cocotb.test(skip=True)
  169. async def test_aes_enc(dut):
  170. """ Test AES encryption """
  171. ~~~~
  172. # Cocotb Triggers
  173. - Indicate when cocotb scheduler should resume coroutine execution
  174. - Triggers should be awaited by coroutines
  175. - Cause execution of the current coroutine to pause
  176. - Execution of paused coroutine will resumes when trigger fires
  177. - Triggers for simulator events, task synchronization etc.
  178. ~~~~ {.python .stretch}
  179. # Wait for 100 ns
  180. await Timer(100, units='ns')
  181. # Wait for rising clock edge
  182. await RisingEdge(dut.clk_i)
  183. # Wait for 10 clock cycles
  184. await ClockCycles(dut.clk_i, 10)
  185. # Fires when first trigger in fires & returns its result
  186. t1 = Timer(10, units='ns')
  187. t2 = Timer(15, units='ns')
  188. t_ret = await First(t1, t2) # returns after 10 ns simulation time
  189. ~~~~
  190. # Cocotb Example: Verifying a UART transmitter
  191. ![](images/cocotb_uarttx.png)
  192. # Cocotb Example: Verifying a UART transmitter
  193. ##
  194. ![](images/vai_uart_wave.png)
  195. # Cocotb Example: Verifying a UART transmitter
  196. ## Valid-Accept Driver Model
  197. ~~~~ {.python .stretch}
  198. async def send(self, data, sync=True):
  199. if sync:
  200. await self._clkedge
  201. self._valid.value = 1
  202. if isinstance(self._data, list):
  203. for i in range(len(self._data)):
  204. self._data[i].value = data[i]
  205. else:
  206. self._data.value = data
  207. while True:
  208. await ReadOnly()
  209. if self._accept.value:
  210. break
  211. await self._clkedge
  212. await self._clkedge
  213. self._valid.value = 0
  214. ~~~~
  215. # Cocotb Example: Verifying a UART transmitter
  216. ## UART Receiver Model
  217. ~~~~ {.python .stretch}
  218. async def receive(self):
  219. # Wait for frame start
  220. await FallingEdge(self._txrx)
  221. # Consume start bit
  222. await self._get_start_bit()
  223. # Receive data bits
  224. self._rec = 0
  225. for x in range(self._bits):
  226. await self._wait_cycle()
  227. await ReadOnly()
  228. self._rec |= bool(self._txrx.value.integer) << x
  229. if self._par:
  230. # Consume parity bit
  231. await self._get_parity_bit()
  232. # Consume stop bit
  233. await self._get_stop_bit()
  234. return self._rec
  235. ~~~~
  236. # Cocotb Example: Verifying a UART transmitter
  237. ## Test function
  238. ~~~~ {.python .stretch}
  239. @cocotb.test()
  240. async def test_uarttx(dut):
  241. # Instantiate VAI driver & UART receiver
  242. vai_driver = VaiDriver(dut.clk_i, dut.data_i, dut.valid_i, dut.accept_o)
  243. uart_receiver = UartReceiver(dut.tx_o, dut.clk_i, 10, 8, True);
  244. # Drive input defaults (setimmediatevalue to avoid x asserts)
  245. dut.data_i.setimmediatevalue(0)
  246. dut.valid_i.setimmediatevalue(0)
  247. cocotb.start_soon(Clock(dut.clk_i, 10, units="ns").start()) # Start the clock
  248. await reset(dut.reset_n_i, 100) # Block until reset() has completed
  249. # Test 10 UART transmissions
  250. for i in range(256):
  251. await RisingEdge(dut.clk_i)
  252. await vai_driver.send(i)
  253. rec = await uart_receiver.receive();
  254. assert rec == i, "UART sent data was incorrect on the {}th cycle".format(i)
  255. ~~~~
  256. # Cocotb Example: Verifying a UART transmitter
  257. ~~~~ {.shell .stretch}
  258. loading VPI module '/usr/local/lib/python3.9/dist-packages/cocotb/libs/libcocotbvpi_ghdl.so'
  259. -.--ns INFO cocotb.gpi ../gpi/GpiCommon.cpp:99 in gpi_print_registered_impl VPI registered
  260. VPI module loaded!
  261. 0.00ns INFO Running on GHDL version 2.0.0-dev (v1.0.0-974-g0e46300c) [Dunoon edition]
  262. 0.00ns INFO Running tests with cocotb v1.7.0.dev0 from /usr/local/lib/python3.9/...
  263. 0.00ns INFO Seeding Python random module with 1644512771
  264. 0.00ns INFO Found test tb_uarttx.test_uarttx
  265. 0.00ns INFO running test_uarttx (1/1)
  266. First simple test
  267. 0.00ns INFO Valid-accept driver
  268. 0.00ns INFO cocotbext-vai version 0.0.1
  269. 0.00ns INFO Copyright (c) 2022 Torsten Meissner
  270. 0.00ns INFO UART receiver
  271. 0.00ns INFO cocotbext-uart version 0.0.1
  272. 0.00ns INFO Copyright (c) 2022 Torsten Meissner
  273. 100.00ns INFO Released reset
  274. 110.00ns INFO Send data: 0xb6
  275. ...
  276. 11160.00ns INFO Received data: 0xd8
  277. 11160.00ns INFO test_uarttx passed
  278. 11160.00ns INFO **********************************************************************
  279. ** TEST STATUS SIM TIME (ns) REAL TIME (s)
  280. **********************************************************************
  281. ** tb_uarttx.test_uarttx PASS 11160.00 0.21
  282. **********************************************************************
  283. ** TESTS=1 PASS=1 FAIL=0 SKIP=0 11160.00 0.22
  284. **********************************************************************
  285. ~~~~
  286. # Python Packages
  287. ## Cocotb related
  288. - Reusable packages for cocotb testbenches
  289. - Bus protocols, reference models etc.
  290. - Verification libraries
  291. - pyuvm
  292. - cocotb-coverage
  293. - uvm-python
  294. - Depending on cocotb
  295. ## Python generic
  296. - Generic Python packages useful for verification
  297. - pyvsc
  298. - pyucis
  299. - Whole Python ecosystem
  300. - Not depending on cocotb
  301. # Python Packages: pyvsc
  302. ## Python library for Verification Stimulus and Coverage
  303. - Random verification-stimulus generation
  304. - Functional coverage collection
  305. - Implemented in pure Python
  306. - Uses Boolector SMT-solver for solving user-defined constraints
  307. ~~~~ {.python .stretch}
  308. @vsc.randobj
  309. class my_cr():
  310. def __init__(self):
  311. self.a = vsc.rand_bit_t(8)
  312. self.b = vsc.rand_bit_t(8)
  313. @vsc.constraint
  314. def ab_c(self):
  315. self.a != 0
  316. self.a <= self.b
  317. self.b in vsc.rangelist(1,2,4,8)
  318. ~~~~
  319. # Live Demo
  320. ## UART transmitter & receiver
  321. - Simple tests using self written Python models for VAI & UART
  322. ## Wishbone slave with local SRAM interface
  323. - Using cocotbext-wishbone package from PyPI
  324. ## AES128 en- and decryption
  325. - Using pyvsc for constrained random & functional coverage
  326. - Using Pycrypto for AES reference
  327. # Summary
  328. - Easy to use
  329. - Good documentation
  330. - In active development with regular releases
  331. - Free and open-source
  332. - Allows Python SW-developers to verify digital systems
  333. - Supports all major simulators used by FPGA teams
  334. ## Presentation's code examples
  335. * https://github.com/tmeissner/cocotb_with_ghdl
  336. ## References
  337. * https://github.com/cocotb/cocotb
  338. * https://github.com/fvutils/pyvsc
  339. * https://github.com/wallento/cocotbext-wishbone
  340. # Extras: Accessing signals
  341. ## *dut.signal.value = 1*
  342. - Value is stored by the Scheduler
  343. - All stored values are written at the same time at the end of the current simulator time step
  344. ## *.setimmediatevalue()*
  345. - Value is assigned to this simulation object immediately
  346. ## Access to elements of indexable objects (arrays etc.)
  347. ~~~~ {.python .stretch}
  348. dut.some_array[0].value = 1
  349. ~~~~
  350. - Bit order depends on the HDL object (*to* or *downto*)
  351. # Extras: Accessing signals
  352. ## Reading synchronous signals
  353. - Returns after clock changes, but no sympathetic signals changed yet
  354. - Sampling any signal here returns values settled during previous clock cycle
  355. - Equivalent to registered processes in HDLs
  356. ## *ReadOnly()*
  357. - Triggers in the postpone phase
  358. - All signals have settled
  359. - No more updates may occur on the clock edge event
  360. - Sampling any signal here returns values settled in current clock cycle
  361. # Extras: Accessing signals
  362. ![](images/readonly.png)