GEM5 Garent CPU cache消息传递路径:1. NI部分

news2024/11/23 6:50:02

简介

我们仔细分析下图怎么连的,以及消息传递路径。
在这里插入图片描述
图来自https://www.gem5.org/documentation/general_docs/ruby/

代码的连接

fs.py->ruby.py-> gem5/configs/ruby/MESI_Two_Level.py 中的
create_system( options, full_system, system, dma_ports, bootmem, ruby_system, cpus ):

cpu_sequencers = []
cpu_seq = RubySequencer(
    version=i,
    dcache=l1d_cache,
    clk_domain=clk_domain,
    ruby_system=ruby_system,
l1_cntrl.sequencer = cpu_seq  #关联Sequencer与L1缓存控制器
exec("ruby_system.l1_cntrl%d = l1_cntrl" % i)# 动态添加Sequencer到Ruby系统

cpu_sequencers.append(cpu_seq) # 将Sequencer添加到列表
)

创建一个RubySequencer实例cpu_seq。RubySequencer是gem5中Ruby缓存一致性协议的一部分,它充当CPU和Ruby缓存系统之间的接口。
version=i:为这个sequencer指定一个版本号或ID,通常对应于处理器核心的ID。
dcache=l1d_cache:将这个sequencer关联到一个L1数据缓存(L1D)实例。
clk_domain=clk_domain:为sequencer指定时钟域。
ruby_system=ruby_system:将这个sequencer关联到整个Ruby系统。

然后关联Sequencer与L1缓存控制器

l1_cntrl.sequencer = cpu_seq

这行代码将cpu_seq作为sequencer设置给L1缓存控制器(l1_cntrl)。这意味着L1缓存控制器将使用cpu_seq来处理来自CPU的请求。
动态添加Sequencer到Ruby系统

exec("ruby_system.l1_cntrl%d = l1_cntrl" % i)

使用exec函数动态地将L1缓存控制器(l1_cntrl)添加到Ruby系统中。这里,控制器被命名为l1_cntrl%d(其中%d是通过格式化字符串替换为i的值),这样每个控制器都有一个唯一的名称。
将Sequencer添加到列表。然后最后会返回这个cpu_sequencers给到 ruby.py中的create system。

cpu_sequencers.append(cpu_seq)

ruby.py中的create system声明,ruby._cpu_ports是我们刚创建的cpu_sequencers。

    ruby._cpu_ports = cpu_sequencers
    ruby.num_of_sequencers = len(cpu_sequencers)

FS.PY中将l1dcache相连的 ruby._cpu_ports (同时也是cpu_sequencers)和cpu相连。

 for (i, cpu) in enumerate(test_sys.cpu):
            # Tie the cpu ports to the correct ruby system ports
            cpu.clk_domain = test_sys.cpu_clk_domain
            cpu.createThreads()
            cpu.createInterruptController()

            test_sys.ruby._cpu_ports[i].connectCpuPorts(cpu)

这里调用了 gem5/src/mem/ruby/system/Sequencer.py 中的connectCpuPorts

    def connectCpuPorts(self, cpu):
        """
        Helper for connecting all cpu memory request output ports to this
        object's in_ports.
        This assumes the provided cpu object is an instance of BaseCPU. Non-cpu
        objects should use connectInstPort and connectDataPort.
        """
        import m5.objects

        assert isinstance(cpu, m5.objects.BaseCPU)
        # this connects all cpu mem-side ports to self.in_ports
        cpu.connectAllPorts(
            self.in_ports, self.in_ports, self.interrupt_out_port
        )

而这使用了 cpu.connectAllPorts。 在gem5/src/cpu/BaseCPU.py中

def connectAllPorts(self, cached_in, uncached_in, uncached_out):
        self.connectCachedPorts(cached_in)
        self.connectUncachedPorts(uncached_in, uncached_out)
def connectCachedPorts(self, in_ports):
        for p in self._cached_ports:
            exec(f"self.{p} = in_ports")
def connectUncachedPorts(self, in_ports, out_ports):
        for p in self._uncached_interrupt_response_ports:
            exec(f"self.{p} = out_ports")
        for p in self._uncached_interrupt_request_ports:
            exec(f"self.{p} = in_ports")

分析

  1. CPU与Sequencer的连接
    Ruby的Sequencer(RubySequencer)充当了CPU和Ruby缓存系统之间的桥梁。
    每个CPU核心通过它的内存端口连接到一个对应的Sequencer。这是通过connectCpuPorts函数完成的,该函数定义在Sequencer.py中。
    connectCpuPorts函数调用cpu.connectAllPorts,将CPU的所有缓存相关端口(包括用于缓存请求的端口和中断请求/响应端口)连接到Sequencer的端口。
  2. Sequencer与L1缓存的连接
    在MESI Two Level协议中,每个Sequencer都与一个私有的L1数据缓存(L1D)相关联。
    当CPU发出缓存请求时,这些请求首先被发送到对应的Sequencer。然后,Sequencer负责将这些请求转发到L1缓存。
    如果L1缓存命中,请求的数据直接从L1缓存返回给CPU。如果未命中,请求被进一步发送到L2缓存。
  3. L1缓存与L2缓存的连接
    L2缓存是一个共享缓存,为所有的CPU核心服务。
    在MESI Two Level协议中,L1缓存与L2缓存维持包含关系(Inclusion),这意味着L1缓存中的所有数据也必须存在于L2缓存中。
    当L1缓存未命中时,L1缓存控制器(L1 cache controller)会将请求发送到L2缓存控制器(L2 cache controller)进行处理。
  4. 连接的整体流程
    当CPU需要读取或写入内存时,它通过与之相连的Sequencer发出请求。
    Sequencer首先检查与之关联的L1缓存是否能满足这个请求。
    如果L1缓存未命中,请求会被发送到共享的L2缓存。
    L2缓存检查是否能满足该请求。如果不能,请求可能会被发送到更高级别的缓存或内存系统。
    这个流程确保了gem5仿真中的内存访问请求能够正确地在多级缓存体系结构中流动,同时维护缓存一致性和数据的有效性。

我们刚刚讲了cpu找l1cache的连接,下面看l1 和l2之间的连接。这里的连接是通过noc的,也就是通过routers。

l1 l2 和 noc routers

fs.py 调用 ruby.create_system,用了configs/network/Network.py 中的 Network.create_network(options, ruby)

network是什么

gem5/configs/ruby/Ruby.py 中 create_system调用了 gem5/configs/network/Network.py 中create_network,使用了下面的代码创建了一个network。
network = GarnetNetwork(
ruby_system=ruby,
topology=options.topology,
routers=[],
ext_links=[],
int_links=[],
netifs=[],
)

GarnetNetwork初始化/创建

gem5/configs/network/Network.py 中的 GarnetNetwork 来自于 gem5/src/mem/ruby/network/garnet/GarnetNetwork.py 中 class GarnetNetwork(RubyNetwork):
而 RubyNetwork 来自于 gem5/src/mem/ruby/network/Network.py class RubyNetwork(ClockedObject)。

if options.network == "garnet":
    NetworkClass = GarnetNetwork
    IntLinkClass = GarnetIntLink
    ExtLinkClass = GarnetExtLink
    RouterClass = GarnetRouter
    InterfaceClass = GarnetNetworkInterface

如我另一篇博客里 gem5 garnet l1 l2 cache的创建与相连 所说 ,每个router有3个extlinks链接了l1 l2和dir。同时有int links和其他router相连。


# Create the network topology
topology.makeTopology(
options, network, IntLinkClass, ExtLinkClass, RouterClass
)
之后,是初始化。
# Initialize network based on topology
Network.init_network(options, network, InterfaceClass)

我们看config.ini中的结果:
l1_cntrl0.sequencer一边是cpu的指令与数据cacheport作为inport,一边并没有outort,而是有一个port叫 mem_request_port。

[system.ruby.l1_cntrl0.sequencer]
in_ports=system.cpu.icache_port system.cpu.dcache_port system.cpu.mmu.itb.walker.port system.cpu.mmu.dtb.walker.port system.cpu.interrupts.int_requestor
mem_request_port=system.iobus.cpu_side_ports[3]#  system.ruby.l1_cntrl0.sequencer.mem_request_port

查看 system.iobus发现cpu_side_ports[3] 是system.ruby.l1_cntrl0.sequencer.mem_request_port。

[system.iobus]
cpu_side_ports=system.pc.south_bridge.io_apic.int_requestor \
system.sys_port_proxy.pio_request_port   \
system.ruby.l1_cntrl0.sequencer.pio_request_port  \
system.ruby.l1_cntrl0.sequencer.mem_request_port
default=system.pc.default_bus.cpu_side_ports[0]
mem_side_ports=system.pc.south_bridge.cmos.pio system.pc.south_bridge.dma1.pio system.pc.south_bridge.ide.pio system.pc.south_bridge.keyboard.pio system.pc.south_bridge.pic1.pio system.pc.south_bridge.pic2.pio system.pc.south_bridge.pit.pio system.pc.south_bridge.speaker.pio system.pc.south_bridge.io_apic.pio system.pc.com_1.pio system.pc.fake_com_2.pio system.pc.fake_com_3.pio system.pc.fake_com_4.pio system.pc.fake_floppy.pio system.pc.pci_host.pio system.ruby.l1_cntrl0.sequencer.pio_response_port system.ruby.io_controller.dma_sequencer.in_ports[0]

这里相当于自己引用自己,其实是一个东西。而定义不在结果里,在代码src/mem/ruby/system/Sequencer.py中。还有一个插曲,这个之前叫 mem_master_port ,被弃用了,现在2023年叫mem_request_port。

class RubyPort(ClockedObject):
   mem_request_port = RequestPort("Ruby mem request port")
   mem_master_port = DeprecatedParam(
     mem_request_port, "`mem_master_port` is now called `mem_request_port`"
 )
 # sequencer里把它和piobus.cpu_side_ports连起来
 def connectIOPorts(self, piobus):
 	self.mem_request_port = piobus.cpu_side_ports

哪里用了def connectIOPorts(self, piobus):呢? 还是ruby.creatysystem

    # Create a port proxy for connecting the system port. This is
    # independent of the protocol and kept in the protocol-agnostic
    # part (i.e. here).
    sys_port_proxy = RubyPortProxy(ruby_system=ruby)
    if piobus is not None:
        sys_port_proxy.pio_request_port = piobus.cpu_side_ports

    # Give the system port proxy a SimObject parent without creating a
    # full-fledged controller
    system.sys_port_proxy = sys_port_proxy

    # Connect the system port for loading of binaries etc
    system.system_port = system.sys_port_proxy.in_ports

    setup_memory_controllers(system, ruby, dir_cntrls, options)

    # Connect the cpu sequencers and the piobus
    if piobus != None:
        for cpu_seq in cpu_sequencers:
            cpu_seq.connectIOPorts(piobus)

src/mem/ruby/protocol/MESI_Two_Level-L1cache.sm

然后我们看一些不是python的代码:src/mem/ruby/protocol/MESI_Two_Level-L1cache.sm 。
SLICC 文件以“.sm”结尾,因为它们是状态机文件。
创建 requestToL1Cache:

   // To this node's L1 cache FROM the network
   // a L2 bank -> this L1
   MessageBuffer * requestToL1Cache, network="From", virtual_network="2",
        vnet_type="request";

   // a L2 bank -> this L1
   MessageBuffer * responseToL1Cache, network="From", virtual_network="1",
        vnet_type="response";

使用 requestToL1Cache: 当前的cpu接到了其他地方传来的request,这个request需要进入当前cpu的l1cache。

  // Request InterChip network - request from this L1 cache to the shared L2
  in_port(requestL1Network_in, RequestMsg, requestToL1Cache, rank = 1) {
    if(requestL1Network_in.isReady(clockEdge())) {
      peek(requestL1Network_in, RequestMsg, block_on="addr") {
        assert(in_msg.Destination.isElement(machineID));

        Entry cache_entry := getCacheEntry(in_msg.addr);
        TBE tbe := TBEs[in_msg.addr];

        if (in_msg.Type == CoherenceRequestType:INV) {
          trigger(Event:Inv, in_msg.addr, cache_entry, tbe);
        } else if (in_msg.Type == CoherenceRequestType:GETX ||
                   in_msg.Type == CoherenceRequestType:UPGRADE) {
          // upgrade transforms to GETX due to race
          trigger(Event:Fwd_GETX, in_msg.addr, cache_entry, tbe);
        } else if (in_msg.Type == CoherenceRequestType:GETS) {
          trigger(Event:Fwd_GETS, in_msg.addr, cache_entry, tbe);
        } else if (in_msg.Type == CoherenceRequestType:GET_INSTR) {
          trigger(Event:Fwd_GET_INSTR, in_msg.addr, cache_entry, tbe);
        } else {
          error("Invalid forwarded request type");
        }
      }
    }
  }

分析代码

这段代码定义了一个输入端口(in_port),称为requestL1Network_in,用于接收来自L1网络的请求消息(RequestMsg)。
消息被发送到名为requestToL1Cache的缓冲区。

消息处理逻辑

当requestL1Network_in端口在当前时钟边沿(clockEdge())准备好接收消息时,代码开始处理接收到的消息。
使用peek函数查看端口上的消息,根据消息中的地址(addr)来阻塞或触发进一步的动作。
消息的目的地(Destination)被检查,确保它是指向当前机器的(machineID)。

根据消息类型触发不同事件

根据接收到的消息类型(in_msg.Type),代码触发不同的事件。事件类型可能包括无效化(Invalidation, INV)、获取写权限(GETX)/升级(UPGRADE)、获取共享读权限(GETS)或获取指令(GET_INSTR)等。
对于每种类型的消息,触发相应的事件,并传递相关参数,如地址(addr)、缓存条目(cache_entry)和事务缓冲条目(TBE)。
在这之前,我们需要知道in_msg来自于哪里:来自peek函数。

peek 函数

// Peek is a special function. Any code inside a peek statement has
            // a special variable declared and populated: in_msg. This contains
            // the message (of type RequestMsg in this case) at the head.
            // "forward_in" is the port we want to peek into
            // "RequestMsg" is the type of message we expect.

注释说明peek是一个特殊的函数。在peek语句内部的代码有一个特殊的变量in_msg被声明并赋值。
in_msg变量
in_msg包含位于输入端口队列头部的消息。在这种情况下,消息的类型是RequestMsg。
通过peek操作,可以查看但不移除端口队列头部的消息。
代码中的peek使用
在peek(requestL1Network_in, RequestMsg, block_on=“addr”) {…}这段代码中,requestL1Network_in是要被peek的端口,RequestMsg是期望的消息类型。
in_msg在peek的作用域内被自动声明,指向requestL1Network_in端口队列头部的RequestMsg类型消息。
in_msg的应用
在peek的代码块中,可以直接使用in_msg访问和处理当前被检视的消息。
例如,可以检查消息的类型(in_msg.Type),地址(in_msg.addr)等属性,并根据这些属性执行不同的操作。

总结peek函数

这段注释说明了peek在SLICC代码中的特殊用法,以及如何利用这个特殊的peek函数和in_msg变量来处理输入端口上的消息。这是实现gem5中复杂缓存一致性协议的关键机制之一,允许开发者以一种高效且声明式的方式来处理协议中的消息。

in_msg处理方式: 无效化(Invalidation, INV)消息

if (in_msg.Type == CoherenceRequestType:INV) {
    trigger(Event:Inv, in_msg.addr, cache_entry, tbe);
}

当接收到无效化请求时,触发一个无效化事件(Event:Inv)。
这种消息通常意味着其他某个缓存控制器或目录控制器要求使得当前缓存中的特定数据块无效。
该事件会使用请求的内存地址(in_msg.addr)、相关的缓存条目(cache_entry)和事务缓冲条目(TBE)作为参数。

in_msg处理方式: 获取写权限(GETX)或升级(UPGRADE)消息

else if (in_msg.Type == CoherenceRequestType:GETX ||
         in_msg.Type == CoherenceRequestType:UPGRADE) {
    trigger(Event:Fwd_GETX, in_msg.addr, cache_entry, tbe);
}

接收到GETX或UPGRADE消息时,触发一个前向写权限获取事件(Event:Fwd_GETX)。
这表示请求者需要写权限。如果是UPGRADE请求,它通常来自已拥有读权限但现在需要写权限的缓存。
事件同样携带内存地址、缓存条目和TBE作为参数

获取共享读权限(GETS)消息

else if (in_msg.Type == CoherenceRequestType:GETS) {
    trigger(Event:Fwd_GETS, in_msg.addr, cache_entry, tbe);
}

接收到GETS消息时,触发一个前向读权限获取事件(Event:Fwd_GETS)。
这表示请求者需要共享的读权限,而不是独占的写权限。
同样使用内存地址、缓存条目和TBE作为参数。

获取指令(GET_INSTR)消息

else if (in_msg.Type == CoherenceRequestType:GET_INSTR) {
    trigger(Event:Fwd_GET_INSTR, in_msg.addr, cache_entry, tbe);
}

接收到GET_INSTR消息时,触发一个前向获取指令事件(Event:Fwd_GET_INSTR)。
这种消息通常用于指令缓存请求,请求者需要读取指令数据。
包含内存地址、缓存条目和TBE作为参数。

Input/output network definitions

    /*************************************************************************/
    // Input/output network definitions

    // Output ports. This defines the message types that will flow ocross the
    // output buffers as defined above. These must be "to" networks.
    // "request_out" is the name we'll use later to send requests.
    // "RequestMsg" is the message type we will send (see MSI-msg.sm)
    // "requestToDir" is the name of the MessageBuffer declared above that
    //      we are sending these requests out of.
    out_port(request_out, RequestMsg, requestToDir);
    out_port(response_out, ResponseMsg, responseToDirOrSibling);

    // Input ports. The order here is/(can be) important. The code in each
    // in_port is executed in the order specified in this file (or by the rank
    // parameter). Thus, we must sort these based on the network priority.
    // In this cache, the order is responses from other caches, forwards, then
    // requests from the CPU.

    // Like the out_port above
    // "response_in" is the name we'll use later when we refer to this port
    // "ResponseMsg" is the type of message we expect on this port
    // "responseFromDirOrSibling" is the name of the buffer this in_port is
    // connected to for responses from other caches and the directory

这段注释是gem5中SLICC代码的一部分,解释了如何在缓存一致性协议中定义输入端口(in_port)和输出端口(out_port)。这些端口用于处理不同类型的消息,并将它们发送到或从缓存的不同网络接收。以下是对这段注释的解读:

输出端口定义
定义输出端口:
out_port(request_out, RequestMsg, requestToDir); 和 out_port(response_out, ResponseMsg, responseToDirOrSibling); 定义了两个输出端口。
这些输出端口用于将消息发送到其他网络组件,如目录控制器或兄弟缓存。
request_out和response_out是在代码中用来引用这些端口的名称。
RequestMsg和ResponseMsg指明通过这些端口发送的消息类型。
requestToDir和responseToDirOrSibling是与这些端口关联的消息缓冲区(MessageBuffer)的名称。
输入端口定义
定义输入端口:
输入端口的定义和输出端口类似,但用于接收消息。
输入端口的顺序可能会影响处理消息的优先级。注释中提到,端口中的代码按照在文件中指定的顺序(或通过rank参数)执行。
例如,in_port(response_in, ResponseMsg, responseFromDirOrSibling);定义了一个输入端口response_in,用于接收类型为ResponseMsg的消息,这些消息来自于其他缓存或目录,并存储在responseFromDirOrSibling消息缓冲区中。

注意顺序

src/learning_gem5/part3/MSI-cache.sm: In this cache, the order is responses from other caches, forwards, then requests
先处理response,然后是forward,然后是requests. 在这之前,是先处理自己的出。
小结就是,先出,然后再进。进的inport是response然后forward然后是requests.
比如这个caches的inports就是

 // "response_in" is the name we'll use later when we refer to this port
 // "ResponseMsg" is the type of message we expect on this port
 // "responseFromDirOrSibling" is the name of the buffer this in_port is
 // connected to for responses from other caches and the directory.
in_port(response_in, ResponseMsg, responseFromDirOrSibling) {
。。。

// Forward requests for other caches.
in_port(forward_in, RequestMsg, forwardFromDir) {
。。。
    
// The "mandatory queue" is the port/queue from the CPU or other processor.
// This is *always* a RubyRequest
in_port(mandatory_in, RubyRequest, mandatoryQueue) {

garnet的NI外部

src/mem/ruby/network/garnet/GarnetNetwork.cc 中初始化garnet

// record the network interfaces
    for (std::vector<ClockedObject*>::const_iterator i = p.netifs.begin();
         i != p.netifs.end(); ++i) {
        NetworkInterface *ni = safe_cast<NetworkInterface *>(*i);
        m_nis.push_back(ni);
        ni->init_net_ptr(this);
    }

消息 注入NI中的 inNode_ptr;

// The Message buffers that takes messages from the protocol
std::vector<MessageBuffer *> inNode_ptr;
// The Message buffers that provides messages to the protocol
std::vector<MessageBuffer *> outNode_ptr; 

ptr 来自于 NI::addNode

void
NetworkInterface::addNode(std::vector<MessageBuffer *>& in,
std::vector<MessageBuffer *>& out)
{
inNode_ptr = in;
outNode_ptr = out;

for (auto& it : in) {
    if (it != nullptr) {
        it->setConsumer(this);
    }
}

}

NI 的addnode在 garnetnetwork中调用:

src/mem/ruby/network/garnet/GarnetNetwork.cc:

GarnetNetwork::init()//yz:只初始化一次
 for (int i=0; i < m_nodes; i++) {
        m_nis[i]->addNode(m_toNetQueues[i], m_fromNetQueues[i]);
    }

m_toNetQueues 来自于ruby network

src/mem/ruby/network/garnet/GarnetNetwork.cc: 引用了 #include “mem/ruby/network/Network.hh”
然后我们看 对应的 src/mem/ruby/network/Network.hh
定义了类型:是一串[一串messagebuffer]

// vector of queues from the components
std::vector<std::vector<MessageBuffer*> > m_toNetQueues;

src/mem/ruby/network/Network.cc中指定了这一串输入的message:

void
Network::setToNetQueue(NodeID global_id, bool ordered, int network_num,
                                std::string vnet_type, MessageBuffer *b)
{
   NodeID local_id = getLocalNodeID(global_id);
   checkNetworkAllocation(local_id, ordered, network_num, vnet_type);

   while (m_toNetQueues[local_id].size() <= network_num) {
       m_toNetQueues[local_id].push_back(nullptr);
   }
   m_toNetQueues[local_id][network_num] = b;
}

setToNetQueue 在哪里调用的呢?

换个思路找:
结果中system.ruby.l2_cntrl0.L2cache.m_demand_accesses 来自CacheMemory: ADD_STAT(m_demand_accesses, “Number of cache demand accesses”,
m_demand_hits + m_demand_misses),
CacheMemory来自#include “mem/ruby/structures/CacheMemory.hh”
一共也只有4个文件用了这个头文件,除了它自己的cc,我们主要发现了sequencer 是用过的,但是印象里我们只在那里连了l1.

在这里插入图片描述
setToNetQueue 只有void 初始化没有调用。在哪里调用呢?
在这里插入图片描述

NI 内部

NI中的wakeup处理message并且生成flits

src/mem/ruby/network/garnet/NetworkInterface.cc
###wakeup():

  1. 会遍历检查 inNode_ptr 中 MessageBuffer的所有VNET,是否有消息准备就绪(b->isReady(curTime))。
  2. 消息转换(flit化):如果协议缓冲区有准备就绪的消息,flitisizeMessage函数被调用,将该消息转换为一个或多个flits。flitisizeMessage根据消息大小和网络配置确定需要多少flits来表示这个消息,并将它们放入NI的输出队列。
  3. 从协议缓冲区中移除消息: 一旦消息被成功转换为flits并加入到输出队列,该消息从协议缓冲区中出队(b->dequeue(curTime))。
void NetworkInterface::wakeup()
	 for (int vnet = 0; vnet < inNode_ptr.size(); ++vnet) {
        MessageBuffer *b = inNode_ptr[vnet];
        if (b == nullptr) {
            continue;
        }

        if (b->isReady(curTime)) { // Is there a message waiting
            msg_ptr = b->peekMsgPtr();
            if (flitisizeMessage(msg_ptr, vnet)) {
                b->dequeue(curTime);
            }
        }
    }

NI中的 生成flits细节

核心是瞬间虚空完成,不管有很多个flit还是一个flit。

// Embed the protocol message into flits
bool
NetworkInterface::flitisizeMessage(MsgPtr msg_ptr, int vnet)
{
    Message *net_msg_ptr = msg_ptr.get();
    NetDest net_msg_dest = net_msg_ptr->getDestination();

    // gets all the destinations associated with this message.
    std::vector<NodeID> dest_nodes = net_msg_dest.getAllDest();

    // Number of flits is dependent on the link bandwidth available.
    // This is expressed in terms of bytes/cycle or the flit size
    OutputPort *oPort = getOutportForVnet(vnet);
    assert(oPort);
    int num_flits = (int)divCeil((float) m_net_ptr->MessageSizeType_to_int(
        net_msg_ptr->getMessageSize()), (float)oPort->bitWidth());

    DPRINTF(RubyNetwork, "Message Size:%d vnet:%d bitWidth:%d\n",
        m_net_ptr->MessageSizeType_to_int(net_msg_ptr->getMessageSize()),
        vnet, oPort->bitWidth());

    // loop to convert all multicast messages into unicast messages
    for (int ctr = 0; ctr < dest_nodes.size(); ctr++) {

        // this will return a free output virtual channel
        int vc = calculateVC(vnet);

        if (vc == -1) {
            return false ;
        }
        MsgPtr new_msg_ptr = msg_ptr->clone();
        NodeID destID = dest_nodes[ctr];

        Message *new_net_msg_ptr = new_msg_ptr.get();
        if (dest_nodes.size() > 1) {
            NetDest personal_dest;
            for (int m = 0; m < (int) MachineType_NUM; m++) {
                if ((destID >= MachineType_base_number((MachineType) m)) &&
                    destID < MachineType_base_number((MachineType) (m+1))) {
                    // calculating the NetDest associated with this destID
                    personal_dest.clear();
                    personal_dest.add((MachineID) {(MachineType) m, (destID -
                        MachineType_base_number((MachineType) m))});
                    new_net_msg_ptr->getDestination() = personal_dest;
                    break;
                }
            }
            net_msg_dest.removeNetDest(personal_dest);
            // removing the destination from the original message to reflect
            // that a message with this particular destination has been
            // flitisized and an output vc is acquired
            net_msg_ptr->getDestination().removeNetDest(personal_dest);
        }

        // Embed Route into the flits
        // NetDest format is used by the routing table
        // Custom routing algorithms just need destID

        RouteInfo route;
        route.vnet = vnet;
        route.net_dest = new_net_msg_ptr->getDestination();
        route.src_ni = m_id;
        route.src_router = oPort->routerID();
        route.dest_ni = destID;
        route.dest_router = m_net_ptr->get_router_id(destID, vnet);

        // initialize hops_traversed to -1
        // so that the first router increments it to 0
        route.hops_traversed = -1;

        m_net_ptr->increment_injected_packets(vnet);
        m_net_ptr->update_traffic_distribution(route);
        int packet_id = m_net_ptr->getNextPacketID();
        for (int i = 0; i < num_flits; i++) {
            m_net_ptr->increment_injected_flits(vnet);
            flit *fl = new flit(packet_id,
                i, vc, vnet, route, num_flits, new_msg_ptr,
                m_net_ptr->MessageSizeType_to_int(
                net_msg_ptr->getMessageSize()),
                oPort->bitWidth(), curTick());

            fl->set_src_delay(curTick() - msg_ptr->getTime());
            niOutVcs[vc].insert(fl);
        }

        m_ni_out_vcs_enqueue_time[vc] = curTick();
        outVcState[vc].setState(ACTIVE_, curTick());
    }
    return true ;
}

NI中的 flit离开NI进入router: scheduleFlit

getOutportForVnet确定与flit的虚拟网络(vnet)对应的输出端口(OutputPort)。
插入flit到发送队列: 然后,flit被插入到该输出端口的flit发送队列(outFlitQueue)中。
安排网络链路发送: 最后,安排与输出端口关联的网络链路(NetworkLink)在下一个时钟周期发送这些flit。这是通过调用该链路的scheduleEventAbsolute方法实现的,确保在下一个时钟边沿发送flit。

NI 中的outport

wakeup插入了一个flit。 oPort是NI定义的一个类,其中outFlitQueue是一个FlitQueue。

oPort->outFlitQueue()->insert(t_flit);
  flitBuffer *
          outFlitQueue()
          {
              return _outFlitQueue;
          }

#include “mem/ruby/slicc_interface/Message.hh”
#include “mem/ruby/common/Consumer.hh”

garnet read me work flow

Garnet Network Parameters and Setup:

  • GarnetNetwork.py
    • defaults can be overwritten from command line (see configs/network/Network.py)
  • GarnetNetwork.hh/cc
    • sets up the routers and links
    • collects stats

CODE FLOW

  • NetworkInterface.cc::wakeup()

    • Every NI connected to one coherence protocol controller on one end, and one router on the other.
    • receives messages from coherence protocol buffer in appropriate vnet and converts them into network packets and sends them into the network.
      • garnet adds the ability to capture a network trace at this point.
    • receives flits from the network, extracts the protocol message and sends it to the coherence protocol buffer in appropriate vnet.
    • manages flow-control (i.e., credits) with its attached router.
    • The consuming flit/credit output link of the NI is put in the global event queue with a timestamp set to next cycle.
      The eventqueue calls the wakeup function in the consumer.
  • NetworkLink.cc::wakeup()

    • receives flits from NI/router and sends it to NI/router after m_latency cycles delay
      • Default latency value for every link can be set from command line (see configs/network/Network.py)
      • Per link latency can be overwritten in the topology file
    • The consumer of the link (NI/router) is put in the global event queue with a timestamp set after m_latency cycles.
      The eventqueue calls the wakeup function in the consumer.
  • Router.cc::wakeup()

    • Loop through all InputUnits and call their wakeup()
    • Loop through all OutputUnits and call their wakeup()
    • Call SwitchAllocator’s wakeup()
    • Call CrossbarSwitch’s wakeup()
    • The router’s wakeup function is called whenever any of its modules (InputUnit, OutputUnit, SwitchAllocator, CrossbarSwitch) have
      a ready flit/credit to act upon this cycle.
  • InputUnit.cc::wakeup()

    • Read input flit from upstream router if it is ready for this cycle
    • For HEAD/HEAD_TAIL flits, perform route computation, and update route in the VC.
    • Buffer the flit for (m_latency - 1) cycles and mark it valid for SwitchAllocation starting that cycle.
      • Default latency for every router can be set from command line (see configs/network/Network.py)
      • Per router latency (i.e., num pipeline stages) can be set in the topology file
  • OutputUnit.cc::wakeup()

    • Read input credit from downstream router if it is ready for this cycle
    • Increment the credit in the appropriate output VC state.
    • Mark output VC as free if the credit carries is_free_signal as true
  • SwitchAllocator.cc::wakeup()

    • Note: SwitchAllocator performs VC arbitration and selection within it.
    • SA-I (or SA-i): Loop through all input VCs at every input port, and select one in a round robin manner.
      • For HEAD/HEAD_TAIL flits only select an input VC whose output port has at least one free output VC.
      • For BODY/TAIL flits, only select an input VC that has credits in its output VC.
    • Place a request for the output port from this VC.
    • SA-II (or SA-o): Loop through all output ports, and select one input VC (that placed a request during SA-I) as the winner for this output port in a round robin manner.
      • For HEAD/HEAD_TAIL flits, perform outvc allocation (i.e., select a free VC from the output port).
      • For BODY/TAIL flits, decrement a credit in the output vc.
    • Read the flit out from the input VC, and send it to the CrossbarSwitch
    • Send a increment_credit signal to the upstream router for this input VC.
      • for HEAD_TAIL/TAIL flits, mark is_free_signal as true in the credit.
      • The input unit sends the credit out on the credit link to the upstream router.
    • Reschedule the Router to wakeup next cycle for any flits ready for SA next cycle.
  • CrossbarSwitch.cc::wakeup()

    • Loop through all input ports, and send the winning flit out of its output port onto the output link.
    • The consuming flit output link of the router is put in the global event queue with a timestamp set to next cycle.
      The eventqueue calls the wakeup function in the consumer.

If a clock domain crossing(CDC) or Serializer-Deserializer unit is
instantiated, then the Network Brisge takes over the flit in HeteroGarnet.

  • NetworkBridge::wakeup()
    • Check if SerDes is enabled and do appropriate calculations for
      serializing or deserializing the flits
    • Check if CDC is enabled and schedule all the flits according
      to the consumers clock domain.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1326249.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32单片机项目实例:基于TouchGFX的智能手表设计(7)MVP架构下的交互逻辑设计

STM32单片机项目实例&#xff1a;基于TouchGFX的智能手表设计&#xff08;7&#xff09;MVP架构下的交互逻辑设计 目录 一、概述 二、MVP架构下的交互逻辑 一、概述 本文例程是基于 TouchGFX 的智能手表设计—Designer 软件 UI 设计的例程 0B-2_STM32U575_MVP_Interactive工…

【点选验证码识别】某招标网站反爬虫分析与验证码自动识别

文章目录 1. 写在前面2. 风控描述3. 验证码裁剪4. 验证码识别 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣…

Redis基础篇-002 初识Redis

1、认识NoSQL 1.1 概念 NoSQL是一个非关系型数据库。 常见的NoSQL有&#xff1a;Redis、MongoDB 1.2 NoSQL与SQL的区别 类别SQLNoSQL数据结构结构化非结构化数据关联关联非关联查询方式SQL非SQL事务特性ACIDBASE存储方式磁盘内存扩展性垂直水平使用场景1&#xff09;数据结…

Uniapp + Vue3 封装请求工具挂载全局

新建request.js工具类 const http {// baseUrl 地址baseUrl: http://localhost:8080,// 请求方法request(config) {// config&#xff1a;请求配置对象&#xff0c;具体参照uniapp文档config beforeRequest(config)// 请求地址拼接config.url this.baseUrl config.url// 异…

WIN10安装gurobi给matlab使用(记录)

https://www.gurobi.com/downloads/gurobi-software/ 这是以前的matlab路径&#xff0c;需要修改成新的 使用校园网激活一下 运行gurobi_setup Google报错信息&#xff0c;发现mathwork官方的解释是&#xff1a;找不到相关的dll文件&#xff0c;所以mex无效。 解决方案&…

Unity | Shader基础知识(第八集:案例<漫反射材质球>)

目录 一、本节介绍 1 上集回顾 2 本节介绍 二、什么是漫反射材质球 三、 漫反射进化史 1 三种算法结果的区别 2 具体算法 2.1 兰伯特逐顶点算法 a.本小节使用的unity自带结构体。 b.兰伯特逐顶点算法公式 c.代码实现——兰伯特逐顶点算法 2.2 代码实现——兰伯特逐…

Windows下结束进程

可以用命令 netstat -ano | findstr :8088 直接找到端口号为8088的进程PID&#xff1b; 根据PID进程号结束进程 &#xff1a;taskkill /pid 14696 -t -f ;

研发提效必备技能:手把手教你基于Docker搭建Maven私服仓库

沉淀&#xff0c;成长&#xff0c;突破&#xff0c;帮助他人&#xff0c;成就自我。 大家好&#xff0c;我是冰河~~ 在研发的过程中&#xff0c;很多企业都会针对自身业务特点来定制研发一些工具类库&#xff0c;但是这些工具类库又不会对外公开&#xff0c;那如何在组织内部共…

如何使用支付宝的沙箱环境在本地配置模拟支付并发布至公网测试

文章目录 前言1. 下载当面付demo2. 修改配置文件3. 打包成web服务4. 局域网测试5. 内网穿透6. 测试公网访问7. 配置二级子域名8. 测试使用固定二级子域名访问 前言 在沙箱环境调试支付SDK的时候&#xff0c;往往沙箱环境部署在本地&#xff0c;局限性大&#xff0c;在沙箱环境…

oracle23安装并 配置内网代理(安装nginx oracle23)

安装nginx步奏如下 1.安装所需依赖 yum install -y gcc gcc-c pcre pcre-devel zlib zlib-devel openssl openssl-devel automake autoconf libtool make2. 解压nginx压缩包&#xff0c;进入解压后的nginx目录 tar -xvf ./nginx-1.24.0.tar.gzcd ./nginx3. 以下编译命令中增…

深度学习或机器学习的模型部署相关的初步了解及分析

机器学习-深度学习 部署相关资料文档 这是上班之后的第一个文档&#xff0c;由于项目原因需要去了解一些和模型部署相关的知识&#xff0c;所以就简单了解了一下相应的部署引擎和框架&#xff0c;也是刚刚开始学习&#xff0c;如果有什么写的不对的欢迎大家交流&#xff0c;看…

2024年3月电子学会青少年编程等级考试时间安排

1考试方式 1. 在线居家考试&#xff08;全国&#xff09;&#xff1b; 2. 对于符合线下考试要求的考试服务网点&#xff0c;经地方实地调研报学会总部批准后&#xff0c;可组织线下考试。 2报名时间 报名时间&#xff1a;2023年12月21日-2024年3月12日16:00&#xff1b; 考…

Web API ------- requestAnimationFrame

官方文档 requestAnimationFrame 是什么 window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画&#xff0c;并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数&#xff0c;该回调函数会在浏览器下一次重绘之前执行 …

HTML输出特殊字符详细方法

以下是部分特殊字符代码表&#xff0c;它们的完整应用代码格式为&#xff1a;&#;用下面的四位数字替换&#xff0c;将得到对应的符号。&#xff08;注意&#xff1a;应用这些代码&#xff0c;编辑器应该切换到HTML模式&#xff09; ☏260f ☎260e ☺263a ☻263b ☼263c ☽…

【UML】第6篇 用例图(1/3)

目录 一、什么是用例图 二、参与者 2.1 什么是参与者 2.2 如何识别参与者 2.3 参与者之间的关系 从今天开始&#xff0c;就到了最干的各种的图的梳理和学习了&#xff0c;未来AI就能编码了&#xff0c;把业务建模和设计的基本功打好&#xff0c;也许能和AI和平相处呢。 一…

第三节TypeScript 基础类型

1、typescript的基础类型 如下表&#xff1a; 数据类型 关键字 描述 任意类型 any 生命any的变量可以赋值任意类型的值 数字类型 number 整数或分数 字符串类型 string 使用单引号&#xff08;‘’&#xff09;或者双引号&#xff08;“”&#xff09;来表示字符串…

nodejs微信小程序+python+PHP购物商城网站-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

3D模型格式转换工具如何实现高性能数据转换?请看CAE系统开发实例!

​ 客户背景 DP Technology是全球知名的CAM的供应商&#xff0c;在全球8个国家设有18个办事处。DP Technology提供的CAMESPRIT系统是一个用于数控编程&#xff0c;优化和仿真全方面的CAM系统。CAMESPRIT的客户来自多个行业&#xff0c;因此支持多种CAD工具和文件格式显得格外重…

区块链技术与应用 【全国职业院校技能大赛国赛题目解析】第一套区块链系统部署与运维

第一套区块链系统部署与运维题目 环境 : ubuntu20 fisco : 2.8.0 子任务1-2-1: 搭建区块链系统并验证 题意: 要求搭建一条四节点的区块链系统,我们选择使用fisco作为此次测试的链子 我们使用build_chain.sh进行构建单机四节点,并且使用官方的默认端口【正式比赛大概率不…

java使用面向对象实现图书管理系统

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …