2. uvm_user_guide_1.2 -- TLM 通信
2.1 TLM-1的实现
2.1.1 Basics
2.1.1.1 Transactions
在UVM中,transaction是包含了两个组件之间传递的信息的类对象。例如,一个传输总线协议信息的transaction以如下方式建模:
class simple_trans extends uvm_sequence_item;
rand data_t data;
rand addr_t addr;
rand enum {WRITE, READ} kind;
constraint c1 { addr < 16'h2000; }
...
endclass
transaction对象包含变量、约束以及其他需要对transaction进行操作的域和方法。此外,上面的transaction可以被扩展到包含更多信息的transaction,也可以定义包含多个其他transaction的更高级别的transaction。
2.1.1.2 Transaction级通信
Transaction级的接口定义了一系列使用transaction对象作为参数的方法。一个TLM port 在特定的连接中定义了这些方法,TLM export 提供了这些方法的实现。将 port 连接到 export 后,在 port 中可以调用该方法进行执行。
2.1.1.3 基本TLM通信
最基本的transaction级操作允许一个组件将transaction put 到另一个组件。
producer的方块表示一个 port,consumer的圆圈表示一个 export。producer产生transaction然后通过其 put_port 发送:
class producer extends uvm_component;
uvm_blocking_put_port #(simple_trans) put_port;
...
put_port = new("put_port", this); // instantiated in build phase
...
virtual task run();
simple_trans t;
for(int i=0; i<N; i++) begin
put_port.put(t);
end
endtask
注:uvm_ * _port的参数是要传输的transaction类型
put() 方法由consumer实现:
class consumer extends uvm_component;
uvm_blocking_put_imp #(simple_trans, consumer) put_export;
...
task put(simple_trans t);
...
endtask
注:uvm_ * _imp的第一个参数是要传输的transaction类型,第二个参数是实现传输方法的对象类型
另一种与 put 操作相对应的操作是 get:
在这种情况下,consumer通过 get 端口向producer请求获取transaction。
class get_consumer extends uvm_component;
uvm_blocking_get_port #(simple_trans) get_port;
...
get_port = new("get_port", this); // instantiated in build phase
...
virtual task run();
simple_trans t;
for(int i=0; i<N; i++) begin
get_port.get(t);
end
endtask
get()方法由producer实现
class get_producer extends uvm_component;
uvm_blocking_get_imp #(simple_trans, get_producer) get_export;
...
task get(output simple_trans t);
...
endtask
上面两个例子中的put和get均为blocking操作。
2.1.1.4 进程间的通信
在上面的基本 put 例子中,只有在调用了 put() 方法之后 consumer 才被激活。在许多情况中,可能需要组件独立操作,producer 在创建 transaction 的同时,consumer 需要对这些 transaction 进行操作。UVM 提供了 uvm_tlm_fifo 通道来实现这种通信,uvm_tlm_fifo 实现了所有的 TLM 接口方法,所以 producer 将 transaction 放入 uvm_tlm_fifo,consumer 从 fifo 独立获取该 transaction: 在 producer 向 fifo 放入 transaction 时,如果 fifo 已满, 则会进行等待,否则会立即放入 fifo 并返回。如果 fifo 有 transaction 可获取,get 操作会立即从 fifo 中取出 transaction 并返回,否则会进入等待,直到有 transaction 可获取。因此,两次连续的 get 操作会为 consumer 取出两个不同的 transaction。peek() 方法会返回 transaction 的副本,并不从 fifo 中取出数据。两次连续的 peek 操作会返回同一个 transaction 的两个副本。
2.1.1.5 Blocking 与 Nonblocking
上述接口操作均为 blocking ---- 任务完成之前会阻塞。没有机制可以使 blocking 调用非正常终止或者改变控制流。blocking 在请求满足前一直等待。
相反地,nonblocking 调用立即返回,这种语义保证了在发起 nonblocking 调用的同一 delta cycle 完成返回,因而不消耗时间。UVM 中,nonblocking 调用建模为 function。
class consumer extends uvm_component;
uvm_get_port #(simple_trans) get_port;
task run;
...
for(int i = 0; i < 10; i++)
if(get_port.try_get(t))
// Do something with t
...
endtask
endclass
如果存在 transaction,该 transaction 会从函数参数中返回,且函数本身会返回 TRUE。如果不存在 transaction,函数会返回 FALSE。对于 try_peek() 与 try_put() 方法,情况与之类似。
2.1.1.6 连接 transaction 级组件
在组件中定义了 port 和 export 之后,实际的连接可以由上层组件或环境调用 connect() 方法来完成。在验证环境中,port 与 export 之间的 connect() 方法调用建立了点对点和层次性连接的网表,使得发起者的 port 连接到了目标的实现。
因此,当一个组件调用了:
put_port.put(t);
连接之后意味着实际上调用了:
target.put_export.put(t);
其中,target 是被连接的组件。
2.1.1.7 点到点的连接
当要连接的组件处于同一层次时,port 总是连接到 export。所有的 connect() 调用由上层组件完成。
class my_env extends uvm_env;
...
virtual function void connect_phase(uvm_phase phase);
// component.port.connect(target.export);
producer.blocking_put_port.connect(fifo.put_export);
get_consumer.get_port.connect(fifo.get_export);
...
endfunction
endclass
2.1.1.8 Port/Export 匹配
要使连接有效,export 必须提供传输方法的实现。例如,一个 blocking_put_port 需要连接到的 blocking_put_export 或 put_export 实现 put() 方法。两个 export 都需要实现一个 put() 方法,put_export 还需要实现 try_put() 和 can_put() 方法。
2.1.2 封装和层次
TLM 接口的使用将验证环境中的每个组件独立出来,小的组件可能被封装到大的组件之中。通过使子类组件的接口在父类可见,能够实现对子类组件的访问。
2.1.2.1 层次性连接
在层次性的组件之间进行连接需要额外的操作: 上图中,producer 包含了三个组件,consumer 包含了两个组件。从 top 的角度看,producer 和 consumer 的连接关系与 Figure 4 一致,producer 的 put_port 连接到了 consumer 的 put_export。两个 fifo 是同一个 uvm_tlm_fifo 组件里的两个独立实例。
在上图中,连接A、B、D 和 F 是标准的点到点的连接。例如,连接 A 在 producer 的 connect() 方法中进行:
gen.put_port.connect(fifo.put_export);
连接 C 和 E 是一种不同类型的连接。连接 C 是 port-to-port 的连接,连接 E 是 export-to-export 的连接。这两种连接时层次性连接中所需要的。连接 C 从外面的组件向内部的组件 imports 了一个 port,连接 E 从内部的组件向外面的组件 exports 了一个 export。但是,port 和 export 的终点不一定是层次中相同的位置。使用 port-to-port 和 export-to-export 的连接来使层次边界的连接能够被更高层次访问。
对于连接 E,fifo 中实现的 export 被连接到了 consumer 的接口。在父类组件中的 export-to-export 连接以如下形式实现:
export.connect(subcomponent.export);
因此,连接 E 的代码为:
class consumer extends uvm_component;
uvm_put_export #(trans) put_export;
uvm_tlm_fifo #(trans) fifo;
...
virtual function void connect_phase(uvm_phase phase);
put_export.connect(fifo.put_export); // E
bfm.get_port.connect(fifo.get_export); // F
endfunction
...
endclass
port-to-port 的连接形式为:
subcomponent.port.connect(port);
连接 C 的代码为:
class producer extends uvm_component;
uvm_put_port #(trans) put_port;
conv c;
...
virtual function void connect_phase(uvm_phase phase);
c.put_port.connect(put_port);
...
endfunction
...
endclass
2.1.2.2 连接类型
注: port.connect() 方法的参数可以为 port 或 export,但是 export.connect() 的参数总是子组件的 export。
2.1.3 Analysis 通信
put/get ports 通常需要对应的 export 来提供传输方法的实现。对于 Analysis 通信,一个组件如 monitor,可以产生一个 transaction 流,不管是否有目标实际进行连接。analysis 组件可以通过 analysis_port 进行连接,并独立接收 transaction 流。
2.1.3.1 Analysis Ports
uvm_analysis_port 是专用的 TLM 端口,其接口只包含一个函数 write()。analysis port 连接了一系列 analysis_export,当组件调用了 analysis_port.write(),analysis_port 调用所有连接的 export 的 write() 方法。如果没有连接任何 export,则 write() 函数可以直接返回。一个 analysis_port 可以连接到任意数量的 analysis_export,但是连接的数量不会影响 analysis_port 组件的 write 操作。因为 write() 是一个 void 函数,调用总是会在相同的 delta cycle 内完成。
class get_consumer_with_ap extends get_consumer;
uvm_analysis_port #(trans) ap;
...
virtual function void build_phase(uvm_phase phase);
ap = new("analysis_port", this);
...
endfunction
task run_phase(uvm_phase phase);
...
for(int i = 0; i < 10; i++)
if(get_port.get(t)) begin
// Do something with t
ap.write(t); // Write transaction
...
end
endtask
endclass
在上层组件中,将 analysis port 连接到 analysis export。
2.1.3.2 Analysis Exports
每个连接到 analysis port 的组件,都要为 analysis export 实现各自的 write() 方法。uvm_subscriber 类可以用于简化这个操作:
class sub1 #(type T = simple_trans) extends uvm_subscriber #(T);
...
my_env env;
function void write(T t);
// Call desired functionality in parent
endfunction
endclass
如果有多个 exports 连接到一个 analysis port,端口会调用每个 export 的 write() 方法。由于 write 方法为函数,因此 analysis port 的 write 方法调用立即完成,不管连接了多少个 export。
class my_env extends uvm_env;
get_component_with_ap g;
sub1 s1;
sub2 s2;
virtual function void build_phase(uvm_phase phase);
s1 = new("s1");
s1.env = this;
s2 = new("s2");
s2.env = this;
endfunction
virtual function void connect_phase(uvm_phase phase);
g.ap.connect(s1.analysis_export);
// to illustrate analysis port can be connected to multiple
// subscribers; usually the subscribers are in separate components
g.ap.connect(s2.analysis_export);
endfunction
endclass
当调用了 write 方法之后,每个连接的 analysis export 都会收到指向同一个 transaction 的句柄。在 write 函数的实现中,必须对 transaction 进行 copy。
UVM 有一个 analysis_fifo,是一个含有 analysis export 的 uvm_tlm_fifo。
|