UVM--单向通信、双向通信、多向通信和通信管道

本文详细介绍了UVM Transaction Level Modeling (TLM) 中的单向、双向和多向通信机制,以及通信管道的实现。在单向通信中,数据流从initiator到target是单向的,包括阻塞和非阻塞传输。双向通信允许数据在两端双向流动,采用transport和master-slave通信方式。多向通信处理多个相同类型端口间的通信,通过端口宏避免方法命名冲突。此外,通过TLM FIFO、AnalysisPort和AnalysisTLMFIFO实现了一端到多端的数据传输,Request&Response通信管道则提供了请求响应的数据交换通道。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 单向通信

单向通信 (unidirectional communication) 指的是从 initiator 到 target 的数据流向是单一方向的, 或者 initiator 和 target 只能扮演 producer 和 consumer 中的一个角色

在UVM中,单数据流向的 TLM 端口有很多类型(部分):

uvm_blocking_put_PORT 

uvm_nonblocking_put_PORT

uvm_put_PORT 

uvm_blocking_get_PORT 

uvm_nonblocking_get_PORT 

uvm_get_PORT 

uvm_blocking_peek_PORT 

uvrn_nonblocking_peek_PORT

uvm_peek_PORT 

uvrn_blocking_get_peek_PORT

uvm_nonblocking_get_peek_PORT

uvm_get_peek_PORT 

按照 UVM 端口名的命名规则, 它们指出了通信的两个要素:

•    是不是阻寒的方式(即可以等待延时);
•    何种通信方法。

 单向通信--方法

•    阻塞传输方式将blocking前缀作为函数名的一部分, 而非阻塞方式则名为nonblocking。 阻塞端口的方法类型为task, 这保证了可以实现事件等待和延时;非阻塞端口的力式类型为function, 这确保了方法调用可以立即返回。
•    我们从方法名也可以发现, 例如uvm_blocking_put_PORT提供的方法 task put()会在数据传送完后返回,uvm_nonblocking_put_PORT对应的两个函数try_put()和can_put()是立刻返回的。
•    uvm_put_PORT则分别提供了blocking和nonblocking的方法, 这为通讯方式提供了更多选择。blocking阻塞传输的方法包含

•   Put (): initiator先生成数据T t, 同时将该数据传送至target。

•   Get(): initiator从target获取数据T t, 而target中的该数据T t则应消耗。

•   Peek(): initiator从target获取数据Tt, 而target中的该数据T t还应保留。

与上述三种任务对应的nonblocking非阻塞方法分别是:

•    try/can_put()
•    try/can_get()
•    try/can_peek()

•   这六个非阻塞函数与对应阻塞任务的区别在于,它们必须立即返回, 如果try_xxx函数可以发送或者获取数据, 那么函数应该返回1, 如果执行失败则应返回0。
•    或者通过can_xxx函数先试探target是否可以接收数据, 如果可以, 再通过try_xxx函数发送, 提高数据发送的成功率。

class itrans extends uvm_transaction;
  int id; 
  int data;
  ...
  endclass 
  
class otrans extends uvm_transaction;
  int id; 
  int data;
  ...
  endclass 
  
class compl extends uvm_component;
  uvm_blocking_put_port #(itrans) bp_port;//定义阻塞型端口
  uvm_nonblocking_get_port #(otrans) nbg_port;//定义非阻塞型端口
  `uvm_component_utils(compl)//注册
  ...
  task run_phase(uvm_phase phase);
  itrans itr;
  otrans otr;
  int trans_num = 2; 
  fork 
    begin
	 for(int i=0; i<trans_num; i++) begin
	  itr = new ("itr", this); 
      itr.id = i; 
      itr.data = 'hlO + i; 
      this.bp_port.put(itr); 
      'uvm_info ("PUT", $sformatf("put itrans id: 'h%0x , data:'h%0x",tr.id, itr.data), UVM_LOW)
      end 
	end 
	begin
	 for(int j=O; j<trans_num; j++) begin
	 forever begin
	 if(this.nbg_port.try_get(otr) == 1) break;
	 else 
	 #lns; 
     end 
	 `uvm_info("TRYGET", $sformatf("get otrans id: 'h%0x , data:'h%0x", otr.id, otr.data), UVM LOW)
	 end
	 end 
     join 
     endtask 
     endclass 
	 
class comp2 extends uvm_component;
  uvm_blocking_put_imp #(itrans, comp2) bp_imp; //定义阻塞型端口(target)
  uvm_nonblocking_get_imp #(otrans, comp2) nbg_imp; //定义阻塞型端口(target)
  itrans itr_q[$]; 
  `uvm_component_utils(comp2)
  ...
  task put(itrans t); 
  itr_q.push back(t);
  endtask 
  function bit try_get (output otrans t);
  itrans i; 
  if(itr_q.size() != 0) begin
  i = itr_q.pop_front (); 
  t = new("t", this);
  t.id = i.id;
  t.data = i.data << 8;
  return l;
  end
  else return 0;
  endfunction 
  function bit can_get();
  if(itr_q.size() != 0) return l; 
  else 
  return 0; 
  endfunction 
  endclass 
  
 class envl extends uvm_env;
   compl cl; 
   comp2 c2; 
   `uvm_component_utils(envl) 
  function void build_phase(uvm_phase phase);
  super.build_phase(phase); 
  cl = compl::type_id::create("cl", this); 
  c2 = comp2::type_id::create("c2", this);
  endfunction: build_phase 
  function void connect_phase(uvm_phase phase);
  super.connect_phase(phase); 
  cl.bp_port.connect(c2.bp_imp); 
  cl.nbg_port.connect(c2.nbg_imp);//端口连接
  endfunction: connect_phase
  endclass

首先comp1例化了两个port端口:
•     uvm_blocking_put_port #(itrans) bp_port; 
•     uvm_nonblocking_get_port #(otrans) nbg_port; 

comp2作为target则相应例化了两个对应的imp端口:
•     uvm_blocking_put_imp #(itrans, comp2) bp_imp; 
•     uvm_nonblocking_get_imp #(otrans, comp2) nbg_imp; 

env1环境将comp1与comp2连接之前, 需要在comp2中实现两个端口对应的方法:
•     task put(itrans t) 
•     function bit try_get (output otrans t) 
•     function bit can_get();

接下来env1对两个组件的端口进行了连接, 这使得comp1在run phase可以通过自身端口间接调用comp2中定义的端口方法。

因此在调用端口方法之前的几个步骤是必不可少的:
•    定义端门
•    实现对应方法
•    在上层将端口进行连接 

2 多向通信

与单向通信相同的是,双向通信(bidirectional communication)的两端也分为initiator和target,但是数据流向在端对端之间是双向的
双向通信中的两端同时扮演着producer和consumer的角色而initiator作为request发起方, 在发起request之后, 还会等待response返回。

UVM双向端口分为以下类型:
•     uvm_blocking_transport_PORT
•     uvm_nonblocking_transport_PORT
•     uvm_transport_PORT
•     uvm_blocking_master_PORT 
•     uvm_nonblocking_master_PORT 
•     uvm_master_PORT 
•     uvm_blocking_slave_PORT 
•     uvm_nonblocking_slave_PORT 
•     uvm_slave_PORT 

双向端口按照通信握手方式可以分为: 

•    transport双向通信方式
•    master和slave双向通信方式

 transport端口通过transport()方法, 可以在同一方法调用过程中完成REQ 和RSP的发出和返回
 master和slave的通信方式必须分别通过put、 get和peek的调用, 使用两个方法才可以完成一次握手通信。
master端口和slave端门的区别在于,当initiator作为master时, 它会发起REQ送至target端, 而后再从target端获取RSP; 当initiator使用slave端口时,它会先从target端获取REQ, 而后将RSP送至target端。

class compl extends uvm_component;
  uvm_blocking_transport_port #(itrans, otrans) bt_port;//定义一个transport双向端口
  `uvm_component_utils (compl)//注册
  ...
  task run_phase(uvm_phase phase);
  itrans itr; 
  otrans otr; 
  int trans_num = 2; 
  for(int i=0; i<trans_num; i++) begin
  itr = new ("itr", this); 
  itr.id = i; 
  itr.data = 'hlO + i; 
  this.bt_port.transport(itr, otr); 
  `uvm info("TRSPT", $sformatf("put itrans id: 'h%0x , data: 'h%0xet otrans id:'h%0x , data: 'h%0x ",
            itr.id, itr.data, otr.id, ctr.data), UVM_LOW)
   end 
   endtask
   endclass 
   
class comp2 extends uvm_component;
  uvm_blocking_transport_imp #(itrans, otrans, comp2) bt_imp; //定义一个transport双向端口target
  'uvm_component_utils(comp2) //注册
  task transport(itrans req, output otrans rsp); 
  rsp = new("rsp", this); 
  rsp.id = req.id; 
  rsp.data = req.data << 8;
  endtask 
endclass 

class envl extends uvm_env;
  compl cl;
  comp2 c2; 
  uvm_component_utils(envl) 
  ...
  function void build_phase(uvm_phase phase);
  super.build_phase(phase); 
  cl= compl::type_id: :create("cl", this); 
  c2= comp2::type_id: :create("c2", this);//例化
  endfunction: build_phase 
  function void connect_phase(uvm_phase phase);
  super.connect_phase(phase); 
  cl.bt_port.connect(c2.bt_imp);//连接
  endfunction: connect_phase 
  endclass 

从上面代码可以看出,双向端口处理类似于单向端口的是端口例化和连接, 不同的只是要求实现对应的双向传输任务: 

task transport (itrans req, output otrans rsp)

3 多向通信

多向通信(multi-directional communication)这个概念听起来容易让读者产生歧义, 因为这种方式服务的仍然是两个组件之间的通信, 而不是多个组件之间的通信, 毕竟多个组件的通信仍然可以由基础的两个组件的通信方式来构建。
多向通信指的是, 如果initiator与target之间的相同TLM端口数目超过一个时的处理解决办法。

comp1有两个uvm_blocking_put_port, 而comp2有两个uvm_blocking_put_imp端口, 我们对于端口例化可以给不同名字, 连接也可以通过不同名字来索引, 但问题在于comp2中需要实现两 一
个task put (itrans t), 又因为不同端口之间要求在imp端口侧实现专属方法, 这就造成了方法命名冲突, 即无法在comp2中定义两个同名的put任务。

UVM通过端口宏声明方式来解决这一问题, 它解决问题的核心在于让不同端口对应不同名的任务, 这样便不会造成方法名的冲突。 UVM 为解决多向通信问题的宏按照端口名的命名方式分为:

`uvm_blocking_put_imp_decl(SFX)

`uvm_nonblocking_put_imp_decl(SFX)

`uvm_put_imp_decl(SFX)

`uvm_blocking_get_imp_decl(SFX)

`uvm_nonblocking_get_imp_decl(SFX)

`uvm_get_imp_decl(SFX)

`uvm_blocking_peek_imp_decl(SFX)

`uvm_nonblocking_peek_imp_decl(SFX)

`uvm_peek_imp_decl(SFX)

`uvm_blocking_get_peek_imp_decl(SFX) 

`uvm_nonblocking_get_peek_imp_decl(SFX) 

`uvm_get_peek_imp_decl(SFX) 

`uvm_blocking_transport_imp_decl(SFX) 

`uvm_nonblocking_transport_imp_decl(SFX) 

`uvm_transport_imp_decl(SFX) 

`uvm_blocking_master_imp_decl(SFX) 

`uvm_nonblocking_master_imp_decl(SFX) 

`uvm_master_imp_decl(SFX) 

`uvm_blocking_slave_imp_decl(SFX) 

`uvm_nonblocking_slave_imp_decl(SFX) 

`uvm_slave_imp_decl(SFX) 

`uvm_blocking_put_imp_decl(_pl) 
`uvm_blocking_put_imp_decl(_p2)//定义了两个宏,为了target声明端口避免冲突
class compl extends uvm_component;
 uvm_blocking_put_port #(itrans) bp_portl;
 uvm_blocking_put_port #(itrans) bp_port2;
 `uvm_component_utils (compl)
 ...
 task run_phase(uvm_phase phase);
 itrans itrl, itr2; 
 int trans_num = 2;
 fork 
 for(int i=0; i<trans_num; i++) begin
 itrl = new("itrl",this); 
 itrl.id = i; 
 itrl.data='hl0 + i ; 
 this.bp_portl.put(itrl);
 end 
 for(int i=0; i<trans_num; i++) begin
 itr2 = new("itr2", this); 
 itr2.id ='hl0 + i; 
 itr2.data ='h20 + i;
 this.bp_port2.put(itr2); 
 end 
 join 
 endtask 
endclass 

class comp2 extends uvm_component;
uvm_blocking_put_imp_pl #(itrans, comp2) bt_imp_pl; //注意bt_imp_pl加了_p1
uvm_blocking_put_imp_p2 #(itrans, comp2) bt_imp_p2;
itrans itr_q[$]; 
semaphore key; //用了semaphore,顺序上有了限定
`uvm_component_utils (comp2)
...
task put_pl(itrans t);
key.get(); 
itr q.push back(t);
uvm_info("PUTPl", $sformatf("put itrans id: 'h%0x , data: 'h%0x",t.id, t.data), UVM_LOW)
key.put();
endtask 
task put_p2(itrans t); //注意put_p2加了_p1
key.get() ; 
itr q.push back(t);
`uvm_info("PUTP2", $sformatf("put itrans id: 'h%Ox , data: 'h%Ox",t.id, t.data), UVM_LOW)
key.put();
endtask 
endclass 

class envl extends uvm_env;
compl cl; 
comp2 c2;
`uvm_component_utils (env1)
...
function void build_phase(uvm_phase phase);
super.build_phase(phase); 
cl = compl::type_id::create("cl", this);
c2 = comp2::type_id::create ("c2",this);
endfunction: build_phase 
function void connect_phase(uvm_phase phase);
super.connect_phase(phase); 
c1.bp_portl.connect(c2.bt_imp_pl); 
c1.bp_port2.connect(c2.bt_imp_p2);
endfunction: connect_phase
endclass 

//仿真结果:
  uvm_test_top.env.c2 [PUTPl] put itrans id: 'h0 , data: 'hl0
  uvm_test_top.env.c2 [POTPl) put itrans id: 'hl , data: 'hll 
  uvm_test_top.env.c2 [PUTP2] put itrans id: 'hl0, data: 'h20 
  uvm_test_top.env.c2 (PUTP2] put itrans id: 'hll, data: 'h21

当一个组件的两个端口通过相同方法(比如task put())向另一个组件传输数据时, 需要使用上述的宏, 分别声明两个不同的 imp 类型, 完整的实现步骤包括:

•    选择正确的 imp 宏来定义不同的 imp 端口类型, 而宏的参数 SFX (后缀名)也会转化为相应的 imp 端口类型名。
•    在 imp 例化的组件中, 分别实现不同的 put_SFX 方法
•    在 port 例化的组件中,不需要对目标imp端口类型进行区分,例如compl 中的 bp_portl和 bp_port2 为相同的端口类型。
•    对于 comp 调用 put()方法, 它只需要选择 bp_port1 或 bp_port2, 而不需要更替 put()方法名, 即仍然按照 put()来调用而不是 put_pl()或 put_p2()。
•    在上层环境应该连接 comp1 和 comp2 的 TLM端口。

用户只需要在例化多个imp端口的组件中实现不同名称的方法,使其与对应imp类型名保持一致。而对于port端口一侧的组件,则不需关心调用的方法名称,因为该方法名并不会发生改变。所以通过这种方式可以防止通信方法名的冲突,从而解决多向通信的问题。

4 通信管道

TLM通信的实现方式, 这些通信有一个共同的地方即都是端对端的, 同时在target一端需要实现传输方法, 例如put()或者get()。

对于monitor、 coverage collector等组件在传输数据时, 会存在一端到多端的传输, 如何解决这一问题?
几个TLM组件和端口可以帮助用户免除这些烦恼:

•    TLM FIFO
•    analysis port
•    analysis TLM FIFO
•    request & response 通信管道

在 一般TLM传输过程中, 无论是initiator给target发起一个 transaction, 还是initiator从target获取一 个transaction, transaction 最终都会流向consumer中(initiator和target都可以是consumer)。

TLM FIFO

consumer在没有分析transaction时, 我们希望将该对象先存储到本地FIFO中供稍后使用。

用户需要分别在两个组件中例化端口, 同时在target中实现相应的传输方法
.多数情况下, 需要实现的传输方法都是相似的, 方法的主要内容即是为了实现一个数据缓存功能。 

TLM FIFO uvm_tlm_fifo类是一个新组件, 它继承于uvm_component 类, 而且已经预先内置了多个端口以及实现了多个对应方法供用户使用。 

一个 uvm_ tlm _ fifo 会包含多个 TLM 端口, 我们摘出常用端口:

•    put_export: 用户可以通过该端口调用 put() 、 try_put() 、 can_put()。
•    put_ap: 调用了put()方法写入的数据同时也会通过该端口的 write()函数送出。
•    get_peek_export: 用户可以通过该端口调用 get() 、 try_get() 、can_get() 、 peek() 、 try_peek()、 can_peek()。
•    get_ap: 调用了 get()和 peek()方法读出的数据也会通过该端口的 write() 函数送出。

uvm_tlm_fifo的功能类似于mailbox,不同的地方在于uvm_tlm_fifo提供了各种端口供用户使用。我们推荐在initiator端例化put_port或者get_peek_port, 来匹配uvm_tim_fifo的端口类型。如果用户例化了其它类型的端口, uvm_tlm_fifo还提供put、get以及peek对应的端口:

uvm_put_imp #(T, this_type) blocking_put_export;

uvm_put_imp #(T, this_type) nonblocking_put_export;

uvm_get_peek_imp #(T, this_type) blocking_get_export; 
uvm_get_peek_imp #(T, this_type) nonblocking_get_export;

uvm_get_peek_imp #(T, this_type) get_export; 
uvm_get_peek_imp # (T, this_type) blocking_peek_export; 
uvm_get_peek_imp #(T, this_type) nonblocking_peek_export;

uvm_get_peek_imp #(T, this_type) peek_export; 
uvm_get_peek_imp #(T, this_type) blocking_get_peek_export;

uvm_get_peek_imp #(T, this_type) nonblocking_get_peek_export;

 Analysis Port

除了端对端的传输, 在一些情况下还有多个组件会对同一个数据进们运算处理
如果这个数据是从同一个源的TLM端口发出到达不同组件, 这就要求该种端口可以满足从一端到多端的需求。
如果数据源端发生变化需要通知跟它关联的多个组件时, 我们可以利用软件的设计模式之一观察者模式(observer pattern)来实现。 

observer pattern 的核心在于, 第一, 这是从一个 initiator 端到多个 target 端的方式;第二, analysis port 采取的是 "push" 模式, 即从 initiator 端调用多个 target 端的 write()函数实现数据传输。

initiator.ap.connect(target1. aimp); 
initiator.ap.connect(target2.aimp); 
initiator.ap.connect(target3.aimp);

一个典型的 analysis port 类型端口的连接方式, 类似于其他 TLM 端口的是, 按照传输方法和端口方向组合可以将 analysis port分为uvm_ analysis _port、uvm_analysis_ export 以及 uvm_analysis_imp。 target 一侧例化了 uvm_analysis_imp 后还需要实现 write()函数。 最后, 在顶层可以将 initiator 端的 uvm _ analysis _port 同多个 target 端的 uvm_analysis_ imp 进行连接。 在 initiator 端调用wirte()函数时, 实际上它是通过循环的方式将所有连接的 target 端内置的 write()函数进行了调用。由于函数立即返回的特点, 无论连接多少个 target 端, initiator 端调用 write()函数总是可以立即返回的。这里稍微不同于之前单一端口函数调用的是,即使没有target 与之相连,调用 write() 函数时也不会发生错误。

 Analysis TLM FIFO

由于 analysis 端口实现了一端到多端的 TLM 数据传输,一个新的数据缓存组件类uvm_tlm_analysis_fifo为用户提供了可以搭配 uvm_analysis_port 端口、 uvm_analysis_imp 端口 和write()函数. uvm_tlm_analysis_fifo 类继承于 uvm_tlm_fifo, 这表明它本身具有面向单一 TLM 端口的数据缓存特性, 同时该类又有一个 uvm_analysis_imp 端口 analysis_ export 且实现了 write()函数:

uvm_analysis_imp #(T, uvm_tlm_analysis_fifo #(T)) analysis_export;

基于 initiator 到多个 target 的连接方式,用户如果想轻松实现一端到多端的数据传输,可以下图那样插入多个 uvm_ tlm _analysis_ fifo。这里给出连接方式:

•    将initiator的analysis port连接到tlm_analysis_fifo的get_export端口,这样数据可以从initiator发起, 写入到各个tlm_analysis_fifo的缓存中。
•    将多个target的get_port连接到tlm_analysis_fifo的get_export, 注意保持端口类型的匹配,这样从target一侧只需要调用get()方法就可以得到先前存储在 tlm_analysis_fifo中的数据。

initiator.ap.connect(tlm_analysis_fifol.analysis_export);

target1.get_port.connect(tlm_analysis_fifol.get_export);
initiator.ap.connect(tlm_analysis_fifo2.analysis_export);

target2.get_port.connect(tlm_analysis_fifo2.get_export);
initiator.ap.connect(tlm_analysis_fifo3.analysis_export);

target3.get_port.connect(tlm_analysis_fifo3.get_export);

  Request&Response 通信管道

双向通信端口 transport, 即通过在 target 端实现 transpo()方法可以在一次传输中既发送 request 又接收 response。

UVM提供了两种简便的通信管道, 它们作为数据缓存区域, 既有TLM端口从外侧接收request和response, 同时也有TLM端口供外侧获取request和response。 这两种TLM通估管道分别是:
•    uvm_tlm_req_rsp_channel
•    uvm_tlm_transport_channel

uvm_tlm_req_rsp_channel 提供的端口首先是单一方向的,为了让端口列表清爽一些,下面只列出该类的例化端口:

•    uvm_put_export # (REQ) put_request_export;
•    uvm_put_export # (RSP) put_response_export;
•    uvm_get_peek_export # (RSP) get_put_peek_response_export;
•    uvm_get_peek_export # (REQ) get_peek_request_export;
•    uvm _ analysis_port #(REQ) request_ap; 
•    uvm _ analysis_port #(RSP) response_ap;
•    uvm_master_imp#(REQ, RSP, this_type, uvm_tlm_fifo # (REQ), uvm_tlm_fifo #(RSP)) master_export;
•    uvm_slave_imp # (REQ, RSP, this_type, uvm _ tlm _ fifo # (REQ) , uvm_tlm_fifo#(RSP)) slave_export;

有了这么多丰富的端口, 用户可以在使用成对的端口进行数据的存储和访问。 需要注意
的是, uvm_tlm _req_rsp _ channel 内部例化了两个 mailbox 分别用来存储 request 和 response:

protected uvm_tlm_fifo#(REQ) m_request_fifo;

protected uvm_tlm_fifo #(RSP) m_response_fifo; 

例如, initiator 端可以连接 channel 的 put_request_export, target 连接 channel的 get_peek_ request_ export, 同时 target 连接 channel 的 put_response_ export, initiator 连接 channel 的 get_peek _response_ export 端口。 如下图 所示, 这种对应使得 initiator 与 target 可以利用 uvrn_tlm_req__rsp_channel 进行 request与response 的数据交换。 

通过这种对应的方式, 使得initiator与target可以利用uvm_tlm_req_rsp_channel进行request与response的数据交换。

initiator.put_port.connect(req_rsp_channel.put_request_export);
target.get_peek_port.connect(req_rsp_channel.get_peek_request_export);
target.put_port.connect(req_rsp_channel.put_response_export);
initiator.get_peek_port.connect(req_rsp_channel.getpeek_response_export);

或者, 也可以利用另一种连接方式,如下:

initiator.master_port.connect(req_rsp_channel.master_export);
target.slave_port.connect(req_rsp_channel.slave_export); 

通过所述的这些方式,我们可以实现initiator与target之间自由的request和 response传输,而这两种连接方式仍然需要分别调用两次方法才可以完成 request和response的传输。
在uvm_tlm_req_rsp _channel的基础上, UVM又添加了具备transport端口的管道组件uvm_tlm_transport_channel类。 它继承于uvm_tlm_req_rsp_channel, 并且新例化了transport端口: 

uvm_transport_imp # (REQ, RSP, this_type) transport_export;

新添加的这个 TLM FIFO 组件类型是针对于一些无法流水化处理的 request 和 response传输, 例如 initiator 端要求每次发送完 request, 必须等到 response 接收到以后才可以发送下一个 request, 这时 transport()方法就可以满足这一需求。如果将上面的传输方式进行修改,那么可以得到下图的连接方式:

需要变化的是initiator 端到req_rsp_ channel 的连接, 应该修改为:

initiator.transport_port.connect(transport_channel.transport_export) 

至于transport_channel和target之间的连接, 则可以仍然保留之前的单向传输连接方式。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

创芯人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值