GEM5教程--修改和拓展gem5(四)

六、在内存系统中创建SimObjects

在这个部分中,我们将创建一个位于CPU和内存总线之间的简单内存对象。在下面的部分中中,我们将使用这个简单的内存对象,并为其添加一些逻辑,使其成为一个非常简单的阻塞单处理器缓存。

1、gem5主、从端口

在深入研究内存对象的实现之前,我们应该首先了解gem5的主端口和从端口接口。正如前面在 创建简单配置脚本 中所讨论的,所有内存对象都通过端口连接在一起。这些端口在这些内存对象之间提供了一个刚性接口。

这些端口实现三种不同的存储系统模式:定时、原子和功能。最重要的模式是定时模式。定时模式是产生正确模拟结果的唯一模式。其他模式仅在特殊情况下使用。

原子模式有助于将模拟快速转发到感兴趣的区域并预热模拟器。此模式假定内存系统中不会生成任何事件。相反,所有的内存请求都通过一个长的调用链执行。不需要为内存对象实现原子访问,除非它将在快速转发期间或模拟器预热期间使用。

功能模式更好地描述为调试模式。功能模式用于将数据从主机读取到模拟器内存中。它在系统调用模拟模式中使用得很频繁。例如,函数模式用于将process.cmd中的二进制文件从主机加载到模拟系统的内存中,以便模拟系统可以访问它。无论数据在哪里,功能访问都应在读时返回最新的数据,并应在写时更新所有可能的有效数据(例如,在具有缓存的系统中,可能有多个具有相同地址的有效缓存块)。

2、数据包

在gem5中,数据包通过端口发送。包由MemReq组成,MemReq是内存请求对象。MemReq保存初始化包的原始请求的信息,例如请求者、地址和请求类型(读、写等)。

数据包还有一个MemCmd,它是数据包的当前命令。此命令可以在数据包的整个生命周期中改变(例如,一旦满足内存命令,请求就变成响应)。最常见的MemCmd是ReadReq(读请求)、ReadResp(读响应)、WriteReq(写请求)、WriteResp(写响应)。还有缓存和许多其他命令类型的写回请求(WritebackDirty、WritebackClean)

数据包也可以保留请求的数据,或指向数据的指针。创建数据包时,可以选择数据是动态的(显式分配和释放)还是静态的(由数据包对象分配和释放)。

最后,在经典缓存中使用包作为跟踪一致性的单元。因此,许多包代码都是针对经典的缓存一致性协议的。然而,包用于gem5中的所有内存对象之间的通信,即使它们不直接涉及一致性(例如DRAM控制器和CPU模型)。

所有端口接口函数都接受数据包指针作为参数。由于这个指针很常见,gem5包含一个typedef:PacketPtr。

3、端口接口

gem5中有两种端口:主端口和从端口。无论何时实现内存对象,都将至少实现其中一种类型的端口。为此,您将创建一个新类,该类分别从主端口或从端口的SlavePort继承主端口和从端口。主端口发送请求(和接收响应),从端口接收请求(和发送响应)。

(1)简单的主从交互,当两者都能接受请求和响应时。 概述主端口和从端口之间最简单的交互。此图显示计时模式下的交互`。其他模式要简单得多,在主模式和从模式之间使用一个简单的调用链。

交互

如上所述,所有端口接口都需要一个PacketPtr作为参数。这些函数(sendTimingReq、recvTimingReq等)都接受一个参数PacketPtr。此数据包是发送或接收的请求或响应。

要发送请求包,主机调用sendTimingReq。反过来,(在同一个调用链中),函数recvTimingReq被调用到具有相同PacketPtr作为其唯一参数的从机上。

recvTimingReq的返回类型为bool。这个布尔返回值直接返回给调用主控。返回值true表示数据包已被从属服务器接受。另一方面,返回值false意味着从机无法接受请求,必须在将来某个时间重试该请求。

在简单的主从交互中,当两者都可以接受请求和响应时,首先,主机通过调用sendTimingReq来发送定时请求,而sendTimingReq又调用recvtimingreps。从机,从recvtimingreps返回true,这是从对sendTimingReq的调用返回的。主服务器继续执行,从服务器执行完成请求所需的任何操作(例如,如果它是一个缓存,它会查找标记以查看是否与请求中的地址匹配)。

一旦从机完成请求,它就可以向主机发送响应。从机使用响应包调用sendTimingResp(这应该是与请求相同的PacketPtr,但现在应该是响应包)。然后,调用主函数recvTimingResp。主机的recvTimingResp函数返回true,这是从机中sendTimingResp的返回值。因此,该请求的交互已完成。

在后面的简单内存对象示例中,我们将展示这些函数的示例代码。

(2)当主服务器或从服务器接收到请求或响应时,它们可能正忙。从机忙时的简单主从交互显示了原始请求发送时从机忙的情况。

交互

在这种情况下,从机从recvTimingReq函数返回false。当主机在调用sendTimingReq后收到false时,它必须等待它的函数recvReqRetry被执行。只有在调用此函数时,才允许主机重试调用sendTimingRequest。上图显示计时请求失败一次,但可能失败任意次数。注意:由主机来跟踪失败的包,而不是从机。从机不保留指向失败数据包的指针。

(3)类似地,当主服务器忙时的简单主从交互显示了当主服务器忙时,从服务器试图发送响应的情况。在这种情况下,从机在收到recvresprety之前不能调用sendTimingResp。

交互

重要的是,在这两种情况下,重试代码路径可以是单个调用堆栈。例如,当主机调用sendRespRetry时,也可以在同一个调用堆栈中调用recvTimingReq。因此,很容易错误地创建无限递归错误或其他错误。重要的是,在内存对象发送重试之前,它在该时刻准备好接受另一个数据包。

4、简单内存对象示例

在这个部分中,我们将构建一个简单的内存对象。最初,它只是将请求从CPU端(一个简单的CPU)传递到内存端(一个简单的内存总线)。请参见下面的图,它有一个位于CPU和内存总线之间的简单内存对象。它将有一个主端口,用于向内存总线发送请求,以及两个cpu侧端口,用于cpu的指令和数据缓存端口。在后面的部分中,我们将添加使此对象成为缓存的逻辑。

结构示意图

5、声明SimObject

就像我们在创建非常简单的SimObject时创建SimObject一样,第一步是创建SimObject Python文件。我们将这个简单内存对象称为simple memobj,并在src/learning-gem5/simple-memobj中创建SimObject Python文件。

from m5.params import *
from m5.proxy import *
from MemObject import MemObject

class SimpleMemobj(MemObject):
    type = 'SimpleMemobj'
    cxx_header = "learning_gem5/simple_memobj/simple_memobj.hh"

    inst_port = SlavePort("CPU side port, receives requests")
    data_port = SlavePort("CPU side port, receives requests")
    mem_side = MasterPort("Memory side port, sends requests")

对于这个对象,我们从MemObject继承,而不是SimObject,因为我们正在创建一个与内存系统交互的对象。MemObject类有两个纯虚函数,我们将在C++实现、getMasterPortgetSlavePort中定义它们。

这个对象的参数是三个端口。两个CPU端口用于连接指令和数据端口,一个端口用于连接内存总线。这些端口没有默认值,它们有一个简单的描述。 记住这些端口的名称很重要。在实现SimpleMemobj和定义getMasterPort和getSlavePort函数时,我们将显式地使用这些名称。

6、定义SimpleMemobj类

现在,我们为simplemmobj创建一个头文件。

class SimpleMemobj : public MemObject
{
  private:

  public:

    /** constructor
     */
    SimpleMemobj(SimpleMemobjParams *params);
};


 
7、定义从端口类型

现在,我们需要为两种端口定义类:CPU端和内存端端口。为此,我们将在simplemmobj类中声明这些类,因为没有其他对象将使用这些类。

让我们从从端口开始,或者从CPU端端口开始。我们将从SlavePort类继承。下面是重写SlavePort类中所有纯虚拟函数所需的代码。

class CPUSidePort : public SlavePort
{
  private:
    SimpleMemobj *owner;

  public:
    CPUSidePort(const std::string& name, SimpleMemobj *owner) :
        SlavePort(name, owner), owner(owner)
    { }

    AddrRangeList getAddrRanges() const override;

  protected:
    Tick recvAtomic(PacketPtr pkt) override { panic("recvAtomic unimpl."); }
    void recvFunctional(PacketPtr pkt) override;
    bool recvTimingReq(PacketPtr pkt) override;
    void recvRespRetry() override;
};

此对象需要定义五个函数。:

(1)AddrRangeList getAddrRanges():此函数返回所有者负责的非重叠地址范围的列表。所有从端口都必须重写此函数并返回包含至少一个项的填充列表。crossbar对象使用它来知道向哪个端口发送请求。大多数内存对象要么返回所有内存,要么返回其对等端响应的任何地址范围。

(2)Tick recvAtomic(PacketPtr pkt):这是每当CPU试图进行原子内存访问时调用的函数。我们暂时不打算实现这个功能。相反,如果调用此函数,我们将“惊慌失措”。panic退出模拟并打印出消息。

(3)void recvFunctional(PacketPtr pkt):当CPU进行功能访问时调用。如上所述,这在syscall仿真模式中用于从主机文件系统加载文件。

(4)bool recvTimingReq(PacketPtr pkt):此端口的对等方调用sendTimingReq时调用此函数。它接受单个参数,该参数是请求的数据包指针。如果数据包被接受,则此函数返回true。如果此函数返回false,则在将来的某个时间点,此对象必须调用sendReqRetry,以便通知对等端口它能够接受被拒绝的请求。

(5)void recvRespRetry():对等端口调用sendRespRetry时调用此函数。执行此函数时,此端口应再次调用sendTimingResp以重试将响应发送到其对等主端口。

这个对象还有一个成员变量,它的所有者,所以它可以调用该对象上的函数。

8、定义主端口类型

接下来,我们需要定义一个主端口类型。这将是内存侧端口,它将请求从CPU端转发到内存系统的其余部分。

 class MemSidePort : public MasterPort
{
  private:
    SimpleMemobj *owner;

  public:
    MemSidePort(const std::string& name, SimpleMemobj *owner) :
        MasterPort(name, owner), owner(owner)
    { }

  protected:
    bool recvTimingResp(PacketPtr pkt) override;
    void recvReqRetry() override;
    void recvRangeChange() override;
};

这个类只有三个我们必须重写的纯虚拟函数。

(1)bool recvTimingResp(PacketPtr pkt):当此端口的从属对等方调用sendTimingResp时调用此函数。如果此对象可以接受响应,则此函数返回true。否则,在将来的某个时候,此对象必须调用sendRespRetry来通知其对等方它现在能够接收响应。

(2)void recvReqRetry():当对等端口调用sendReqRetry时调用此函数,表示此对象应尝试重新发送以前失败的数据包。

(3)void recvRangeChange():与上面的sendRangeChange类似,每当对等端口希望通知此对象它接受的地址范围正在更改时,都会调用此函数。此函数通常只在内存系统初始化时调用,而不是在模拟执行时调用。

9、定义MemObject接口

现在我们已经定义了这两种新类型CPUSidePort和MemSidePort,我们可以将这三个端口声明为SimpleMemobj的一部分。我们还需要声明MemObject类中的两个纯虚拟函数getMasterPort和getSlavePort。在初始化阶段,gem5使用这两个函数通过端口将内存对象连接在一起。

class SimpleMemobj : public MemObject
{
  private:

    <CPUSidePort declaration>
    <MemSidePort declaration>

    CPUSidePort instPort;
    CPUSidePort dataPort;

    MemSidePort memPort;

  public:
    SimpleMemobj(SimpleMemobjParams *params);

    BaseMasterPort& getMasterPort(const std::string& if_name,
                                  PortID idx = InvalidPortID) override;

    BaseSlavePort& getSlavePort(const std::string& if_name,
                                PortID idx = InvalidPortID) override;

};
10、实现基本的MemObject函数

(1)对于SimpleMemobj的构造函数,我们只需调用MemObject构造函数。我们还需要初始化所有端口。每个端口的构造函数都有两个参数:名称和指向其所有者的指针,正如我们在头文件中定义的那样。名称可以是任何字符串,但根据约定,它与Python SimObject文件中的名称相同。

SimpleMemobj::SimpleMemobj(SimpleMemobjParams *params) :
    MemObject(params),
    instPort(params->name + ".inst_port", this),
    dataPort(params->name + ".data_port", this),
    memPort(params->name + ".mem_side", this)
{
}

(2)接下来,我们需要实现接口来获取端口。这个接口由两个函数getMasterPort和getSlavePort组成。这些函数有两个参数。if_name是此对象接口的Python变量名。在主端口的情况下,它将是mem_side,因为这是我们在Python SimObject文件中声明的主端口。

BaseMasterPort &getMasterPort(const std::string &if_name, PortID idx):尝试将从端口连接到此对象时调用此函数。if_name是此对象接口的Python变量名。idx是使用矢量端口时的端口号,默认情况下无效。此函数返回对主端口对象的引用。

BaseSlavePort &getSlavePort(const std::string &if_name, PortID idx):尝试将主端口连接到此对象时调用此函数。if_name是此对象接口的Python变量名。idx是使用矢量端口时的端口号,默认情况下无效。此函数返回对从端口对象的引用。

为了实现getMasterPort,我们比较if_name并检查它是否是Python SimObject文件中指定的mem_side。如果是,则返回memPort对象。如果没有,则将请求名称传递给父级。但是,如果我们尝试将一个从端口连接到任何其他命名端口,这将是一个错误,因为父类没有定义端口。

BaseMasterPort&
SimpleMemobj::getMasterPort(const std::string& if_name, PortID idx)
{
    if (if_name == "mem_side") {
        return memPort;
    } else {
        return MemObject::getMasterPort(if_name, idx);
    }
}

为了实现getSlavePort,我们同样检查if_name是否与我们在Python SimObject文件中为从端口定义的任何一个名称匹配。如果名称是“inst_port”,则返回instPort;如果名称是data_port,则返回data port。

BaseSlavePort&
SimpleMemobj::getSlavePort(const std::string& if_name, PortID idx)
{
    if (if_name == "inst_port") {
        return instPort;
    } else if (if_name == "data_port") {
        return dataPort;
    } else {
        return MemObject::getSlavePort(if_name, idx);
    }
}
11、实现从端口和主端口功能

从端口和主端口的实现都相对简单。在大多数情况下,每个端口函数只是将信息转发给主内存对象(SimpleMemobj)。

(1)从两个简单的函数开始,getAddrRanges和recvFunctional只需调用SimpleMemobj。

AddrRangeList
SimpleMemobj::CPUSidePort::getAddrRanges() const
{
    return owner->getAddrRanges();
}

void
SimpleMemobj::CPUSidePort::recvFunctional(PacketPtr pkt)
{
    return owner->handleFunctional(pkt);
}

(2)这些函数在simplemmobj中的实现同样简单。这些实现只是将请求传递到内存端。我们可以在这里使用DPRINTF调用来跟踪出于调试目的发生的事情。

void
SimpleMemobj::handleFunctional(PacketPtr pkt)
{
    memPort.sendFunctional(pkt);
}

AddrRangeList
SimpleMemobj::getAddrRanges() const
{
    DPRINTF(SimpleMemobj, "Sending new ranges\n");
    return memPort.getAddrRanges();
}

(3)同样对于MemSidePort,我们需要实现recvRangeChange并通过SimpleMemobj将请求转发到从端口。

void
SimpleMemobj::MemSidePort::recvRangeChange()
{
    owner->sendRangeChange();
}
void
SimpleMemobj::sendRangeChange()
{
    instPort.sendRangeChange();
    dataPort.sendRangeChange();
}
12、实现接收请求

(1)recvTimingReq的实现稍微复杂一些。我们需要检查 SimpleMemobj是否能够接受请求。 SimpleMemobj是一个非常简单的阻塞结构;我们一次只允许一个未完成的请求。因此,如果我们在另一个请求未完成时收到请求, SimpleMemobj将阻止第二个请求。

为了简化实现,CPUSidePort存储端口接口的所有流控制信息。因此,我们需要在CPUSidePort中添加一个额外的成员变量needRetry,这个布尔值存储当SimpleMemobj空闲时是否需要发送重试。然后,如果SimpleMemobj在请求时被阻止,我们设置在将来某个时候需要发送一个重试。

bool
SimpleMemobj::CPUSidePort::recvTimingReq(PacketPtr pkt)
{
    if (!owner->handleRequest(pkt)) {
        needRetry = true;
        return false;
    } else {
        return true;
    }
}

(2)为了处理对SimpleMemobj的请求,我们首先检查SimpleMemobj是否已经被阻止,等待对另一个请求的响应。如果它被阻塞了,那么我们返回false来向主叫端口发送我们现在不能接受请求的信号。否则,我们将端口标记为阻塞,并将数据包从内存端口发送出去。为此,我们可以在MemSidePort对象中定义一个helper函数,以对SimpleMemobj实现隐藏流控件。我们假设memPort处理所有的流控制,并且总是从handleRequest返回true,因为我们成功地使用了请求。

bool
SimpleMemobj::handleRequest(PacketPtr pkt)
{
    if (blocked) {
        return false;
    }
    DPRINTF(SimpleMemobj, "Got request for addr %#x\n", pkt->getAddr());
    blocked = true;
    memPort.sendPacket(pkt);
    return true;
}

(3)接下来,我们需要在MemSidePort中实现sendPacket函数。此函数将处理流控制,以防其对等从端口无法接受请求。为此,我们需要在MemSidePort中添加一个成员来存储数据包,以防它被阻塞。如果接收者无法接收请求(或响应),发送者有责任存储数据包。

这个函数只是通过调用sendTimingReq函数来发送数据包。如果发送失败,则此对象将数据包存储在blockedPacket成员函数中,以便稍后(在接收到recvReqRetry时)发送数据包。此函数还包含一些防御代码,以确保不存在错误,并且我们从不试图错误地覆盖blockeddepacket变量。

void
SimpleMemobj::MemSidePort::sendPacket(PacketPtr pkt)
{
    panic_if(blockedPacket != nullptr, "Should never try to send if blocked!");
    if (!sendTimingReq(pkt)) {
        blockedPacket = pkt;
    }
}

(4)接下来,我们需要实现代码来重新发送数据包。在这个函数中,我们试图通过调用上面编写的sendPacket函数来重新发送数据包。

 void
SimpleMemobj::MemSidePort::recvReqRetry()
{
    assert(blockedPacket != nullptr);

    PacketPtr pkt = blockedPacket;
    blockedPacket = nullptr;

    sendPacket(pkt);
}
13、执行接收响应

(1)响应码路径类似于接收码路径。当MemSidePort得到响应时,我们通过SimpleMemobj将响应转发到相应的 CPUSidePort

bool
SimpleMemobj::MemSidePort::recvTimingResp(PacketPtr pkt)
{
    return owner->handleResponse(pkt);
}

(2)在SimpleMemobj中,首先,当我们收到响应时,它应该总是被阻塞,因为对象是阻塞的。在将数据包发送回CPU端之前,我们需要标记该对象不再被阻止。这必须在调用“sendTimingResp”之前完成。否则,可能会陷入无限循环,因为主端口在接收响应和发送另一个请求之间可能只有一个调用链。

在解除SimpleMemobj的阻塞后,我们检查数据包是否是指令或数据包,并通过适当的端口将其发回。最后,由于对象现在已解除阻止,我们可能需要通知CPU端端口,它们现在可以重试失败的请求。

bool
SimpleMemobj::handleResponse(PacketPtr pkt)
{
    assert(blocked);
    DPRINTF(SimpleMemobj, "Got response for addr %#x\n", pkt->getAddr());

    blocked = false;

    // Simply forward to the memory port
    if (pkt->req->isInstFetch()) {
        instPort.sendPacket(pkt);
    } else {
        dataPort.sendPacket(pkt);
    }

    instPort.trySendRetry();
    dataPort.trySendRetry();

    return true;
}

(3)类似于我们如何在MemSidePort中实现发送数据包的便利函数,我们可以在CPUSidePort中实现发送数据包函数,将响应发送到CPU端。此函数调用sendTimingResp,后者将依次调用对等主端口上的recvTimingResp。如果此调用失败且对等端口当前被阻止,则存储稍后要发送的数据包。

 void
SimpleMemobj::CPUSidePort::sendPacket(PacketPtr pkt)
{
    panic_if(blockedPacket != nullptr, "Should never try to send if blocked!");

    if (!sendTimingResp(pkt)) {
        blockedPacket = pkt;
    }
}

(4)稍后我们将在接收到recvRespRetry时发送此被阻止的数据包。此函数与上面的recvReqRetry完全相同,只是尝试重新发送数据包,数据包可能会再次被阻塞。

void
SimpleMemobj::CPUSidePort::recvRespRetry()
{
    assert(blockedPacket != nullptr);

    PacketPtr pkt = blockedPacket;
    blockedPacket = nullptr;

    sendPacket(pkt);
}

(5)最后,我们需要为CPUSidePort实现额外的函数trySendRetry。只要SimpleMemobj 可以被解除阻塞,SimpleMemobj 就会调用此函数。trySendRetry检查是否需要重试,只要SimpleMemobj在新请求上被阻止,我们就会在recvTimingReq中标记重试。然后,如果需要重试,则此函数调用sendRetryReq,后者反过来调用对等主端口(本例中为CPU)上的recvReqRetry

void
SimpleMemobj::CPUSidePort::trySendRetry()
{
    if (needRetry && blockedPacket == nullptr) {
        needRetry = false;
        DPRINTF(SimpleMemobj, "Sending retry req for %d\n", id);
        sendRetryReq();
    }
}

(6)下图显示了CPUSidePortMemSidePortSimpleMemobj之间的关系。此图显示对等端口如何与SimpleMemobj的实现交互。每个粗体函数都是我们必须实现的函数,而非粗体函数是对等端口的端口接口。颜色突出显示通过对象的一个API路径(例如,接收请求或更新内存范围)。

在这里插入图片描述

对于这个简单的内存对象,数据包只是从CPU端转发到内存端。但是,通过修改handleRequest和handleResponse,我们可以创建丰富的特性对象,就像下一部分中的缓存一样。

14、创建一个配置文件

这是实现一个简单内存对象所需的全部代码!在下一部分中,我们将采用这个框架并添加一些缓存逻辑,使这个内存对象成为一个简单的缓存。不过,在这之前,让我们看看配置文件,将SimpleMemobj添加到您的系统中。

此配置文件是在创建简单配置脚本时基于简单配置文件生成的。但是,我们将实例化一个SimpleMemobj,并将其放置在CPU和内存总线之间,而不是将CPU直接连接到内存总线。

import m5
from m5.objects import *

system = System()
system.clk_domain = SrcClockDomain()
system.clk_domain.clock = '1GHz'
system.clk_domain.voltage_domain = VoltageDomain()
system.mem_mode = 'timing'
system.mem_ranges = [AddrRange('512MB')]

system.cpu = TimingSimpleCPU()

system.memobj = SimpleMemobj()

system.cpu.icache_port = system.memobj.inst_port
system.cpu.dcache_port = system.memobj.data_port

system.membus = SystemXBar()

system.memobj.mem_side = system.membus.slave

system.cpu.createInterruptController()
system.cpu.interrupts[0].pio = system.membus.master
system.cpu.interrupts[0].int_master = system.membus.slave
system.cpu.interrupts[0].int_slave = system.membus.master

system.mem_ctrl = DDR3_1600_8x8()
system.mem_ctrl.range = system.mem_ranges[0]
system.mem_ctrl.port = system.membus.master

system.system_port = system.membus.slave

process = Process()
process.cmd = ['tests/test-progs/hello/bin/x86/linux/hello']
system.cpu.workload = process
system.cpu.createThreads()

root = Root(full_system = False, system = system)
m5.instantiate()

print "Beginning simulation!"
exit_event = m5.simulate()
print 'Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause())

现在,当您运行这个配置文件时,您将得到以下输出。

gem5 Simulator System.  http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 compiled Jan  5 2017 13:40:18
gem5 started Jan  9 2017 10:17:17
gem5 executing on chinook, pid 5138
command line: build/X86/gem5.opt configs/learning_gem5/part2/simple_memobj.py

Global frequency set at 1000000000000 ticks per second
warn: DRAM device capacity (8192 Mbytes) does not match the address range assigned (512 Mbytes)
0: system.remote_gdb.listener: listening for remote gdb #0 on port 7000
warn: CoherentXBar system.membus has no snooping ports attached!
warn: ClockedObject: More than one power state change request encountered within the same simulation tick
Beginning simulation!
info: Entering event queue @ 0.  Starting simulation...
Hello world!
Exiting @ tick 507841000 because target called exit()

如果使用SimpleMemobj调试标志运行,则可以看到来自CPU和来自CPU的所有内存请求和响应。

gem5 Simulator System.  http://gem5.org
gem5 is copyrighted software; use the --copyright option for details.

gem5 compiled Jan  5 2017 13:40:18
gem5 started Jan  9 2017 10:18:51
gem5 executing on chinook, pid 5157
command line: build/X86/gem5.opt --debug-flags=SimpleMemobj configs/learning_gem5/part2/simple_memobj.py

Global frequency set at 1000000000000 ticks per second
Beginning simulation!
info: Entering event queue @ 0.  Starting simulation...
      0: system.memobj: Got request for addr 0x190
  77000: system.memobj: Got response for addr 0x190
  77000: system.memobj: Got request for addr 0x190
 132000: system.memobj: Got response for addr 0x190
 132000: system.memobj: Got request for addr 0x190
 187000: system.memobj: Got response for addr 0x190
 187000: system.memobj: Got request for addr 0x94e30
 250000: system.memobj: Got response for addr 0x94e30
 250000: system.memobj: Got request for addr 0x190
 ...

GEM5系列教程索引

GEM5教程–gem5开始之旅(一)

GEM5教程–gem5开始之旅(二)

GEM5教程–修改和拓展gem5(一)

GEM5教程–修改和拓展gem5(二)

GEM5教程–修改和拓展gem5(三)

GEM5教程–修改和拓展gem5(四)

GEM5教程-互联网络

GEM5教程-Garnet

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
gem5学习基础完整版,介绍了gem5环境的安装,以及一些基本概念。 gem5仿真器是用于计算机系统体系结构研究的模块化平台,涵盖系统级体系结构以及处理器微体系结构。1、多个可互换的CPU型号。 gem5提供了种基于解释的CPU模型:简单的单CPI CPU; 有序CPU的详细模型和无序CPU的详细模型。 这些CPU模型使用通用的高级ISA描述。 此外,gem5具有基于KVM的CPU,该CPU使用虚拟化来加速仿真。 2、完全集成的GPU模型,可以执行真实计算机ISA,并支持与主机CPU共享的虚拟内存。 3、NoMali GPU模型。 gem5带有集成的NoMali GPU模型,该模型与Linux和Android GPU驱动程序堆栈兼容,因此无需进行软件渲染。 NoMali GPU不产生任何输出,但可以确保以CPU为中心的实验产生代表性的结果。 4、事件驱动的内存系统。 gem5具有详细的,事件驱动的内存系统,包括高速缓存,交叉开关,探听过滤器以及快速而准确的DRAM控制器模型,用于捕获当前和新兴内存的影响,例如内存。 LPDDR3 / 4/5,DDR3 / 4,GDDR5,HBM1 / 2/3,HMC,WideIO1 / 2。 可以灵活地布置组件,例如,以具有异构存储器的复杂的多级非均匀高速缓存层次结构来建模。 5、基于跟踪的CPU模型,可播放弹性跟踪,这些跟踪是由附着到乱序CPU模型的探针生成的依赖项和定时注释的跟踪。 跟踪CPU模型的重点是以快速,合理的方式而不是使用详细的CPU模型来实现内存系统(高速缓存层次结构,互连和主内存)的性能探索。 6、异构和异构多核。 可以将CPU模型和缓存组合到任意拓扑中,从而创建同构异构的多核系统。 MOESI侦听缓存一致性协议可保持缓存一致性。 7、多种ISA支持。 gem5将ISA语义与其CPU模型解耦,从而实现对多个ISA的有效支持。 目前gem5支持Alpha,ARM,SPARC,MIPS,POWER,RISC-V和x86 ISA。 有关更多信息,请参见支持的体系结构。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值