目录
(2)关于单线程系统(STS)模式和多线程系统(MTS)模式
三、以CPUSidePort为例定义一个SlavePort以实现内存系统通信
一、关于SE模式和FS模式
gem5支持两种系统模拟方式,一种是System Call Emulation(SE),也叫系统调用模拟模式;另一种是Full System Simuluation(FS),也叫全系统模拟模式。
模拟模式 | 特点 |
SE | 基于系统调用模拟的,它只模拟处理器和内存,而不模拟 I/O 系统和外部设备。这种模式可以在较短的时间内模拟出系统的性能,适用于快速的性能评估。 它无法模拟和 OS 相关的操作,比如文件系统访问、网络通信等。它只能模拟处理器执行指令和内存访问,并不能模拟完整的操作系统行为。 |
FS | 基于全系统模拟的,它模拟了整个计算机系统,包括处理器、内存、I/O 系统和外部设备。这种模式可以更准确地模拟出系统的性能,适用于详细的性能分析和研究。 |
(1)SE模式的系统调用会涉及到操作系统吗?
SE 模式下,用户程序是运行在模拟器内部的,而不是真实的操作系统中。因此,系统调用并不会直接涉及操作系统,它们是由模拟器提供的接口,用来模拟系统调用的行为。模拟器内部会模拟出系统调用的效果,但不会真正调用操作系统。所以,SE 模式下,系统调用并不会涉及操作系统。
总的来说,SE 模式是模拟系统调用的行为,而 FS 模式是使用真实的操作系统来完成系统调用。因此,FS 模式更加精确,可以更准确地模拟出系统的性能,但模拟时间也更长。
(2)关于单线程系统(STS)模式和多线程系统(MTS)模式
-
STS 模式(Single Thread System Mode)是指 Gem5 模拟器运行在单线程模式下,只能使用一个 CPU 核心运行模拟的模式。在 STS 模式下,Gem5 模拟器只模拟硬件,不提供完整的操作系统环境。
-
MTS 模式(Multi-Thread System Mode)是指 Gem5 模拟器运行在多线程模式下,但是所有的线程都是由 gem5 端控制的,m5 端不能被调用的模式。在 MTS 模式下,Gem5 模拟器提供了一个完整的操作系统环境,用户可以在这个环境中运行应用程序,但是不能与模拟硬件进行交互。
二、内存
Gem5 模拟器可以模拟各种不同类型的内存,包括 DRAM、SRAM、Flash 和其他类型的存储器。gem5中的DRAM模型是事件驱动的,支持多种市面上常见的DRAM,如DDR3、DDR4、DDR5、GDDR、HMC、HBM等。在gem5中,DRAM模型不是时钟精确的,但是可以和时钟精确的DRAM模拟器DRAMSim3结合使用,gem5中提供了和DRAMSim连接的接口
(1)关于MasterPort 和 SlavePort
内存本身并不具备端口,它是一种计算机存储器,用来存储程序和数据。通常,内存由一个或多个内存模块组成,并且每个内存模块都有多个存储单元,用于存储数据。但是,内存本身不包含端口。
SlavePort 和 MasterPort 是模拟器程序中使用的概念,用于描述计算机系统中不同组件之间的数据传输关系。在模拟器中,SlavePort 和 MasterPort 是用于连接计算机系统中的各种组件的接口。例如,MasterPort 可以作为内存的接口,用于与处理器连接,并允许处理器读取和写入内存。同样,SlavePort 可以作为处理器的接口,用于与内存连接,并允许内存读取和写入处理器。
此外,内存模型两侧的 MasterPort 和 SlavePort 是可以随意选定的。在模拟器程序中,MasterPort 和 SlavePort 只是用于描述计算机系统中不同组件之间的数据传输关系的概念,并不存在“正确”或“错误”的使用方式。所以,在内存模型两侧的 MasterPort 和 SlavePort 可以随意选定。例如,如果希望将内存模型的 MasterPort 与 CPU 相连,那么与内存相连的另外一端就是 SlavePort,反过来也成立。不过,需要注意的是,MasterPort 和 SlavePort 的选定必须保证模拟器程序的正确性,因为它们决定了不同组件之间的数据传输方向。
具体来说,MasterPort 表示数据的发送方,而 SlavePort 表示数据的接收方。因此,在模拟器程序中,MasterPort 和 SlavePort 的选定决定了不同组件之间的数据传输方向。例如,如果内存模型的 MasterPort 连接到处理器,那么数据就会从内存模型流向处理器。同样,如果内存模型的 SlavePort 连接到处理器,那么数据就会从处理器流向内存模型。
三、以CPUSidePort为例定义一个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;
};
这段代码定义了一个名为 CPUSidePort 的类,它继承自 SlavePort 类。在 CPUSidePort 类的构造函数中,它接受两个参数:name 和 owner。这两个参数分别用于指定新创建的 CPUSidePort 对象的名称和所属的 SimpleObject 对象。它首先调用 SlavePort 类的构造函数,将 name 和 owner 传递给父类。接着,它将 owner 参数赋值给 CPUSidePort 类的同名成员变量 owner。该构造函数用于创建一个 CPUSidePort 对象,并初始化其名称和所属的SimpleObject 对象。
CPUSidePort 类中包含了五个成员函数:getAddrRanges()、recvAtomic()、recvFunctional()、recvTimingReq() 和 recvRespRetry()。
- CPUSidePort 类中的第一个成员函数 getAddrRanges() 用于获取内存模型的地址范围,并返回一个 AddrRangeList 类型的值。
- 第二个成员函数 recvAtomic() 用于处理原子操作的数据包(Atomic Packet)原子操作是指不能被中断的操作,通常用于同步或互斥操作。该函数接收一个 PacketPtr 类型的参数 pkt,但由于该函数未实现,因此如果调用该函数会导致程序崩溃,并引发 panic 错误。
- 第三个成员函数 recvFunctional() 用于处理功能请求的数据包(Functional Packet)。功能请求是指不改变系统状态的请求,通常用于读取数据或检查系统状态。
- 第四个成员函数recvTimingReq() 函数用于处理定时请求的数据包。定时请求是指需要等待一段时间后才能完成的请求,通常用于写入数据或执行耗时操作。
- 第五个成员函数recvRespRetry() 函数用于处理响应重试的数据包。响应重试是指接收到响应时,系统需要重新发送请求才能完成操作。这种情况通常发生在系统存在某些冲突或限制时。
四、一些不容易想到的坑点
(1)关于sendRetryReq
void
SimpleMemobj::CPUSidePort::trySendRetry()
{
if (needRetry && blockedPacket == nullptr) {
needRetry = false;
DPRINTF(SimpleMemobj, "Sending retry req for %d\n", id);
sendRetryReq();
}
}
当时我看这段代码,眼睛看花了都没有找到sendRetryReq()函数的实现在哪里,结果是自带的。gem5 模拟器中的计算机总线交互是基于 SystemC TLM (Transaction Level Modeling) 标准的,所以它提供了一系列的函数来支持计算机总线交互。其中就包括了 sendRetryReq 函数。
在 gem5 中,你可以在 SimpleMemObject 类的 CPUSidePort 类型中使用 sendRetryReq 函数,来向对端主端口发送重试请求。(比如,如果CPUSidePort 和CPU相连,那这个函数会向该端口再次发送请求重试,无需自己实现)
(2)关于InvalidPortID
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;
};
我觉得 <CPUSidePort declaration>,<MemSidePort declaration>这俩代码没啥用,因为CPUSidePort和MemSidePort类都已经定义好了,可以删掉,有问题咱们可以讨论别骂我。
此外,InvalidPortID 是一个常量,它的值通常是 -1。它可能是在某些库中定义的,表示无效的端口 ID。在 gem5 模拟器中,InvalidPortID 是在头文件 <base/port.hh> 中定义的。gem5 中的端口是通过 PortID 类型的变量来标识的,InvalidPortID 常量用来表示无效的端口 ID。
(3)关于pkt->req->isInstFetch()
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;
}
isInstFetch() 是 Packet 类的一个成员函数,它用于判断当前的 Packet 对象是否是用于指令访问的请求。如果是,则返回 true;否则返回 false。Packet 类是 gem5 模拟器中用于封装访问请求和响应的类,它在头文件 <mem/packet.hh> 中定义。isInstFetch() 函数是 Packet 类中自带的函数,不需要你自己实现。如果你的代码中使用了 isInstFetch() 函数,那么你需要包含 <mem/packet.hh> 头文件。
(4)关于blockedPacket
void
SimpleMemobj::CPUSidePort::sendPacket(PacketPtr pkt)
{
panic_if(blockedPacket != nullptr, "Should never try to send if blocked!");
if (!sendTimingResp(pkt)) {
blockedPacket = pkt;
}
}
我觉得官方教程太简陋了,很多变量出现的有点没头脑也不介绍是从哪儿来的,对新手入门一点也不友好……比如,blockedPacket 在我自己写的CPUSidePort或者SimpleMemobj类中压根没有这个成员变量,它其实是继承来的,具体继承的是谁呢?不清楚……可能blockedPacket 是从 SimObject 类继承而来的,更多有关这些类的信息,可以尝试查看相关的文档或源代码
(5)关于输出
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
救命,我没看出来这个debug模式的输出为啥是这样的,迷了迷了,公开求解,有知道的小伙伴欢迎留言告知,非常感谢♥。
五、题外话记录:
(1)关于m5和gem5
Gem5模拟器通常由两个部分组成:m5端和gem5端。
m5 是一个用 C++ 编写的模拟实验室,它提供了用于模拟的硬件模型和模拟控制器。它的主要功能是提供一个可以模拟各种硬件的环境,用于测试和评估软件。m5 端通常包括一个模拟内存子系统、一个模拟处理器架构、一个模拟 I/O 子系统和一个模拟存储子系统等。
gem5 是一个用 C++ 和 Python 编写的模拟引擎,它用来运行模拟和生成性能指标。gem5 端通常包括一个模拟操作系统、一个模拟应用程序、一个模拟库函数和一个模拟驱动程序等。
m5 和 gem5 端是互补的,即 m5 端主要负责模拟硬件,而 gem5 端则主要负责模拟软件。因此,m5端和gem5端可以说是两个互补的部分,共同完成了Gem5模拟器的功能。
因此,如果你想同时调用 m5 端和 gem5 端,那么你需要使用 FS 模式。FS 模式(Full System Mode)是指 Gem5 模拟器运行在多线程模式下,同时模拟硬件和软件的模式。在 FS 模式下,Gem5 模拟器提供了一个完整的操作系统环境,用户可以在这个环境中运行应用程序,并通过硬件模型与模拟系统进行交互。
(2)关于memory system modes
memory system modes指的是gem5模拟器中的内存系统模式。gem5模拟器支持三种不同的内存系统模式:timing mode、atomic mode和functional mode。
-
atomic
模式:在这种模式下,所有内存访问都是原子的,也就是说,在一个内存访问操作完成之前,不会有其他内存访问操作,用于将模拟快速转发到感兴趣的区域。 -
timing
模式:在这种模式下,内存访问的时间是真实的,也就是说,内存访问的时间是基于真实的内存访问时间模拟的。最重要的内存系统模式,它模拟了真实系统中的内存访问时间,可以精确模拟出内存访问的时序关系。 -
functional
模式:在这种模式下,内存访问的结果是正确的,但是内存访问的时间是不真实的。它只模拟内存访问操作的功能,不考虑时序和原子性。也称为调试模式,用于从主机读取数据到模拟器内存。它大量用于系统调用仿真模式 -
detailed
模式:在这种模式下,内存访问的时间和结果都是真实的,也就是说,它提供了最细粒度的模拟。