如何实现在线切换UVM VIP时钟极性并优化时钟块代码?
Great question! Duplicating clock edge checks across multiple tasks (like your wait_clock_event and sample_data) is definitely error-prone and redundant, especially when you need on-the-fly polarity switching. Let's break down a cleaner, more maintainable approach:
Core Idea: Centralize Clock Logic in the Interface
Since your interface is the layer that directly interacts with the DUT's clock and signals, it's the perfect place to encapsulate all clock polarity-related logic. This way, your monitor only needs to call simple, unified methods instead of repeating conditional checks everywhere.
Step 1: Update the Interface with Wrapped Methods
Keep your two clock blocks (they're essential for maintaining proper sampling timing with #1step), but add helper tasks/functions to hide the polarity check:
interface my_dut_if(input clk); // Existing clock blocks for both edges clocking passive_cb @(posedge clk); default input #1step; logic data; endclocking clocking passive_cbn @(negedge clk); default input #1step; logic data; endclocking // Wrapped task to wait for the configured clock edge task wait_clock_event(int polarity); if (polarity == 0) @passive_cb; else @passive_cbn; endtask // Wrapped function to sample data from the correct clock block function logic get_sample(int polarity); return (polarity == 0) ? passive_cb.data : passive_cbn.data; endfunction endinterface
Step 2: Simplify the Monitor Code
Now your monitor can use these interface methods to eliminate redundant conditionals entirely:
class my_monitor extends uvm_monitor; `uvm_component_utils(my_monitor) virtual my_dut_if vif; my_cfg cfg; my_pkt pkt; function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); if (!uvm_config_db#(virtual my_dut_if)::get(this, "", "vif", vif)) begin `uvm_fatal(get_full_name(), "Failed to get virtual interface") end if (!uvm_config_db#(my_cfg)::get(this, "", "cfg", cfg)) begin `uvm_fatal(get_full_name(), "Failed to get config") end pkt = my_pkt::type_id::create("pkt"); endfunction virtual task run_phase(uvm_phase phase); super.run_phase(phase); forever begin // Wait for the configured clock edge (using interface wrapper) vif.wait_clock_event(cfg.pol); // Sample data (no more conditional checks!) pkt.data = vif.get_sample(cfg.pol); // Send packet to analysis port, etc. `uvm_info(get_full_name(), $sformatf("Sampled data: 0x%0h", pkt.data), UVM_MEDIUM) analysis_port.write(pkt); end endtask endclass
Why This Works Better
- Reduced redundancy: The polarity check lives in exactly one place (the interface) instead of being duplicated across tasks.
- Lower error risk: You won't accidentally forget a conditional in one task (e.g., updating
wait_clock_eventbut notsample_datawhen adding new features). - Cleaner monitor code: The monitor focuses on its core job (sampling and sending packets) instead of low-level clock timing logic.
- On-the-fly switching still works: Since we check
cfg.polevery time we call the interface methods, switching polarity during runtime will take effect on the next clock edge.
Bonus: Even Tighter Integration
If you want to go one step further, you can combine the wait and sample into a single interface task to make the monitor code even more concise:
// In the interface task sample_on_edge(int polarity, output logic sampled_data); wait_clock_event(polarity); sampled_data = get_sample(polarity); endtask // In the monitor's run phase forever begin vif.sample_on_edge(cfg.pol, pkt.data); analysis_port.write(pkt); end
This reduces the monitor to just a single line of core sampling logic!
内容的提问来源于stack exchange,提问作者AldoT

