0
# Signal Handling
1
2
Cocotb's signal handling system provides hierarchical access to HDL design objects through a comprehensive handle system. It supports reading, writing, forcing, and releasing signals across different HDL types with automatic type conversion and simulator integration.
3
4
## Capabilities
5
6
### Base Handle Classes
7
8
Foundation classes for all HDL object access with common properties and methods.
9
10
```python { .api }
11
class SimHandleBase:
12
"""
13
Base class for all simulation object handles.
14
15
Properties:
16
- _name: Signal name
17
- _type: HDL type information
18
- _fullname: Full hierarchical name
19
- _path: Hierarchical path
20
- _def_name: Definition name
21
- _def_file: Definition file
22
"""
23
24
def get_definition_name(self):
25
"""
26
Get the definition name of the object.
27
28
Returns:
29
Definition name string or empty string if not available
30
"""
31
32
def get_definition_file(self):
33
"""
34
Get the definition file of the object.
35
36
Returns:
37
Definition file path or empty string if not available
38
"""
39
```
40
41
**Usage Examples:**
42
43
```python
44
@cocotb.test()
45
async def handle_info_test(dut):
46
"""Test demonstrating handle information access."""
47
48
# Access handle properties
49
cocotb.log.info(f"DUT name: {dut._name}")
50
cocotb.log.info(f"DUT type: {dut._type}")
51
cocotb.log.info(f"DUT full name: {dut._fullname}")
52
53
# Get definition information
54
def_name = dut.get_definition_name()
55
def_file = dut.get_definition_file()
56
57
cocotb.log.info(f"Defined as: {def_name} in {def_file}")
58
59
# Access sub-signals
60
if hasattr(dut, 'cpu'):
61
cpu_def = dut.cpu.get_definition_name()
62
cocotb.log.info(f"CPU module: {cpu_def}")
63
```
64
65
### Hierarchical Objects
66
67
Objects that contain child elements and support attribute-based access to design hierarchy.
68
69
```python { .api }
70
class HierarchyObject(SimHandleBase):
71
"""
72
Hierarchical objects with child attribute access.
73
74
Supports:
75
- Attribute access to child objects (dut.cpu.registers.pc)
76
- Dynamic discovery of child objects
77
- Hierarchical navigation
78
"""
79
80
class HierarchyArrayObject(HierarchyObject):
81
"""
82
Arrays of hierarchical objects.
83
84
Supports:
85
- Indexed access to array elements (dut.memory[0])
86
- Slicing operations (dut.memory[0:7])
87
- Iteration over elements
88
"""
89
90
class RegionObject(HierarchyObject):
91
"""
92
Region/namespace objects in design hierarchy.
93
94
Represents logical groupings like packages, scopes,
95
and generate blocks.
96
"""
97
```
98
99
**Usage Examples:**
100
101
```python
102
@cocotb.test()
103
async def hierarchy_test(dut):
104
"""Test demonstrating hierarchical object access."""
105
106
# Navigate design hierarchy
107
cpu = dut.cpu_subsystem.cpu_core
108
registers = cpu.register_file
109
110
# Access specific register
111
program_counter = registers.pc
112
cocotb.log.info(f"PC value: {program_counter.value}")
113
114
# Access array elements
115
memory = dut.memory_controller.ram
116
117
# Access specific memory location
118
mem_location = memory[0x100]
119
cocotb.log.info(f"Memory[256]: {mem_location.value}")
120
121
# Iterate over array elements (if supported)
122
for i in range(8):
123
register = registers._id(f"r{i}", extended=False)
124
cocotb.log.info(f"R{i}: {register.value}")
125
126
# Access generate block instances
127
for i in range(4):
128
instance = dut._id(f"gen_block[{i}].instance", extended=False)
129
cocotb.log.info(f"Generated instance {i}: {instance._name}")
130
```
131
132
### Value Objects
133
134
Objects representing actual signal values with read/write capabilities.
135
136
```python { .api }
137
class NonHierarchyObject(SimHandleBase):
138
"""Base class for non-hierarchical (leaf) objects."""
139
140
class ConstantObject(NonHierarchyObject):
141
"""
142
Read-only constant objects.
143
144
Properties:
145
- value: Current value (read-only)
146
"""
147
148
class NonConstantObject(NonHierarchyObject):
149
"""
150
Non-constant signal objects with read capability.
151
152
Properties:
153
- value: Current signal value
154
"""
155
156
class ModifiableObject(NonConstantObject):
157
"""
158
Modifiable signal objects with read/write capability.
159
160
Properties:
161
- value: Current signal value (read/write)
162
"""
163
164
class IntegerObject(ModifiableObject):
165
"""
166
Integer signal objects.
167
168
Optimized for integer values with automatic conversion.
169
"""
170
171
class RealObject(ModifiableObject):
172
"""
173
Real number signal objects.
174
175
Supports floating-point signal values.
176
"""
177
178
class EnumObject(ModifiableObject):
179
"""
180
Enumeration signal objects.
181
182
Supports enumerated type values.
183
"""
184
185
class StringObject(ModifiableObject):
186
"""
187
String variable objects.
188
189
Supports string values in HDL variables.
190
"""
191
192
class NonHierarchyIndexableObject(NonConstantObject):
193
"""
194
Indexable arrays and memories.
195
196
Supports:
197
- Indexed access (signal[index])
198
- Slicing operations (signal[start:end])
199
- Bit manipulation
200
"""
201
```
202
203
**Usage Examples:**
204
205
```python
206
@cocotb.test()
207
async def value_objects_test(dut):
208
"""Test demonstrating value object operations."""
209
210
# Read constant values
211
version_reg = dut.version_register # ConstantObject
212
cocotb.log.info(f"Version: {version_reg.value}")
213
214
# Read/write integer signals
215
counter = dut.counter # IntegerObject
216
original_value = counter.value
217
counter.value = 42
218
assert counter.value == 42
219
220
# Real number signals
221
if hasattr(dut, 'voltage_level'):
222
voltage = dut.voltage_level # RealObject
223
voltage.value = 3.3
224
cocotb.log.info(f"Voltage set to: {voltage.value}")
225
226
# Enumeration signals
227
if hasattr(dut, 'state'):
228
state = dut.state # EnumObject
229
state.value = "IDLE"
230
cocotb.log.info(f"State: {state.value}")
231
232
# String variables
233
if hasattr(dut, 'debug_message'):
234
message = dut.debug_message # StringObject
235
message.value = "Test message"
236
237
# Indexable objects (arrays, bit vectors)
238
data_bus = dut.data_bus # NonHierarchyIndexableObject
239
240
# Access individual bits
241
lsb = data_bus[0]
242
msb = data_bus[31]
243
244
# Access bit slices
245
lower_byte = data_bus[7:0]
246
upper_word = data_bus[31:16]
247
248
# Modify bit slices
249
lower_byte.value = 0xFF
250
upper_word.value = 0xABCD
251
252
cocotb.log.info(f"Data bus: {data_bus.value}")
253
```
254
255
### Signal Actions
256
257
Control signal behavior beyond simple value assignment.
258
259
```python { .api }
260
class Deposit(value):
261
"""
262
Deposit value action (simulator-dependent behavior).
263
264
Parameters:
265
- value: Value to deposit
266
267
Usage:
268
signal.value = Deposit(42)
269
"""
270
271
class Force(value):
272
"""
273
Force signal to specific value.
274
275
Overrides normal signal driving until released.
276
277
Parameters:
278
- value: Value to force
279
280
Usage:
281
signal.value = Force(1)
282
"""
283
284
class Freeze():
285
"""
286
Freeze signal at current value.
287
288
Prevents signal changes until released.
289
290
Usage:
291
signal.value = Freeze()
292
"""
293
294
class Release():
295
"""
296
Release forced or frozen signal.
297
298
Returns signal to normal operation.
299
300
Usage:
301
signal.value = Release()
302
"""
303
```
304
305
**Usage Examples:**
306
307
```python
308
@cocotb.test()
309
async def signal_actions_test(dut):
310
"""Test demonstrating signal actions."""
311
312
# Normal assignment
313
dut.control_signal.value = 1
314
await Timer(100, units="ns")
315
316
# Force signal to specific value
317
dut.control_signal.value = Force(0)
318
cocotb.log.info("Control signal forced to 0")
319
320
# Signal will remain 0 even if logic tries to change it
321
await Timer(200, units="ns")
322
assert dut.control_signal.value == 0
323
324
# Freeze at current value
325
dut.status_signal.value = Freeze()
326
original_status = dut.status_signal.value
327
328
await Timer(100, units="ns")
329
assert dut.status_signal.value == original_status
330
331
# Release forced/frozen signals
332
dut.control_signal.value = Release()
333
dut.status_signal.value = Release()
334
335
cocotb.log.info("Signals released to normal operation")
336
337
# Deposit value (simulator-specific behavior)
338
dut.memory_data.value = Deposit(0xDEADBEEF)
339
await Timer(50, units="ns")
340
341
@cocotb.test()
342
async def debug_force_test(dut):
343
"""Test using force for debugging."""
344
345
# Force clock for debugging
346
dut.clk.value = Force(0)
347
await Timer(50, units="ns")
348
349
dut.clk.value = Force(1)
350
await Timer(50, units="ns")
351
352
# Release and return to normal clock
353
dut.clk.value = Release()
354
355
# Continue with normal test
356
await Timer(100, units="ns")
357
```
358
359
### Handle Factory
360
361
Create appropriate handle objects for simulator objects.
362
363
```python { .api }
364
def SimHandle(handle, path=None):
365
"""
366
Factory function to create appropriate handle object.
367
368
Automatically selects correct handle type based on
369
simulator object characteristics.
370
371
Parameters:
372
- handle: Simulator handle object
373
- path: Optional hierarchical path
374
375
Returns:
376
Appropriate SimHandleBase subclass instance
377
"""
378
```
379
380
**Usage Examples:**
381
382
```python
383
@cocotb.test()
384
async def handle_factory_test(dut):
385
"""Test demonstrating handle factory usage."""
386
387
# Factory automatically selects appropriate handle type
388
from cocotb import simulator
389
390
# Get raw simulator handle
391
raw_handle = simulator.get_handle_by_name("dut.cpu.pc")
392
393
# Convert to appropriate cocotb handle
394
pc_handle = SimHandle(raw_handle, "dut.cpu.pc")
395
396
# Use the handle normally
397
cocotb.log.info(f"PC handle type: {type(pc_handle)}")
398
cocotb.log.info(f"PC value: {pc_handle.value}")
399
400
# Factory handles different signal types automatically
401
signals_to_test = [
402
"data_bus", # Likely NonHierarchyIndexableObject
403
"counter", # Likely IntegerObject
404
"enable", # Likely ModifiableObject
405
"cpu", # Likely HierarchyObject
406
"memory" # Likely HierarchyArrayObject
407
]
408
409
for signal_name in signals_to_test:
410
if hasattr(dut, signal_name):
411
signal = getattr(dut, signal_name)
412
cocotb.log.info(f"{signal_name}: {type(signal).__name__}")
413
```
414
415
## Advanced Signal Handling
416
417
### Dynamic Signal Discovery
418
419
```python
420
@cocotb.test()
421
async def discovery_test(dut):
422
"""Test demonstrating dynamic signal discovery."""
423
424
# Discover all signals in a module
425
def discover_signals(module, prefix=""):
426
signals = []
427
428
# Try common signal name patterns
429
test_names = [
430
"clk", "clock", "reset", "rst", "enable", "valid", "ready",
431
"data", "addr", "address", "control", "status"
432
]
433
434
for name in test_names:
435
try:
436
signal = module._id(name, extended=False)
437
signals.append(f"{prefix}.{name}")
438
cocotb.log.info(f"Found signal: {prefix}.{name}")
439
except:
440
pass # Signal doesn't exist
441
442
return signals
443
444
# Discover signals in DUT
445
dut_signals = discover_signals(dut, "dut")
446
447
# Try to discover sub-modules
448
sub_modules = ["cpu", "memory", "controller", "interface"]
449
for sub_name in sub_modules:
450
try:
451
sub_module = dut._id(sub_name, extended=False)
452
sub_signals = discover_signals(sub_module, f"dut.{sub_name}")
453
dut_signals.extend(sub_signals)
454
except:
455
pass # Sub-module doesn't exist
456
457
cocotb.log.info(f"Total signals discovered: {len(dut_signals)}")
458
```
459
460
### Signal Monitoring
461
462
```python
463
class SignalMonitor:
464
"""Generic signal monitor for value tracking."""
465
466
def __init__(self, signal, name=None):
467
self.signal = signal
468
self.name = name or str(signal._name)
469
self.history = []
470
self.monitor_task = None
471
472
async def start_monitoring(self):
473
"""Start monitoring signal changes."""
474
self.monitor_task = cocotb.start_soon(self._monitor_loop())
475
476
def stop_monitoring(self):
477
"""Stop monitoring signal changes."""
478
if self.monitor_task:
479
self.monitor_task.kill()
480
481
async def _monitor_loop(self):
482
"""Internal monitoring loop."""
483
last_value = self.signal.value
484
self.history.append((get_sim_time("ns"), last_value))
485
486
while True:
487
await Edge(self.signal)
488
current_value = self.signal.value
489
current_time = get_sim_time("ns")
490
491
self.history.append((current_time, current_value))
492
cocotb.log.info(f"{self.name}: {last_value} -> {current_value} @ {current_time}ns")
493
494
last_value = current_value
495
496
def get_history(self):
497
"""Get signal change history."""
498
return self.history.copy()
499
500
@cocotb.test()
501
async def monitoring_test(dut):
502
"""Test demonstrating signal monitoring."""
503
504
# Create monitors
505
clk_monitor = SignalMonitor(dut.clk, "Clock")
506
data_monitor = SignalMonitor(dut.data_bus, "Data Bus")
507
508
# Start monitoring
509
await clk_monitor.start_monitoring()
510
await data_monitor.start_monitoring()
511
512
# Generate activity
513
for i in range(10):
514
dut.data_bus.value = i
515
await Timer(50, units="ns")
516
517
# Stop monitoring
518
clk_monitor.stop_monitoring()
519
data_monitor.stop_monitoring()
520
521
# Analyze history
522
data_history = data_monitor.get_history()
523
cocotb.log.info(f"Data bus changed {len(data_history)} times")
524
525
for timestamp, value in data_history[-5:]: # Last 5 changes
526
cocotb.log.info(f" {timestamp}ns: {value}")
527
```
528
529
### Complex Signal Operations
530
531
```python
532
@cocotb.test()
533
async def complex_operations_test(dut):
534
"""Test demonstrating complex signal operations."""
535
536
# Multi-bit manipulation
537
control_reg = dut.control_register # 32-bit register
538
539
# Set individual control bits
540
control_reg[0].value = 1 # Enable bit
541
control_reg[1].value = 0 # Reset bit
542
control_reg[7:4].value = 0xA # Mode bits
543
544
# Read back constructed value
545
final_value = control_reg.value
546
cocotb.log.info(f"Control register: 0x{final_value:08x}")
547
548
# Array operations
549
if hasattr(dut, 'register_file'):
550
reg_file = dut.register_file
551
552
# Initialize register file
553
for i in range(32):
554
try:
555
reg = reg_file[i]
556
reg.value = i * 0x10 # Pattern
557
except:
558
break # Fewer registers than expected
559
560
# Verify pattern
561
for i in range(8): # Check first 8
562
try:
563
reg = reg_file[i]
564
expected = i * 0x10
565
assert reg.value == expected, f"R{i}: expected {expected}, got {reg.value}"
566
except:
567
break
568
569
# Memory-mapped operations
570
if hasattr(dut, 'memory'):
571
memory = dut.memory
572
573
# Write test pattern
574
test_data = [0xDEAD, 0xBEEF, 0xCAFE, 0xBABE]
575
for i, data in enumerate(test_data):
576
try:
577
memory[i].value = data
578
except:
579
pass # Memory might not support direct indexing
580
581
# Read back and verify
582
for i, expected in enumerate(test_data):
583
try:
584
actual = memory[i].value
585
assert actual == expected, f"Memory[{i}] mismatch"
586
except:
587
break
588
```