如何保证UVM组件中TLM FIFO读写进程的执行顺序?
问题描述
需要实现一个包含两个TLM输入FIFO和一个输出分析端口(AP)的UVM组件:
- 一个FIFO接收用于构建内部状态的写数据包
- 另一个FIFO接收查询内部状态的读请求数据包
- 输出AP负责广播读请求对应的状态
以下是缓存建模的简化代码(省略new/build等方法):
class cache_model extends uvm_component; `uvm_component_utils(cache_model) // 两个TLM分析FIFO输入 uvm_tlm_analysis_fifo#(cache_write_pkt_t) write_af; uvm_tlm_analysis_fifo#(tag_t) read_query_req_af; // 查询响应输出端口 uvm_analysis_port#(data_t) read_query_rsp_ap; // 内部建模状态 data_t cache[tag_t]; task run_phase(uvm_phase phase); super.run_phase(phase); fork forever process_writes(); forever process_queries(); join_none endtask protected task process_writes(); cache_write_pkt_t pkt; write_af.get(pkt); // 将数据包转换为tag和data并写入缓存 endtask protected task process_queries(); tag_t tag; read_query_req_af.get(tag); read_query_rsp_ap.write(cache[tag]); endtask endclass
当前遇到的问题:同一仿真时间步内同时存在写操作和读操作时,需要保证先执行写操作,再执行读操作(让读操作获取最新写入的数据),但FIFO的数据包推入顺序无法保证这一点。之前尝试用事件同步的方案无效,因为process_queries判断write_af是否为空时,写数据包可能还未在同一时间步的后续阶段被推入FIFO。
解决方案
方案一:统一调度进程(推荐)
放弃两个独立的forever进程,改用单个循环进程优先处理写操作,再处理读操作,从根源上避免进程竞争:
class cache_model extends uvm_component; `uvm_component_utils(cache_model) uvm_tlm_analysis_fifo#(cache_write_pkt_t) write_af; uvm_tlm_analysis_fifo#(tag_t) read_query_req_af; uvm_analysis_port#(data_t) read_query_rsp_ap; data_t cache[tag_t]; task run_phase(uvm_phase phase); super.run_phase(phase); fork forever process_transactions(); join_none endtask protected task process_transactions(); cache_write_pkt_t write_pkt; tag_t read_tag; // 等待任一FIFO有数据 wait(write_af.num_items() > 0 || read_query_req_af.num_items() > 0); // 优先处理所有待处理的写操作 while (write_af.try_get(write_pkt)) begin // 转换数据包并更新缓存 cache[write_pkt.tag] = write_pkt.data; end // 处理读请求 if (read_query_req_af.try_get(read_tag)) begin read_query_rsp_ap.write(cache[read_tag]); end endtask endclass
核心逻辑:每次循环先清空所有待处理的写请求,再处理读请求。即使同一时间步内写和读数据包同时被推入FIFO,写操作会被优先处理,确保读请求拿到最新缓存状态。
方案二:事件同步+时间步确认
如果必须保留两个独立进程,可以通过uvm_event结合时间戳判断,确保读操作等待同一时间步内的所有写操作完成:
class cache_model extends uvm_component; `uvm_component_utils(cache_model) uvm_tlm_analysis_fifo#(cache_write_pkt_t) write_af; uvm_tlm_analysis_fifo#(tag_t) read_query_req_af; uvm_analysis_port#(data_t) read_query_rsp_ap; data_t cache[tag_t]; uvm_event write_done_ev; task run_phase(uvm_phase phase); super.run_phase(phase); write_done_ev = new("write_done_ev"); fork forever process_writes(); forever process_queries(); join_none endtask protected task process_writes(); cache_write_pkt_t pkt; write_af.get(pkt); // 更新缓存 cache[pkt.tag] = pkt.data; write_done_ev.trigger(); endtask protected task process_queries(); tag_t tag; time current_time; read_query_req_af.get(tag); current_time = $time; // 等待同一时间步内的所有写操作完成 while (write_done_ev.get_trigger_time() == current_time) begin write_done_ev.reset(); wait(write_done_ev.triggered()); end read_query_rsp_ap.write(cache[tag]); // 重置事件,为下一个时间步做准备 if (write_done_ev.get_trigger_time() == current_time) begin write_done_ev.reset(); end endtask endclass
核心逻辑:利用uvm_event的get_trigger_time()方法判断事件是否在当前时间步触发,确保读操作等待同一时间步内所有写操作完成后再执行。
关键方法论总结
- 优先统一调度:当多个进程存在依赖时,用单个调度进程按优先级处理不同事务,从根源避免进程竞争,代码更易维护。
- 不依赖FIFO写入顺序:TLM分析FIFO的写入顺序由外部组件决定,无法保证,组件内部不能依赖该顺序来保证处理优先级。
- 慎用
#0延迟:#0会强制跳转到当前时间步末尾,但可能引入不可预测的调度行为,仅作为最后的备选方案。
内容的提问来源于stack exchange,提问作者Sean McLoughlin
相关产品推荐
相关产品推荐

