gem5学习(7):内存系统中创建 SimObjects--Creating SimObjects in the memory system

目录

一、gem5 master and slave ports

二、Packets

三、Port interface

1、主设备发送请求时从设备忙

2、从设备发送响应时主设备忙

四、Simple memory object example

1、Declare the SimObject

2、Define the SimpleMemobj class

3、Define the SimpleMemobj class

4、Define a slave port type

5、Define a master port type

6、Defining the SimObject interface

7、Implementing basic SimObject functions

8、Implementing slave and master port functions

(1)两个简单的函数:getAddrRanges 和 recvFunctional

(2)handleFunctional

(3)recvRangeChange

9、Implementing receiving requests

(1)recvTimingReq

(2)handleRequest(辅助函数)

(3)sendPacket

(4)recvReqRetry

10、Implementing receiving responses

(1)handleResponse

(3)recvRespRetry

(4)trySendRetry

11、create

五、函数之间的调用关系

六、Create a config file

七、测试程序

1、不使用debug flag

2、使用debug flag

3、将 CPU 模型更改为乱序模型(X86O3CPU)【可选项】


前阵子忙于期末大论文和专利的撰写,没有继续学习剩余教程。

今天接着之前的博客总结一下教程学习过程中的心得。

官网教程:gem5: Creating SimObjects in the memory system

这部分教程主要是为了创建一个简单的位于CPU和内存之间的缓存类(下一个教程是在这个缓存类的基础上增加部分逻辑,完成一个简单的拥塞式单处理器缓存)。

一、gem5 master and slave ports

gem5中有两种端口:主端口(master port)和从端口(slave port)。所有的内存对象都通过端口连接在一起,这些端口在内存对象之间提供一个严格的接口。

端口可以实现三种不同的内存系统模式:时序(timing), 原子(atomic)和功能(functional)。

原子模式(Atomic mode:在原子模式下,内存系统操作按照顺序依次执行,没有并发事件发生。使用该模式的主要目的是加快仿真速度并预热仿真器。通过将所有内存请求串行执行,避免事件的并发处理和同步开销。

功能模式(Functional mode:也可以被描述为调试模式,是用于从主机上读取数据到模拟器内存中。

例如:功能模式用于将主机中的process.cmd中的二进制文件加载到模拟系统的内存中,以便模拟系统可以访问它。在读取时,功能访问应返回最新的数据,无论数据位于何处,并且在写入时应更新所有可能的有效数据(例如,在具有缓存的系统中,可能存在多个具有相同地址的有效缓存块)

二、Packets

在gem5中,数据包(Packet)通过端口进行传输。一个数据包由一个内存请求对象(MemReq)组成。内存请求对象(MemReq)保存了关于发起该数据包(Packet)的原始请求的信息,例如请求者、地址和请求类型(读取、写入等)。

数据包还有一个内存命令(MemCmd),它表示数据包的当前命令。该命令在数据包的生命周期中可以发生变化(例如,一旦内存命令满足,请求就会转变为响应)。最常见的内存命令包括ReadReq(读取请求)、ReadResp(读取响应)、WriteReq(写入请求)、WriteResp(写入响应)。还有用于缓存的写回请求(WritebackDirty、WritebackClean)和许多其他命令类型。

内存命令详细解释。

  • ReadReq(读取请求):ReadReq是一个读取请求命令,用于请求从内存中读取数据。当CPU需要读取特定内存地址中的数据时,它将发送一个ReadReq命令给内存对象,以请求数据传输。
  • ReadResp(读取响应):ReadResp是一个读取响应命令,用于回复读取请求并返回请求的数据。当内存对象接收到ReadReq命令后,在从内存中读取到相应数据后,会通过发送ReadResp命令将数据返回给CPU。
  • WriteReq(写入请求):WriteReq是一个写入请求命令,用于请求将数据写入到内存中的特定地址。当CPU需要将数据写入到特定内存地址时,它将发送一个WriteReq命令给内存对象,以请求数据的写入操作。
  • WriteResp(写入响应):WriteResp是一个写入响应命令,用于回复写入请求并确认数据已成功写入内存。当内存对象接收到WriteReq命令后,它会执行写入操作,并通过发送WriteResp命令来确认数据已经成功写入内存。

ReadReq和ReadResp用于在CPU和内存之间进行读取数据的请求和响应;WriteReq和WriteResp用于在CPU和内存之间进行写入数据的请求和响应。读取请求和写入请求是CPU向内存发送的命令,而读取响应和写入响应是内存对象向CPU发送的命令,用于确认请求的执行和数据的传输。

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

最后,数据包在经典缓存中被用作跟踪一致性的单位。因此,数据包代码的大部分是针对经典缓存一致性协议的。然而,在gem5中,数据包用于所有内存对象之间的通信,即使它们与一致性没有直接关系(例如DRAM控制器和CPU模型)。

所有端口接口函数都接受一个数据包(Packet)指针作为参数。由于该指针非常常见,gem5中包含了一个typedef:PacketPtr。【用来作为一次请求是否完成的根据】

三、Port interface

在Gem5中,有两种类型的端口:主端口(Master Port)和从端口(Slave Port)。要实现一个内存对象,都需要实现至少一种类型的端口。主端口用于向内存对象发送读取和写入请求,从端口用于接收来自其他组件(例如CPU)的读取和写入请求。

为此,可以创建一个新的类,继承自MasterPort或SlavePort,用于主端口和从端口。

  • 主端口(Master Port)用于向内存对象发送读取和写入请求。它由主要的控制单元(通常是CPU)使用,用于主动发起对内存对象的读取和写入操作。主端口负责管理请求的生成、发送和处理,以及与内存对象的通信。CPU或其他主要组件使用主端口来访问内存对象,主动控制读取和写入的时机和方式。
  • 从端口(Slave Port)用于接收来自其他组件(例如CPU)的读取和写入请求。它作为内存对象的接口,用于接收来自其他组件的读取和写入请求,并将这些请求传递给内部的处理逻辑。从端口负责处理传入请求,并根据请求类型执行相应的读取和写入操作。
  • 主端口和从端口的主要区别在于使用方式和功能角色。主端口由主要控制单元使用,主动发起对内存对象的读取和写入请求;而从端口作为内存对象的接口,被动地接收和处理来自其他组件的读取和写入请求。

下面的图示展示了主端口和从端口之间最简单的交互方式。该图展示了时序模式下的交互。其他模式则更简单,并且在主端口和从端口之间使用简单的调用链。

所有的端口接口都要求以PacketPtr作为参数。每个函数(sendTimingReq、recvTimingReq等)都接受一个参数,即PacketPtr。这个PacketPtr【数据包指针】代表要发送或接收的请求或响应数据包。

要发送一个请求数据包,主设备(发送请求的设备)调用sendTimingReq函数。在同一个调用链中,从设备上的recvTimingReq函数被调用,它的唯一参数也是PacketPtr,与sendTimingReq函数使用的是同一个PacketPtr。

recvTimingReq函数的返回类型是bool。这个布尔返回值直接返回给调用的主设备。返回true表示从设备已经接受了数据包。而返回false则表示从设备无法接受数据包,请求必须在将来的某个时间重试。

在上面的示例中,首先,主设备通过调用sendTimingReq发送一个定时请求,该函数接着调用recvTimingResp。从设备从recvTimingReq函数中返回true,这个返回值从sendTimingReq函数中返回。主设备继续执行,而从设备则完成必要的操作来处理请求(例如,如果它是一个缓存,它会查找标签以查看请求中的地址是否匹配)。

一旦从设备完成请求处理,它可以向主设备发送响应。从设备调用 sendTimingResp 函数并传递响应数据包(这应该是与请求相同的 PacketPtr,但现在应该是一个响应数据包)。接着,主设备的 recvTimingResp 函数将被调用。主设备的 recvTimingResp 函数返回 true,而这个返回值将传递给从设备的 sendTimingResp。因此,该请求的交互过程完成了。

1、主设备发送请求时从设备忙

在这种情况下,从设备在 recvTimingReq 函数中返回 false【说明此时从设备在忙,没能及时相应主设备的请求】。当主设备在调用 sendTimingReq 后收到 false 时,它必须等待直到执行 recvReqRetry 函数【主设备需要等待从设备发出的信号,而不是持续等待,而从设备会主动发送sendReqRetry信号,通知主设备可以重新尝试发送请求】。只有在调用该函数之后,主设备才能重新尝试调用 sendTimingRequest【相当于第一次请求没响应,再请求一次】。上述图示展示了定时请求失败一次的情况,但它可能会失败任意次数。

注意:跟踪失败的数据包是主设备要完成的事情,而不是从设备的任务。从设备不会保留失败的数据包指针。【也就是说从设备没有记忆,不会记录失败的数据包】

2、从设备发送响应时主设备忙

类似于上述,当主设备在从设备尝试发送响应时忙于其他任务的情况【主设备忙】。在这种情况下,从设备在接收到 recvRespRetry 之前无法调用 sendTimingResp。

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

通俗理解:一些函数的调用会在同一个调用栈中完成,由于代码路径的连续性,可能创建无限递归或其他错误,导致程序陷入无限循环,或者产生其他不正确的行为。所以,从设备在发送sendReqRetry信号之前,就要做好处理下一个请求的准备。

四、Simple memory object example

在本节中,将构建一个简单的内存对象。

它将仅仅将请求从 CPU 端(a simple CPU)传递到内存端(a simple memory bus)。它具有一个主设备端口(master port),用于向内存总线(the memory bus)发送请求,并具有两个 CPU 端口(two cpu-side ports),用于 CPU 的指令(instruction port)和数据缓存端口(data cache port)。

1、Declare the SimObject

创建一个 SimObject 的 Python 文件。名称:SimpleMemobj.py

将这个简单的内存对象命名为 SimpleMemobj,并在 src/learning_gem5/part2/simple_memobj 中创建 SimObject 的 Python 文件。

from m5.params import *
from m5.proxy import *
from m5.SimObject import SimObjectclass SimpleMemobj(SimObject):type = 'SimpleMemobj'cxx_header = "learning_gem5/part2/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")

这个对象是从 SimObject 继承的。SimObject 类有一个纯虚函数【就是只定义了这个函数,但是没有任何实现。如果继承了这个类,就需要在类中实现这个函数】,需要在 C++ 实现中定义它,即 getPort

这个对象的参数是三个端口。两个端口用于连接 CPU 的指令和数据端口,另一个端口用于连接内存总线。这些端口没有默认值,并且有一个简单的描述【可以没有参数,如果有的话就必须是描述】。在实现 SimpleMemobj 并定义 getPort 函数时,需要使用这些名称。

2、Define the SimpleMemobj class

构造文件中声明SimObject 的 Python 文件。名称:SConscript

Import('*')SimObject('SimpleMemobj.py')
Source('simple_memobj.cc')DebugFlag('SimpleMemobj', "For Learning gem5 Part 2.")

3、Define the SimpleMemobj class

为SimpleMemobj类创建一个头文件。名称:SimpleMemobj.hh

#include "mem/port.hh"
#include "params/SimpleMemobj.hh"
#include "sim/sim_object.hh"class SimpleMemobj : public SimObject
{private:public:/** constructor*/SimpleMemobj(SimpleMemobjParams *params);
};

前阵子师弟问了我一个问题,就是在引入头文件的时候,没有"params/SimpleMemobj.hh",为什么在引入后不会提示有错。

答案:这个文件夹里的都是.hh头文件,准确来说/build/X86/params文件夹中的.hh文件是由gem5的构建过程生成的,这个构建过程包括了三个阶段:配置(SCons配置gem5的构建环境),编译(编译源代码并生成可执行文件)、生成(把可执行文件和其他文件复制到指定的目标位置)。这个过程中就会自动生成一些文件,其中就包括这个.hh文件(这个文件是构建过程中生成的中间文件)。在scons的时候,哪怕这个编译过程没有完成,但是前面这些为了生成可执行文件而配置的环境和中间文件,就自动生成了。构建系统会根据构建规则生成编译所需的中间文件和目标文件,并将它们放置在指定的构建目录中(比如gem5/build/X86/params文件夹)。所以即使在构建过程中尚未生成ZylObject.hh文件,但因为指定了路径,编译过程会成功,并且引用的头文件将在构建后的可执行文件中正确被解析。【可能有些绕,多读几遍】

4、Define a slave port type

这部分是在SimpleMemobj.hh中定义SimpleMemobj类时的内部类定义。

从设备端口(CPU 端口)

这两个类型的端口可以直接在SimpleMemobj 类内部声明,因为其他对象不会使用这些类。

从 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;
};

这个对象需要定义五个函数。

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

5、Define a master port type

这部分也是在SimpleMemobj.hh中定义SimpleMemobj类时的内部类定义。

定义一个主设备端口类型。这将是内存端口,它将把来自 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;
};

这个类只有三个纯虚函数。

6、Defining the SimObject interface

上面已经定义了两种新类型CPUSidePort 和 MemSidePort,将它们作为 SimpleMemobj 的一部分来声明三个端口。还需要在 SimObject 类中声明纯虚函数 getPort。在初始化阶段,gem5 使用这个函数通过端口将内存对象连接在一起。

  • 纯虚函数(Pure Virtual Function)是在基类中声明的虚函数,但没有提供具体的实现。
  • 纯虚函数在基类中用于定义接口和行为规范,它要求派生类必须实现该函数。由于纯虚函数没有具体的实现,所以基类本身不能被实例化,而只能作为一个抽象类使用【抽象类(Abstract Class)是指包含纯虚函数(pure virtual function)的类。抽象类不能被实例化,只能作为基类用于派生其他类】,派生类必须提供对纯虚函数的具体实现才能被实例化。
  • 派生类必须在其定义中提供对纯虚函数的实现,否则派生类也将成为抽象类。如果派生类没有提供对所有纯虚函数的实现,则该派生类仍然是抽象类,无法被实例化。
class SimpleMemobj : public SimObject
{private:<CPUSidePort declaration><MemSidePort declaration>CPUSidePort instPort;CPUSidePort dataPort;MemSidePort memPort;public:SimpleMemobj(SimpleMemobjParams *params);Port &getPort(const std::string &if_name,PortID idx=InvalidPortID) override;
};

7、Implementing basic SimObject functions

名称:SimpleMemobj.cc

对于 SimpleMemobj 的构造函数,将简单地调用 SimObject 的构造函数。还需要初始化所有的端口。每个端口的构造函数有两个参数:名称和指向其所有者的指针。名称可以是任何字符串,但按照惯例,它与 Python SimObject 文件中的名称相同。同时将 blocked 初始化为 false。

#include "learning_gem5/part2/simple_memobj.hh"
#include "debug/SimpleMemobj.hh"SimpleMemobj::SimpleMemobj(SimpleMemobjParams *params) :SimObject(params),instPort(params->name + ".inst_port", this),dataPort(params->name + ".data_port", this),memPort(params->name + ".mem_side", this), blocked(false)
{
}

接下来,我们需要实现获取端口的接口。这个接口是由函数 getPort 组成。该函数有两个参数。if_name 是该对象的接口的 Python 变量名。

为了实现 getPort,我们将比较 if_name 并检查它是否与我们的 Python SimObject 文件【SimpleMemobj.py】中指定的 mem_side 相匹配。如果匹配,则返回 memPort 对象。如果名称是 "inst_port",则返回 instPort,如果名称是 "data_port",则返回data_Port。如果不是,我们将请求名称传递给父类。【这个过程就是判断端口属性的】

Port &
SimpleMemobj::getPort(const std::string &if_name, PortID idx)
{panic_if(idx != InvalidPortID, "This object doesn't support vector ports");// This is the name from the Python SimObject declaration (SimpleMemobj.py)if (if_name == "mem_side") {return memPort;} else if (if_name == "inst_port") {return instPort;} else if (if_name == "data_port") {return dataPort;} else {// pass it along to our super classreturn SimObject::getPort(if_name, idx);}
}

8、Implementing slave and master port functions

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

(1)两个简单的函数:getAddrRanges 和 recvFunctional

它们只是调用 SimpleMemobj 的相应函数。

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

(2)handleFunctional

将请求传递到内存端,使用 DPRINTF 调用来跟踪调试目的的操作情况。

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

(3)recvRangeChange

对于 MemSidePort,需要实现 recvRangeChange 并通过 SimpleMemobj 将请求转发到从设备端口。

void
SimpleMemobj::MemSidePort::recvRangeChange()
{owner->sendRangeChange();
}
void
SimpleMemobj::sendRangeChange()
{instPort.sendRangeChange();dataPort.sendRangeChange();
}

9、Implementing receiving requests

(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;}
}

为了处理 SimpleMemobj 的请求,首先检查 SimpleMemobj 是否已经被阻塞,等待另一个请求的响应。如果被阻塞(有请求未处理),将返回 false,向调用的主设备端口表示从设备目前无法接受该请求。否则,将标记该端口为被阻塞状态,并通过内存端口发送数据包。

为此,可以在 MemSidePort 对象中定义一个辅助函数,将流控隐藏在 SimpleMemobj 实现之后。我们假设 memPort 处理所有的流控,并且始终从 handleRequest 中返回 true,因为我们成功消耗了请求。

(2)handleRequest(辅助函数)

  • blocked为true,表示SimpleMemobj当前被阻塞,有其他请求正在处理中,那么函数会返回false,表示无法接受新的请求。
  • SimpleMemobj没有被阻塞,那么会输出一条日志信息,表示接收到了请求的地址。接着,将blocked标记为true,表示SimpleMemobj被阻塞,然后通过memPort发送数据包(pkt)。
  • 最后,函数返回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)sendPacket

需要在 MemSidePort 中实现 sendPacket 函数。这个函数将处理流控,以防其对等的从设备端口无法接受请求。为此,我们需要在 MemSidePort 中添加一个成员变量来存储在被阻塞时的数据包。如果接收方无法接收请求(或响应),发送方负责存储数据包。

这个函数简单地调用 sendTimingReq 函数来发送数据包。如果发送失败,那么该对象将数据包存储在 blockedPacket 成员变量中,以便在以后的时间发送数据包(当它接收到 recvReqRetry 时)。这个函数还包含了一些防御性的代码提示,如果blockedPacket 不为空(即已经有一个被阻塞的数据包),就会抛出错误(panic)。提示“如果存在被阻塞的数据包,就不应该尝试发送新的数据包”。【防止出现不一致状态】

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

(4)recvReqRetry

实现重新发送数据包。在这个函数里,可以直接调用上述的sendPacket 函数重新发送数据包。

void
SimpleMemobj::MemSidePort::recvReqRetry()
{assert(blockedPacket != nullptr);PacketPtr pkt = blockedPacket;blockedPacket = nullptr;sendPacket(pkt);
}

10、Implementing receiving responses

响应请求部分和接受请求类似。

当 MemSidePort 收到响应时,通过 SimpleMemobj 将响应转发到相应的 CPUSidePort。

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

(1)handleResponse

在 SimpleMemobj 中,当收到响应时,首先,对象应该始终处于阻塞状态,因为它是一个阻塞对象(阻塞状态表示该对象当前正在处理某个请求,还未完成)。在将数据包发送回 CPU 端口之前,需要将对象标记为非阻塞状态。这一步骤必须在调用sendTimingResp函数之前完成。如果在发送响应之前没有解除阻塞状态,可能会导致无限循环。这是因为在接收到响应并发送另一个请求之间,主设备端口可能只有一个调用链(调用路径),而没有其他机制来检测和处理阻塞状态。

在解除 SimpleMemobj 的阻塞后,检查数据包是指令包还是数据包,并将其通过适当的端口发送回去。最后,由于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 portif (pkt->req->isInstFetch()) {instPort.sendPacket(pkt);} else {dataPort.sendPacket(pkt);}instPort.trySendRetry();dataPort.trySendRetry();return true;
}

(2)sendPacket

类似于在 MemSidePort 中实现的发送数据包函数,可以在 CPUSidePort 中实现一个 sendPacket 函数,用于向 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;}
}

(3)recvRespRetry

接收到 recvRespRetry 时,将重新发送这个被阻塞的数据包。这个函数与上面的 recvReqRetry 完全相同,只是简单地尝试重新发送数据包,它可能再次被阻塞。

void
SimpleMemobj::CPUSidePort::recvRespRetry()
{assert(blockedPacket != nullptr);PacketPtr pkt = blockedPacket;blockedPacket = nullptr;sendPacket(pkt);
}

(4)trySendRetry

最后,需要在 CPUSidePort 中实现额外的函数 trySendRetry。每当 SimpleMemobj 可能解除阻塞时, SimpleMemobj 调用该函数。trySendRetry函数的作用是检查是否需要进行重试。在SimpleMemobj的recvTimingReq函数中,当SimpleMemobj在新的请求上被阻塞时,会进行标记。这个标记的目的是指示当前的请求无法立即执行,需要进行重试。因此,在trySendRetry函数中,会检查是否存在需要重试的情况。如果需要重试,该函数会调用sendRetryReq函数,而sendRetryReq函数会调用对等主设备端口(在这个例子中是CPU)的recvReqRetry函数。

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

11、create

上述的函数都是在SimpleMemobj.cc中实现的,但是下面create函数还没找到在哪实现(存疑)。

除了这个函数之外,为了完成文件,我们还需要添加 SimpleMemobj 的 create 函数

SimpleMemobj*
SimpleMemobjParams::create()
{return new SimpleMemobj(this);
}

五、函数之间的调用关系

下图显示了 CPUSidePort、MemSidePort 和 SimpleMemobj 之间的关系。该图示展示了对等端口与 SimpleMemobj 实现之间的交互方式。每个加粗的函数都是必须实现的函数,而非加粗的函数则是与对等端口的接口函数。颜色突出显示了对象中的一个 API 路径(例如,接收请求或更新内存范围)。

对于这个简单的内存对象,数据包只是从 CPU 端转发到内存端。然而,通过修改 handleRequest 和 handleResponse,我们可以创建功能丰富的对象,比如在下一章中介绍的缓存对象。gem5: Creating a simple cache object

六、Create a config file

文件名:simple_cache.py【执行文件】

将 SimpleMemobj 添加到系统的配置文件中。

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 = X86TimingSimpleCPU()system.memobj = SimpleMemobj()system.cpu.icache_port = system.memobj.inst_port
system.cpu.dcache_port = system.memobj.data_portsystem.membus = SystemXBar()system.memobj.mem_side = system.membus.slavesystem.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.mastersystem.mem_ctrl = DDR3_1600_8x8()
system.mem_ctrl.range = system.mem_ranges[0]
system.mem_ctrl.port = system.membus.mastersystem.system_port = system.membus.slaveprocess = 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模拟器配置系统并运行一个简单的hello程序。

  • 首先,通过import m5from m5.objects import *导入gem5的相关模块和对象。

  • 然后,创建一个System对象来表示系统。设置系统的时钟域(clk_domain)为1GHz,并指定电压域(voltage_domain)。将系统的内存模式(mem_mode)设置为'timing',表示使用时序模型。并指定系统的内存范围(mem_ranges)为512MB。

  • 接下来,创建一个X86架构的简单时序CPU(X86TimingSimpleCPU)作为系统的CPU。

  • 创建一个SimpleMemobj对象作为系统中的内存对象。

  • 将CPU的指令端口(icache_port)和数据端口(dcache_port)连接到SimpleMemobj的端口。

  • 创建一个SystemXBar对象作为系统的内存总线。

  • SimpleMemobj的内存端口(mem_side)连接到系统的内存总线上。

  • 为CPU创建一个中断控制器,并将其与系统的总线连接起来,用于处理中断信号。

  • 创建一个DDR3内存控制器(DDR3_1600_8x8),并将其范围设置为系统的内存范围,将其端口连接到系统的总线上。

  • 将系统总线的从端口(slave)连接到系统的系统端口(system_port)。

  • 创建一个Process对象,设置其cmd属性为要运行的hello程序的路径【在这段代码中,process.cmd是一个字符串列表,指定要运行的可执行程序的路径和命令行参数。在这里,process.cmd被设置为['tests/test-progs/hello/bin/x86/linux/hello'],表示要运行的可执行程序是hello。这个程序将在gem5模拟的系统中作为工作负载被执行】。

  • 将这个进程作为工作负载(workload)分配给CPU。

  • 创建CPU的线程。

  • 创建一个Root对象来表示系统的根节点,将其full_system属性设置为False,表示运行的是一个部分系统模拟。将系统对象指定为系统根节点的属性。

  • 通过m5.instantiate()实例化系统对象。

  • 开始仿真过程,调用m5.simulate()函数,并将返回的exit_event保存在exit_event变量中。

    最后,打印仿真结束的信息,包括仿真结束时的时钟周期数和结束的原因。

七、测试程序

1、不使用debug flag

build/X86/gem5.opt configs/learning_gem5/part2/simple_cache.py

2、使用debug flag

build/X86/gem5.opt --debug-flags=SimpleMemobj configs/learning_gem5/part2/simple_memobj.py

3、将 CPU 模型更改为乱序模型(X86O3CPU)【可选项】

使用乱序 CPU,可能会看到不同的地址流,因为它允许同时存在多个内存请求。

在使用乱序 CPU 时,由于 SimpleMemobj 是阻塞的,可能会出现很多停顿。

最后说明:

上述是个人比较肤浅的学习总结,大多是直接翻译英文教程,其中有一些英文逻辑理解不太清晰的我结合自己个人理解又加了一些内容,欢迎大家提出问题,共同探讨。

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

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

相关文章

EST-100身份证社保卡签批屏按捺终端PC版web版本http协议接口文档,支持web网页开发对接使用

<!DOCTYPE html><html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width,initial-scale1.0"><title>演示DEMO</title><script type"text/…

亚马逊SEO是什么意思?亚马逊标题的SEO方法是什么?-站斧浏览器

亚马逊SEO是什么意思&#xff1f; 亚马逊SEO主要包括了对标题、描述、五点简介等元素的优化&#xff0c;以及评价和评论的管理等方面。下面将详细分析亚马逊SEO的相关内容&#xff0c;帮助卖家更好地理解和应用。 在亚马逊平台上进行SEO优化需要考虑以下几个方面&#xff1a;…

gin框架使用系列之四——json和protobuf的渲染

系列目录 《gin框架使用系列之一——快速启动和url分组》《gin框架使用系列之二——uri占位符和占位符变量的获取》《gin框架使用系列之三——获取表单数据》 上篇我们介绍了如何获取数据&#xff0c;本篇我们介绍一下如何返回固定格式的数据。 一、返回JSON数据 在web开发中…

Linux管理LVM逻辑卷

目录 一、LVM逻辑卷介绍 1. 概述 2. LVM基本术语 2.1 PV&#xff08;Physical Volume&#xff0c;物理卷&#xff09; 2.2 VG (Volume Group&#xff0c;卷组&#xff09; 2.3 LV (Logical Volume&#xff0c;逻辑卷&#xff09; 3. 常用的磁盘命令 4. 查看系统信息的命…

golang第一卷---go入门

go入门 对于使用go的好处环境变量配置开发工具 参考网站 &#xff1a;go入门 对于使用go的好处 简单好记的关键词和语法。轻松上手&#xff0c;简单易学。更高的效率。比Java&#xff0c;C等拥有更高的编译速度&#xff0c;同时运行效率媲美C&#xff0c;同时开发效率非常高。…

爬虫工作量由小到大的思维转变---<第三十三章 Scrapy Redis 23年8月5日后会遇到的bug)>

前言: 收到回复评论说,按照我之前文章写的: 爬虫工作量由小到大的思维转变---&#xff1c;第三十一章 Scrapy Redis 初启动/conn说明书)&#xff1e;-CSDN博客 在启动scrapy-redis后,往redis丢入url网址的时候遇到: TypeError: ExecutionEngine.crawl() got an unexpected …

数据资产专题3:估值

欢迎关注主页个人介绍及相关链接&#xff0c;获取更多算法源码材料 2023数据资源入表白皮书&#xff0c;推荐系统源码下载-CSDN博客 浅析研发支出费用化和资本化的区别-CSDN博客 商业银行数据资产估值白皮书&#xff0c;推荐系统源码下载-CSDN博客 用友BIP数据资产入表解决…

飞企互联-FE企业运营管理平台 登录绕过漏洞复现

0x01 产品简介 飞企互联-FE企业运营管理平台是一个基于云计算、智能化、大数据、物联网、移动互联网等技术支撑的云工作台。这个平台可以连接人、链接端、联通内外&#xff0c;支持企业B2B、C2B与O2O等核心需求&#xff0c;为不同行业客户的互联网转型提供支持。 0x02 漏洞概…

【12.28】转行小白历险记-刷算法04

01两两交换链表中的节点 整体思路 1.要修改后一个节点的指向一定要知道前一个节点的指向才可以改变后面一个节点的 2.分情况奇数和偶数节点&#xff0c;终止条件很重要 3.虚拟头节点&#xff0c;是对我们操作的指针是不是头节点进行判断 02删除链表的倒数第N个节点 思路 …

QT应用篇 二、QML用Image组件实现Progress Bar 的效果

QT应用篇 一、QT上位机串口编程 二、QML用Image组件实现Progress Bar 的效果 三、QML自定义显示SpinBox的加减按键图片及显示值效果 文章目录 QT应用篇前言一、qml需求二、使用组件1.Image组件2.Image中fillMode的使用例子 总结 前言 记录自己学习QML的一些小技巧方便日后查找…

前端使用高德api的AMap.Autocomplete无效,使用AMap.Autocomplete报错

今天需要一个坐标拾取器&#xff0c;需要一个输入框输入模糊地址能筛选的功能 查看官方文档&#xff0c;有一个api可以直接满足我们的需求 AMap.Autocomplete 上代码 AMapLoader.load({"key": "你的key", // 申请好的Web端开发者Key&#xff0c;首次调…

C语言 linux文件操作(二)

文章目录 一、获取文件长度二、追加写入三、覆盖写入四、文件创建函数creat 一、获取文件长度 通过lseek函数&#xff0c;除了操作定位文件指针&#xff0c;还可以获取到文件大小&#xff0c;注意这里是文件大小&#xff0c;单位是字节。例如在file1文件中事先写入"你好世…

通过Python将PDF转为文本,快速提取PDF中的文字

快速高效地从PDF文档中提取信息对于专业人士来说非常重要。处理大量PDF文件时&#xff0c;将PDF转换为可编辑的文本格式可以节省时间和精力。而强大的Python语言正是在这些方面发挥其作用。利用Python中丰富的API&#xff0c;我们可以轻松在Python程序中将PDF转换为文本&#x…

第二证券:A股市场放量反弹 跨年行情或启动

沪指日线等级放量反弹&#xff0c;周四收中阳线成功站上20日均线&#xff0c;底部结构或可树立。创业板指大涨近4%&#xff0c;日线MACD出现底违反&#xff0c;多方动能较强&#xff0c;中等级反弹行情或在酝酿。月线来看&#xff0c;12月创业板指探底上升出现较长下影&#xf…

畅捷通的 Serverless 探索实践之路

作者&#xff1a;计缘&#xff0c;阿里云云原生架构师 畅捷通介绍 畅捷通是中国领先的小微企业财税及业务云服务提供商&#xff0c;成立于 2010 年。畅捷通在 2021 年中国小微企业云财税市场份额排名第一&#xff0c;在产品前瞻性及行业全覆盖方面领跑市场&#xff0c;位居中…

小型洗衣机怎么用?高质量的小型洗衣机推荐

清洗内衣内裤这些贴身衣物确实是一件比较头疼的事&#xff0c;有的小伙子由于工作的劳累通常在洗完澡后并不喜欢直接清洗内衣内裤&#xff0c;会存上几天再扔到洗衣机里&#xff0c;这样做是很不可取的&#xff0c;因为穿过的内裤很久不洗就会滋生细菌&#xff0c;另外&#xf…

AGV|RGV小车RFID传感器CNS-RFID-01/1S的RS232通讯联机方法

CNS-RFID-01/1S广泛应用于AGV小车&#xff0c;搬运机器人&#xff0c;无人叉车等领域&#xff0c;用于定位&#xff0c;驻车等应用&#xff0c;可通过多种通讯方式进行读写操作&#xff0c;支持上位机控制&#xff0c;支持伺服电机&#xff0c;PLC等控制设备联机&#xff0c;本…

CUDA驱动深度学习发展 - 技术全解与实战

全面介绍CUDA与pytorch cuda实战 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&#xff0c;项目管理专业人士&…

盘点 2023 公开的攻击面发现平台

针对可以直接购买并且明码标价的攻击面发现平台进行了对比&#xff0c;结果如下 测试用例&#xff1a; 企业&#xff08;某制造有限公司&#xff09;、高校&#xff08;某职业学院&#xff09; 测试对象&#xff1a; 零零信安攻击面管理平台 长亭云图极速版攻击面管理平台 …

【AI大语言模型】ChatGPT在地学、GIS、气象、农业、生态、环境等领域中的应用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…