rad_cdc_mcp — DV Guide¶
Overview¶
This verification environment implements a dual-agent architecture to properly model the independent source and destination clock domains of the rad_cdc_mcp dual-clock Multi-Cycle Path (MCP) synchronizer.
Design Rationale¶
The rad_cdc_mcp RTL has:
- a-domain (source): Operates on
aclk, usesarst_n, controlsasendandadatain, observesaready - b-domain (destination): Operates on
bclk, usesbrst_n, controlsbload, observesbdataandbvalid
These domains are independent - in a real system, they would be controlled by separate blocks that only see their respective interface signals. This fundamental architectural constraint led to the dual-agent design.
Dual-Agent Architecture¶
Agent 0: a-domain (Source)¶
- Clock:
aclk(default 1000ps period) - Reset:
arst_n - Driver:
RadCdcMcpWriteDriver- Drivesasend,adatainonaclkedges - Monitors:
RadCdcMcpWriteMonitorIn- Samplesasend,adatainonaclkRadCdcMcpWriteMonitorOut- Samplesareadyonaclk
- Item:
RadCdcMcpWriteItem- Inputs:
asend,adatain - Outputs:
aready(feedback from DUT)
- Inputs:
- Sequence:
RadCdcMcpWriteSequence- Generates random send transactions with configurable probability (default 0.7)
- Protocol enforcement handled by driver, not sequence
- Generates random
adatainvalues within configured data width
Agent 1: b-domain (Destination)¶
- Clock:
bclk(default 1200ps period) - Reset:
brst_n - Driver:
RadCdcMcpReadDriver- Drivesbloadonbclkedges - Monitors:
RadCdcMcpReadMonitorIn- SamplesbloadonbclkRadCdcMcpReadMonitorOut- Samplesbdata,bvalidonbclk
- Item:
RadCdcMcpReadItem- Inputs:
bload - Outputs:
bdata,bvalid(feedback from DUT)
- Inputs:
- Sequence:
RadCdcMcpReadSequence- Generates random load transactions with configurable probability (default 0.7)
- Protocol enforcement not required (driver does not implement backpressure)
Component Structure¶
RadCdcMcpEnv (num_agents=2)
├── agent0 (a-domain / source)
│ ├── drv: RadCdcMcpWriteDriver
│ ├── sqr: BaseSequencer[RadCdcMcpWriteItem]
│ ├── mon_in: RadCdcMcpWriteMonitorIn
│ └── mon_out: RadCdcMcpWriteMonitorOut
├── agent1 (b-domain / destination)
│ ├── drv: RadCdcMcpReadDriver
│ ├── sqr: BaseSequencer[RadCdcMcpReadItem]
│ ├── mon_in: RadCdcMcpReadMonitorIn
│ └── mon_out: RadCdcMcpReadMonitorOut
├── mon_rst: BaseResetMonitor (arst_n, configured but not used)
├── reset_sink: BaseResetSink (configured but not used)
├── mon_arst: BaseResetMonitor (arst_n) → arst_sink
├── mon_brst: BaseResetMonitor (brst_n) → brst_sink
├── arst_sink: RadCdcMcpResetSink → routes to ref_model.arst_change()
├── brst_sink: RadCdcMcpResetSink → routes to ref_model.brst_change()
├── sb: RadCdcMcpSb
│ ├── prd: RadCdcMcpSbPredictor[RadCdcMcpRefModel]
│ └── cmp: BaseSbComparator
└── cov: RadCdcMcpCoverage
Test Execution Flow¶
- Clock Setup: Test creates independent
aclk_driverandbclk_driver - Reset Setup: Test creates independent
arst_driverandbrst_driver - Factory Overrides: Instance-specific overrides for each agent
*.agent0.*→ a-domain (source) components*.agent1.*→ b-domain (destination) components- Run Phase: Sequences run concurrently using
cocotb.start_soon()
```python write_task = cocotb.start_soon(write_seq.start(self.env.agents[0].sqr)) read_task = cocotb.start_soon(read_seq.start(self.env.agents[1].sqr))
await write_task await read_task ```
Protocol Enforcement (Backpressure)¶
Backpressure is handled in the drivers, not the sequences. This separation allows:
- Sequences to focus on stimulus generation (probabilities, patterns)
- Drivers to enforce protocol correctness (respecting ready/valid flags)
a-domain Driver (Write) Backpressure¶
The a-domain driver samples aready at each drive edge and stalls if necessary:
while True:
await self.clock_drive_edge() # Advance to negedge of aclk
aready_now = utils_dv.get_signal_value_int(dut.aready.value)
if tr.asend and not aready_now:
# Backpressure: MCP is not ready, stall this send
dut.asend.value = 0
# Loop to next cycle and re-check aready
else:
# Either no send requested, or MCP is ready
dut.asend.value = tr.asend
dut.adatain.value = tr.adatain
break # Exit after successful drive
b-domain Driver (Read) - No Backpressure¶
The b-domain driver does not implement backpressure logic:
await self.clock_drive_edge() # Advance to negedge of bclk
dut.bload.value = tr.bload
# Can always drive bload regardless of bvalid state
Sequence Generation¶
Sequences generate transactions based on probability, independent of MCP state:
# a-domain send sequence
item.asend = 1 if random.random() < self.send_prob else 0
if item.asend:
item.adatain = random.randint(0, (1 << self.dsize) - 1)
# b-domain load sequence
item.bload = 1 if random.random() < self.load_prob else 0
This design ensures protocol correctness while allowing flexible stimulus patterns.
Reset Handling¶
The environment supports independent reset domains with domain-specific routing:
- a-domain Reset Path:
mon_arst→arst_sink→ref_model.arst_change() - b-domain Reset Path:
mon_brst→brst_sink→ref_model.brst_change()
Each reset sink (RadCdcMcpResetSink) routes reset events to:
- The corresponding driver (agent0.drv or agent1.drv) via
drv.reset_change() - The reference model's domain-specific method (
arst_changeorbrst_change)
The reference model (RadCdcMcpRefModel) implements:
arst_change(value, active): Clears a-domain state when activebrst_change(value, active): Clears b-domain state when activereset_change(value, active): Legacy single-reset method (clears both domains)
Key Design Decisions¶
Why Dual Agents?¶
Rejected alternatives:
- ❌ Single sequence controlling both domains - unrealistic, violates clock domain separation
- ❌ Dual sequences with single agent - complexity in driver/sequencer routing
- ❌ Single sequence with dual clock driver - doesn't model real system behavior
- ✅ Dual agents - clean separation, matches real system architecture
Why Backpressure in a-domain Driver Only?¶
- Protocol enforcement separate from stimulus generation
- a-domain (source) must respect
areadyto ensure MCP can accept data - b-domain (destination) can assert
bloadfreely; data validity is checked viabvalid - Sequences focus on test patterns (random, burst, corner cases)
- Drivers ensure protocol correctness at the interface level
- Models real hardware behavior: protocol is enforced at interface level
Why cocotb.start_soon?¶
- Both sequences must run concurrently (not sequentially)
- Send and load operations happen simultaneously in different clock domains
cocotb.start_soon()is the cocotb-native way to launch concurrent tasks- Unlike
asyncio.gather(), it works properly with cocotb's scheduler
Files¶
Transaction Items¶
rad_cdc_mcp_item.py- Contains both transaction classes:RadCdcMcpWriteItem- a-domain (asend, adatain, aready)RadCdcMcpReadItem- b-domain (bload, bdata, bvalid)
Drivers¶
rad_cdc_mcp_driver.py- Contains both driver classes:RadCdcMcpWriteDriver- Drives a-domain inputs on aclk with backpressureRadCdcMcpReadDriver- Drives b-domain inputs on bclk (no backpressure)
Sequences¶
rad_cdc_mcp_sequence.py- Contains both sequence classes:RadCdcMcpWriteSequence- Generates send transactionsRadCdcMcpReadSequence- Generates load transactions
Monitors¶
rad_cdc_mcp_monitor_in.py- Contains both input monitor classes:RadCdcMcpWriteMonitorIn- Samples asend, adatain on aclkRadCdcMcpReadMonitorIn- Samples bload on bclk
rad_cdc_mcp_monitor_out.py- Contains both output monitor classes:RadCdcMcpWriteMonitorOut- Samples aready on aclkRadCdcMcpReadMonitorOut- Samples bdata, bvalid on bclk
Environment & Test¶
rad_cdc_mcp_env.py- Environment withnum_agents=2and dual reset monitorstest_rad_cdc_mcp.py- Test with dual clocks/resets/sequences
Shared Components¶
rad_cdc_mcp_ref_model.py- Golden model with dual-reset supportrad_cdc_mcp_coverage.py- Functional coverage for both domainsrad_cdc_mcp_reset_sink.py- Reset event router with configurable method names
Configuration¶
Clock Configuration¶
# a-domain clock (default 1000ps)
ACLK_PERIOD_PS=1000 ACLK_ENABLE=1
# b-domain clock (default 1200ps, shows async behavior)
BCLK_PERIOD_PS=1200 BCLK_ENABLE=1
Reset Configuration¶
# a-domain reset
ARST_ENABLE=1 ARST_ACTIVE_LOW=1 ARST_CYCLES=10
# b-domain reset
BRST_ENABLE=1 BRST_ACTIVE_LOW=1 BRST_CYCLES=10
Sequence Configuration¶
# a-domain sequence length (number of send transactions)
RAD_CDC_MCP_WRITE_SEQ_LEN=100
# b-domain sequence length (number of load transactions)
RAD_CDC_MCP_READ_SEQ_LEN=100
MCP Configuration¶
# Data width (default 8 bits)
RAD_CDC_MCP_DSIZE=8
Future Enhancements¶
Potential improvements:
- Variable timing sequences: Add random delays between transactions
- Burst sequences: Send/load bursts with configurable patterns
- Stress sequences: Maximum rate sends and loads
- Corner case sequences: Single item, simultaneous send/load edge cases
- Protocol violations: Intentionally violate timing constraints
- Coverage-driven: Use coverage feedback to guide sequence generation
- Back-to-back operations: Test consecutive sends/loads without idle cycles
Licensing¶
See the LICENSES directory at the repository root.