0
# Clock Generation
1
2
Cocotb's clock generation system provides utilities for creating periodic signals with configurable periods, duty cycles, and phase relationships. The clock system integrates with the trigger system to enable precise timing control in testbenches.
3
4
## Capabilities
5
6
### Basic Clock Generation
7
8
Simple clock generation with 50% duty cycle and configurable period.
9
10
```python { .api }
11
class Clock(signal, period, units="step"):
12
"""
13
Simple 50:50 duty cycle clock generator.
14
15
Parameters:
16
- signal: HDL signal to drive as clock
17
- period: Clock period value
18
- units: Time units ("fs", "ps", "ns", "us", "ms", "sec", "step")
19
20
Methods:
21
- start(cycles=None, start_high=True): Start clock generation
22
"""
23
24
async def start(self, cycles=None, start_high=True):
25
"""
26
Start clock generation task.
27
28
Parameters:
29
- cycles: Number of cycles to generate (None for infinite)
30
- start_high: True to start with high level, False for low
31
32
Returns:
33
Task object for the clock generation coroutine
34
"""
35
```
36
37
**Usage Examples:**
38
39
```python
40
@cocotb.test()
41
async def basic_clock_test(dut):
42
"""Test demonstrating basic clock generation."""
43
44
# Create 100MHz clock (10ns period)
45
clock = Clock(dut.clk, 10, units="ns")
46
47
# Start infinite clock
48
clock_task = cocotb.start_soon(clock.start())
49
50
# Run test with clock running
51
await Timer(1000, units="ns") # 100 clock cycles
52
53
# Stop clock
54
clock_task.kill()
55
56
@cocotb.test()
57
async def finite_clock_test(dut):
58
"""Test with finite number of clock cycles."""
59
60
# Create clock
61
clock = Clock(dut.clk, 20, units="ns") # 50MHz
62
63
# Generate exactly 50 cycles
64
await clock.start(cycles=50, start_high=True)
65
66
# Clock automatically stops after 50 cycles
67
cocotb.log.info("50 clock cycles completed")
68
69
@cocotb.test()
70
async def multiple_clocks_test(dut):
71
"""Test with multiple independent clocks."""
72
73
# Create multiple clocks with different frequencies
74
main_clock = Clock(dut.main_clk, 10, units="ns") # 100MHz
75
slow_clock = Clock(dut.slow_clk, 100, units="ns") # 10MHz
76
fast_clock = Clock(dut.fast_clk, 2, units="ns") # 500MHz
77
78
# Start all clocks
79
main_task = cocotb.start_soon(main_clock.start())
80
slow_task = cocotb.start_soon(slow_clock.start())
81
fast_task = cocotb.start_soon(fast_clock.start())
82
83
# Run test
84
await Timer(2000, units="ns")
85
86
# Stop all clocks
87
main_task.kill()
88
slow_task.kill()
89
fast_task.kill()
90
```
91
92
### Advanced Clock Generation
93
94
Base class for custom clock implementations with advanced features.
95
96
```python { .api }
97
class BaseClock:
98
"""
99
Abstract base class for clock generators.
100
101
Provides foundation for custom clock implementations
102
with non-standard duty cycles, jitter, or phase relationships.
103
"""
104
```
105
106
**Usage Examples:**
107
108
```python
109
class CustomClock(BaseClock):
110
"""Custom clock with configurable duty cycle."""
111
112
def __init__(self, signal, period, duty_cycle=0.5, units="step"):
113
self.signal = signal
114
self.period = period
115
self.duty_cycle = duty_cycle
116
self.units = units
117
self.high_time = period * duty_cycle
118
self.low_time = period * (1 - duty_cycle)
119
120
async def start(self, cycles=None, start_high=True):
121
"""Start custom clock generation."""
122
cycle_count = 0
123
current_level = start_high
124
125
while cycles is None or cycle_count < cycles:
126
self.signal.value = 1 if current_level else 0
127
128
if current_level:
129
await Timer(self.high_time, units=self.units)
130
else:
131
await Timer(self.low_time, units=self.units)
132
cycle_count += 1 # Count on falling edge
133
134
current_level = not current_level
135
136
@cocotb.test()
137
async def custom_clock_test(dut):
138
"""Test demonstrating custom clock generation."""
139
140
# Create clock with 25% duty cycle
141
clock_25 = CustomClock(dut.clk_25, 100, duty_cycle=0.25, units="ns")
142
143
# Create clock with 75% duty cycle
144
clock_75 = CustomClock(dut.clk_75, 100, duty_cycle=0.75, units="ns")
145
146
# Start custom clocks
147
task_25 = cocotb.start_soon(clock_25.start(cycles=20))
148
task_75 = cocotb.start_soon(clock_75.start(cycles=20))
149
150
# Wait for completion
151
await task_25.join()
152
await task_75.join()
153
154
cocotb.log.info("Custom clocks completed")
155
```
156
157
## Clock Control Patterns
158
159
### Conditional Clock Generation
160
161
```python
162
class ConditionalClock:
163
"""Clock that can be enabled/disabled during operation."""
164
165
def __init__(self, signal, period, units="step"):
166
self.signal = signal
167
self.period = period
168
self.units = units
169
self.enabled = False
170
171
def enable(self):
172
"""Enable clock generation."""
173
self.enabled = True
174
175
def disable(self):
176
"""Disable clock generation."""
177
self.enabled = False
178
179
async def start(self):
180
"""Start conditional clock generation."""
181
while True:
182
if self.enabled:
183
self.signal.value = 1
184
await Timer(self.period / 2, units=self.units)
185
self.signal.value = 0
186
await Timer(self.period / 2, units=self.units)
187
else:
188
await Timer(self.period / 4, units=self.units) # Check enable more frequently
189
190
@cocotb.test()
191
async def conditional_clock_test(dut):
192
"""Test demonstrating conditional clock control."""
193
194
# Create conditional clock
195
cond_clock = ConditionalClock(dut.gated_clk, 20, units="ns")
196
clock_task = cocotb.start_soon(cond_clock.start())
197
198
# Initially disabled
199
await Timer(100, units="ns")
200
cocotb.log.info("Clock should be stopped")
201
202
# Enable clock
203
cond_clock.enable()
204
await Timer(200, units="ns")
205
cocotb.log.info("Clock should be running")
206
207
# Disable clock
208
cond_clock.disable()
209
await Timer(100, units="ns")
210
cocotb.log.info("Clock should be stopped again")
211
212
# Clean up
213
clock_task.kill()
214
```
215
216
### Phase-Shifted Clocks
217
218
```python
219
class PhaseShiftedClock:
220
"""Clock generator with configurable phase shift."""
221
222
def __init__(self, signal, period, phase_shift=0, units="step"):
223
self.signal = signal
224
self.period = period
225
self.phase_shift = phase_shift
226
self.units = units
227
228
async def start(self, cycles=None):
229
"""Start phase-shifted clock generation."""
230
231
# Apply initial phase shift
232
if self.phase_shift > 0:
233
await Timer(self.phase_shift, units=self.units)
234
235
# Generate clock
236
cycle_count = 0
237
while cycles is None or cycle_count < cycles:
238
self.signal.value = 1
239
await Timer(self.period / 2, units=self.units)
240
241
self.signal.value = 0
242
await Timer(self.period / 2, units=self.units)
243
244
cycle_count += 1
245
246
@cocotb.test()
247
async def phase_shifted_test(dut):
248
"""Test demonstrating phase-shifted clocks."""
249
250
period = 20 # 50MHz base frequency
251
252
# Create phase-shifted clocks
253
clk_0 = PhaseShiftedClock(dut.clk_0, period, phase_shift=0, units="ns")
254
clk_90 = PhaseShiftedClock(dut.clk_90, period, phase_shift=period/4, units="ns")
255
clk_180 = PhaseShiftedClock(dut.clk_180, period, phase_shift=period/2, units="ns")
256
clk_270 = PhaseShiftedClock(dut.clk_270, period, phase_shift=3*period/4, units="ns")
257
258
# Start all clocks
259
tasks = [
260
cocotb.start_soon(clk_0.start(cycles=10)),
261
cocotb.start_soon(clk_90.start(cycles=10)),
262
cocotb.start_soon(clk_180.start(cycles=10)),
263
cocotb.start_soon(clk_270.start(cycles=10))
264
]
265
266
# Wait for all to complete
267
for task in tasks:
268
await task.join()
269
270
cocotb.log.info("Phase-shifted clocks completed")
271
```
272
273
### Clock Domain Crossing
274
275
```python
276
@cocotb.test()
277
async def clock_domain_crossing_test(dut):
278
"""Test demonstrating clock domain crossing scenarios."""
279
280
# Create clocks for different domains
281
fast_clock = Clock(dut.fast_clk, 5, units="ns") # 200MHz
282
slow_clock = Clock(dut.slow_clk, 50, units="ns") # 20MHz
283
284
# Start clocks
285
fast_task = cocotb.start_soon(fast_clock.start())
286
slow_task = cocotb.start_soon(slow_clock.start())
287
288
# Test data transfer from fast to slow domain
289
await transfer_fast_to_slow(dut)
290
291
# Test data transfer from slow to fast domain
292
await transfer_slow_to_fast(dut)
293
294
# Clean up
295
fast_task.kill()
296
slow_task.kill()
297
298
async def transfer_fast_to_slow(dut):
299
"""Transfer data from fast clock domain to slow clock domain."""
300
301
# Synchronize to fast clock and send data
302
await RisingEdge(dut.fast_clk)
303
dut.fast_data.value = 0xABCD
304
dut.fast_valid.value = 1
305
306
await RisingEdge(dut.fast_clk)
307
dut.fast_valid.value = 0
308
309
# Wait for slow domain to capture (may take several slow cycles)
310
timeout_cycles = 10
311
for _ in range(timeout_cycles):
312
await RisingEdge(dut.slow_clk)
313
if dut.slow_data_valid.value:
314
break
315
else:
316
raise AssertionError("Data not transferred to slow domain")
317
318
# Verify data integrity
319
assert dut.slow_data.value == 0xABCD
320
cocotb.log.info("Fast-to-slow transfer successful")
321
322
async def transfer_slow_to_fast(dut):
323
"""Transfer data from slow clock domain to fast clock domain."""
324
325
# Synchronize to slow clock and send data
326
await RisingEdge(dut.slow_clk)
327
dut.slow_data.value = 0x1234
328
dut.slow_valid.value = 1
329
330
await RisingEdge(dut.slow_clk)
331
dut.slow_valid.value = 0
332
333
# Fast domain should capture quickly
334
await RisingEdge(dut.fast_clk)
335
await RisingEdge(dut.fast_clk) # Allow for synchronizer delay
336
337
if dut.fast_data_valid.value:
338
assert dut.fast_data.value == 0x1234
339
cocotb.log.info("Slow-to-fast transfer successful")
340
else:
341
cocotb.log.warning("Slow-to-fast transfer not detected")
342
```
343
344
## Clock Utilities
345
346
### Clock Measurement
347
348
```python
349
class ClockMeasurer:
350
"""Utility for measuring actual clock characteristics."""
351
352
def __init__(self, signal):
353
self.signal = signal
354
self.measurements = []
355
356
async def measure_period(self, num_cycles=10):
357
"""Measure actual clock period over multiple cycles."""
358
359
edge_times = []
360
361
# Collect edge timestamps
362
for _ in range(num_cycles + 1):
363
await RisingEdge(self.signal)
364
edge_times.append(get_sim_time("ps")) # High precision
365
366
# Calculate periods
367
periods = []
368
for i in range(1, len(edge_times)):
369
period = edge_times[i] - edge_times[i-1]
370
periods.append(period)
371
372
# Statistics
373
avg_period = sum(periods) / len(periods)
374
min_period = min(periods)
375
max_period = max(periods)
376
jitter = max_period - min_period
377
378
result = {
379
'average_period_ps': avg_period,
380
'min_period_ps': min_period,
381
'max_period_ps': max_period,
382
'jitter_ps': jitter,
383
'frequency_mhz': 1e6 / avg_period
384
}
385
386
self.measurements.append(result)
387
return result
388
389
@cocotb.test()
390
async def clock_measurement_test(dut):
391
"""Test demonstrating clock measurement."""
392
393
# Start clock under test
394
clock = Clock(dut.clk, 10, units="ns") # Nominal 100MHz
395
clock_task = cocotb.start_soon(clock.start())
396
397
# Measure actual characteristics
398
measurer = ClockMeasurer(dut.clk)
399
result = await measurer.measure_period(num_cycles=20)
400
401
# Report results
402
cocotb.log.info(f"Measured frequency: {result['frequency_mhz']:.2f} MHz")
403
cocotb.log.info(f"Average period: {result['average_period_ps']:.1f} ps")
404
cocotb.log.info(f"Jitter: {result['jitter_ps']:.1f} ps")
405
406
# Verify within tolerance
407
expected_freq = 100.0 # MHz
408
tolerance = 0.1 # MHz
409
410
assert abs(result['frequency_mhz'] - expected_freq) < tolerance, \
411
f"Frequency error too large: {result['frequency_mhz']} MHz"
412
413
# Clean up
414
clock_task.kill()
415
```
416
417
### Clock Synchronization
418
419
```python
420
async def wait_for_clock_edges(clock_signal, num_edges):
421
"""Wait for specific number of clock edges."""
422
423
for _ in range(num_edges):
424
await RisingEdge(clock_signal)
425
426
async def synchronize_to_clock(clock_signal):
427
"""Synchronize current coroutine to clock edge."""
428
429
await RisingEdge(clock_signal)
430
431
@cocotb.test()
432
async def clock_sync_test(dut):
433
"""Test demonstrating clock synchronization utilities."""
434
435
# Start system clock
436
sys_clock = Clock(dut.sys_clk, 8, units="ns") # 125MHz
437
clock_task = cocotb.start_soon(sys_clock.start())
438
439
# Perform clock-synchronized operations
440
await synchronize_to_clock(dut.sys_clk)
441
cocotb.log.info("Synchronized to system clock")
442
443
# Wait for specific number of cycles
444
await wait_for_clock_edges(dut.sys_clk, 16)
445
cocotb.log.info("16 clock cycles elapsed")
446
447
# Use built-in ClockCycles trigger
448
await ClockCycles(dut.sys_clk, 8)
449
cocotb.log.info("8 more clock cycles elapsed")
450
451
# Clean up
452
clock_task.kill()
453
```
454
455
## Best Practices
456
457
### Clock Management in Large Tests
458
459
```python
460
class ClockManager:
461
"""Centralized clock management for complex testbenches."""
462
463
def __init__(self):
464
self.clocks = {}
465
self.tasks = {}
466
467
def add_clock(self, name, signal, period, units="ns"):
468
"""Add a clock to the manager."""
469
clock = Clock(signal, period, units)
470
self.clocks[name] = clock
471
472
async def start_all_clocks(self):
473
"""Start all managed clocks."""
474
for name, clock in self.clocks.items():
475
task = cocotb.start_soon(clock.start())
476
self.tasks[name] = task
477
cocotb.log.info(f"Started clock: {name}")
478
479
def stop_all_clocks(self):
480
"""Stop all managed clocks."""
481
for name, task in self.tasks.items():
482
task.kill()
483
cocotb.log.info(f"Stopped clock: {name}")
484
self.tasks.clear()
485
486
def stop_clock(self, name):
487
"""Stop specific clock."""
488
if name in self.tasks:
489
self.tasks[name].kill()
490
del self.tasks[name]
491
cocotb.log.info(f"Stopped clock: {name}")
492
493
@cocotb.test()
494
async def managed_clocks_test(dut):
495
"""Test demonstrating centralized clock management."""
496
497
# Create clock manager
498
clock_mgr = ClockManager()
499
500
# Add clocks
501
clock_mgr.add_clock("system", dut.sys_clk, 10, "ns") # 100MHz
502
clock_mgr.add_clock("memory", dut.mem_clk, 5, "ns") # 200MHz
503
clock_mgr.add_clock("interface", dut.if_clk, 20, "ns") # 50MHz
504
505
# Start all clocks
506
await clock_mgr.start_all_clocks()
507
508
# Run test with all clocks active
509
await Timer(1000, units="ns")
510
511
# Stop specific clock for power testing
512
clock_mgr.stop_clock("interface")
513
await Timer(500, units="ns")
514
515
# Stop all clocks
516
clock_mgr.stop_all_clocks()
517
```