4.3 组件间的通信(Communicating Components)
这一节,我们将回顾组件间的事务传递的基本含义。我们将分析put、get以及transport这三种形式的事务通信方式。这里的所举的例子仅仅是用来说明事务级通信的基本机制,因而不使用AVM库,以免引入过多的细枝末节。在下一节中,我们将看到使用AVM库的更多、更完整的事务级通信的例子。
4.3.1 Put
Put 是一种“给予”配置。一个组件向另一个组件发送事务信息,这样的操作称为put。发起传递操作的组件称之为“发起方”(initiator),而接收事务信息的组件称之为“目标方”(target)。
使用TLM的术语,我们这样描述这个传递动作:“发起方put事务信息到目标方。”(The initiator puts transaction to the target.)
图4-1 Put
图4-1描述的是A puts 事务信息到B。发起方有一个用方框表示的port,而目标有一个用圆形框表示的export。控制流的方向是从方框到圆形框,也就是,A调用B。图中箭头指示了数据流的方向,本例中,数据从A移动到B。
我们将以一个“生产者”(producer)和一个“使用者/消费者”(consumer)为例,说明这些组件的SystemC和SystemVerilog代码。producer是发起方,而consumer是目标方。在构建这些组件时,我们必须做到:既为它们增加某种通信接口,又不能让它们通过这个接口知晓对方的存在。为达到这个目的,我们使用纯虚接口(pure virtual interface)来定义那个发起方向目标方传递数据的函数。首先,让我们来看一下SystemVerilog版本的producer的代码。
Producer是一个类(class),这意味着它是被动态创建的。它有两个主要成员:一个run()任务和一个put_port接口。在run()任务中,做10次循环,每次循环puts一个事务信息(transaction)。为了简化,这里的事务信息(transaettion)就是一个整型数。而实际应用中,事务信息(transaetion)可以是任意复杂的对象,例如结构(struct)或类(class)。
Producer通过调用put_port接口的put()函数来发送事务信息(transaettion)。这里的“put_port接口”到底是什么呢?它不同于Verilog语言中的传统意义上的port。它是一个指向put_if的一个引用(reference)。那么,“put_if”又是什么呢?put_if是一个虚接口类(virtual interface class),initiator(producer)和target(consumer)共享这个虚接口。
Put_if 类有一个纯虚任务,也就是说,这个类里并没有提供这个任务的具体实现。虚类(virtual class)由于没有提供任务和函数的实现(implementation),所以不能被实例化的,它必须是其他可被实例化的类的基类。在本例中,consumer就是由纯虚类put_if 类派生而来的。
consumer包含了在put_if类定义的纯虚任务put()的实现(函数体)。在put()任务中接收并打印传递给它的参数。put_if是producer和consumer连接的关键,producer侧的指向它的引用我们称之为“port”。port要求欲和它相连的对象必须提供接口中的函数和任务(这里就是put())的实现。consumer也是由put_if类派生而来的,因此,它必须提供能满足port所要求的纯虚任务的实现。
producer和consumer的上一层模块将它们连接起来。
请注意第81行:
这行代码使得producer和consumer被连接到一起。当p的构造函数new()被调用后,产生了producer的一个新的实例(instance),而这时这个实例的成员put_port还没有值(只是一个指向为null的handle)。在连接赋值(即81行)之前调用put_port.put()将会导致运行失败(run-time failure)。把c赋值给p.put_port这个操作,为port提供了一个指向consumer的引用(reference),接口任务put()的实现则包含在所指向的consumer中。
put在SystemC中的实现与上述的SystemVerilog的实现是非常相似的,使用一个纯虚接口(pure virtual interface)来连接initiator和target。我们使用一个sc_export来把纯虚接口连接到producer。
像SystemVerilog的consumer一样,SystemC的consumer从纯虚接口put_if派生而来,它提供了put()的实现。
而纯虚接口类put_if提供了用于producer和consumer之间通信的函数的定义。
同样,上一层模块把producer和consumer连接到一起。
在SystemVerilog中,我们使用赋值语句来连接port和export,而在SystemC中,我们重载operator()函数来完成连接。构造函数通过调用p.put_port(c)来连接port和export。
4.3.2 Get
与put相对的就是get了。在这个过程中,initiator从target接收事务信息(transaction)。控制流的方向与put是一样的:从initiator到target,但数据流的方向正好相反。initiator从target获取(get)事务信息。在这种情况下,consumer是initiator,而producer则是target。consumer发起一个函数调用来从producer取来一个事务信息。
图4-2 Get
图4-2和图4-1非常相似。唯一不同的是,这里箭头的方向是从target到initiator。箭头的方向显示了数据流的方向是从target到initiator。下面是consumer(initiator)的SystemVerilog代码。
consumer有一个任务run(),它迭代(循环)10次来获取10个事务信息(transaction)。就像put例子中的producer一样,这里的consumer也有一个port。同样,这个port也是一个指向纯虚接口的引用,这里,它称为get_if。
Get_if是一个定义了任务get()的纯虚接口类。target(producer)的构造方式与put例子中的target类似,它包含了接口任务get()的实现。下面的例子中,producer产生一个介于0和99之间的随机数。
上一层次的连接方法也是相似的。
通过调用new()创建了producer和consumer的实例(instance)后,使用连接赋值把两个组件连接起来。
在SystemC里,正如你所预料的那样,consumer使用sc_export来连接producer。
producer实现了接口函数get()。与SystemVerilog版本一样,它是一个介于0和99之间的随机数。
纯虚接口类get_if与put_if类似,并没有什么特殊之处。
同样,consumer和producer也是在上一层模块里完成连接。
4.3.3 Transport
Transport是一种双向配置。通过这个接口,我们既可以把事务信息(transacttion)从initiator发送到target,又可以把事务信息从target接收回到initiator。典型地,我们使用transport来建模请求/响应协议(request/re sponse protocols)。当谈到双向通信的组件时,我们用术语master到代替initiator,用slave来代替target。
图4-3中,master(A) 在一次函数调用中,既进行put操作,又进行get操作。正如我们在前面的章节中看到的,put()和get()都只有一个参数,这个参数就是它们想要通信的内容。而对于transport()任务来说,它有两个参数,即一个请求和一个响应。transport()发送请求,然后返回响应。slave(B)接收到请求后,通过响应来应答master(A)。
图4-3 transport
让我们先看看这个纯虚接口。
这个接口只有一个任务:transport()。这个任务有两个参数,一个是被传递到target的request,一个是返回给initiator的response。
master调用transport()来创建请求,并通过transport把请求发送给slave。它还处理返回来的响应。
而slave实现了transport()任务。在我们的这个例子中,它做了很简单的处理来产生响应。
上一层模块使用与put/get相同的方法来连接master和slave。
89行的连接赋值语句,把master和slave连接到一起。当这个赋值操作完成后,master就可以直接通过这个连接来调用slave的函数了。
现在我们来看看transport在SystemC中的实现。同样地,其实现的方式仍然与SystemVerilog的非常相似。master和slave通过一个export和一个通用的接口进行连接。
transport接口有一个transport()函数。注意到,与SystemVerilog版本不同,这个函数有一个参数和一个返回值,而不是有两个参数。request是通过函数参数发送给slave的,而response则是通过函数返回值返回给master的。之所以有这样的区别,是因为SystemVerilog的task不允许有返回值。
master非常简单,它通过一个export。通过这个export,master可以调用transport()函数。
slave则实现了transport()函数。像SystemVerilog版本一样,它仅做简单的处理来产生响应。
4.3.4 阻塞和非阻塞(Blocking vs.Nonblocking)
在前面的章节中,我们看到的接口都是阻塞(blocking)的。这意味着函数和任务将阻塞它的执行,直到返回。一个阻塞调用是不允许失败的。并不存在什么机制来异常地终止阻塞调用,或者改变阻塞调用的控制权。阻塞调用只是简单地等待,直到返回。在与时间相关的系统(timed system)中,意味着在函数的调用点和返回点之间,时间可能已经向前推进。
在put配置中,我们有两个组件:producer和consumer。producer产生一个随机数并通过put()把它发送到consumer。在put()被调用之前,consumer没有做任何动作。put()的调用使得consumer把参数的值打印出来。在consumer活跃期间(即put()函数的执行期间),producer处于等待状态。这是一种天然的阻塞调用。主调者(或主调函数)必须一直处于等待状态,直到被调用函数结束并返回。
与阻塞调用相对应的是非阻塞调用(nonblocking call)。非阻塞调用在调用点就立刻返回。术语nonblocking保证函数调用点和返回点处于同一个delta cycle,也就是两点之间没有消耗任何时间,甚至是一个delta cycle也没有消耗。
连接nonblocking slave和master之间的纯虚接口与前面提到的其他纯虚接口非常相似,最主要的不同是,nb_get()返回一个状态值而不是事务信息。
master(consumer)必须检查nb_get()返回的状态值,以确定函数是否成功结束。请注意在下面的例子中,我们在模型中加入了时间信息。consumer每隔4ns检查一次返回值是否有效。
下面的代码把producer组织为一个nb_get()函数加一个run()任务。run()任务里有一个永不停止的循环,因此,它是一个持续执行的进程。它不断地产生consumer将拿走的随机数。可以看到,这个随机数每7ns里有2ns时间是有效的(即等于1)。而nb_get()函数就是nb_get具体的实现了,它通过输出参数t返回run()所产生的周期性的随机数的值。
当我们运行这个例子时,可以看到,并不是每次调用nb_get()都能成功。
对于阻塞get应用中,仅有一个进程——consumer不断地请求producer发送新的值。与阻塞get接口不同的是,非阻塞get应用中,有两个进程:Consumer按一定的规律查询producer,看是否有数据可以拿走;而producer则异步地产生新的数据。在本例中,nonblocking producer每7ns产生一个新的数据,即,等待了5ns后,产生一个新的数据,并让这个新数据保持2ns的有效时间。标记rand_avail当有效的随机数可用时被置位,不可用时被清除。
在本例函数nb_get()的实现中,必须检查rand_avail,以便知道是否确实有数据待发送。如果没有数据待发送,则返回0来指示request失败;如果有数据待发送,则发送数据并返回1来指示request成功。
阻塞接口在同步地操作两个组件时非常有用。无论需要消耗多长的时间,阻塞调用都会一直处于等待状态,直到请求操作结束。而非阻塞接口则在异步地操作两个组件时非常有用。这类接口不会等待,可以用来查询target,如前面的例子所述。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。