深入理解 Gem5 之四

Gem5 中的内存系统

本文部分内容参考官方文档中关于内存系统的相关说明内存系统中创建 SimObjects

MemObjects

之前的 gem5 版本中,所有连接到内存系统的对象都派生于 MemObject 类。然而,在最新版本(v21.0.1.0)中,该类被删去了。

那么现在用什么类呢?猜测是 SimObject

Ports

在深入研究内存系统之前,我们应该首先理解 gem5 中的端口类 Port。因为所有在内存系统内的对象都要通过端口来建立连接,因而它们总是成对出现,这使得 gem5 的设计更加模块化。

memory system modes

Port 类实现了三种不同的内存系统模式:时序(timing)、原子(Atomic)和功能(functional),最重要的模式是时序模式。时序模式是产生正确仿真结果的唯一模式。其他模式仅在特殊情况下使用:

  1. Atomic mode 原子模式常用于快进到感兴趣的模拟区域,以及预热缓存,这种模式假设在内存系统中不会产生任何事件。相反,所有的内存请求都通过一个长调用链执行。除非它将在快进或模拟器预热期间使用,否则不需要实现对内存对象的原子访问。
  2. Functional mode 功能模式更适合描述为调试模式。功能模式用于从 host 读取数据到模拟器内存等操作。它在 Syscall Emulation(SE) 模式中被大量使用。例如,函数模式使用 process.cmd 从 host 中加载二进制文件,这样模拟系统就可以访问它。不论数据在何处,函数的读操作总能返回最新的数据,而其写操作中需要更新所有可能的有效数据(比如多个有效的缓存块中)。

Port

Port 类(端口)是 SimObject 之间的交互接口。在 gem5 中,Port 类是所有交互接口类(包括网络连接以及硬件模块端口连接等)的父类,其地位可见一斑。

1
2
3
4
5
6
7
8
9
10
11
12
class Port {
private:
const std::string portName;
const PortID id;
Port *_peer;
bool _connected;
protected:
class UnboundPortException {};
public:
Port(const std::string& _name, PortID _id)
: portName(_name), id(_id), _peer(nullptr), _connected(false)
};

portName 是端口的描述名,id 类型为 typename int16_t PortID,用于在 vector 中区分并识别端口,当 id 为负数时,指示端口不在 vector 中。_peer 指向与该端口相连的端口,_connected 表示端口是否有一个端口与之相连。此外,Port 类中还定义了一个空类 UnboundPortException,用于在程序发现未绑定端口时 throw 出特定的错误。

成对的两个端口如何进行绑定与解绑呢?很简单,只需要改变 _peer 指针就行:

1
2
3
4
5
6
7
8
9
10
11
/** Attach to a peer port. */
virtual void bind(Port &peer) {
_peer = &peer;
_connected = true;
}

/** Dettach from a peer port. */
virtual void unbind() {
_peer = nullptr;
_connected = false;
}

takeOverFrom() 函数也提供了快速交换两个端口之间连接的方法。它将原本与 old 绑定的端口绑定。

1
2
3
4
5
6
7
8
9
void takeOverFrom(Port *old) {
Port &peer = old->getPeer();
// Disconnect the original binding.
old->unbind();
peer.unbind();
// Connect the new binding.
peer.bind(*this);
bind(peer);
}

Master-vs-Slave ? Request-vs-Response !

Port 类分别派生出了两种不同的子类,RequestPort 类和 ResponsePort 类。其中,RequsetPort 类是请求端口,用于发送接受请求,RespondPort 则用于发送应答。在之前的版本中,RequestPort 被称为 MasterPort,而 RespondPort 被称为 SlavePort,且官方文档中仍旧使用这些名称,为方便统一,下文使用 RequestPort 和 ResponsePort。一个主模块,如 CPU,通常有一个或多个 RequestPort 实例,从 Cache 中请求想要的数据。从模块,例如内存,具有一个或多个 ResponsePort,响应请求发回对应的数据。一个互连组件,例如缓存、网桥或总线,通常同时具有 RequestPort 和 ResponsePort 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class RequestPort: public Port, public AtomicRequestProtocol,
public TimingRequestProtocol, public FunctionalRequestProtocol {
friend class ResponsePort;
private:
ResponsePort *_responsePort;
protected:
SimObject &owner;
}

class ResponsePort : public Port, public AtomicResponseProtocol,
public TimingResponseProtocol, public FunctionalResponseProtocol {
friend class RequestPort;
private:
RequestPort* _requestPort;
bool defaultBackdoorWarned;
protected:
SimObject& owner;
}

以 RequestPort 类为例,RequestPort 类中包含了指向应答端口的指针 _responsePort 和拥有该请求端口的 SimObject。它还继承了三个不同级别的传输协议类:AtomicRequestProtocol、TimingRequestProtocol 和 FunctionalRequestProtocol。除了发送数据包的基本功能外,它还可以更改接收范围和侦听(snoop)端口的功能。

时序传输数据流程

时序模式下传输数据在 gem5 中非常常见,因此我们先来了解一下时序模式下的数据传输实现。gem5 中的数据传输,都是靠 Packet 类来完成。因此不论是 send 还是 recv 函数,都需要传递 Packet 类指针:PacketPtr。

当主模块需要下游传来数据时,会通过 RequestPort 调用 sendTimingReq(pkt) 发送请求, pkt 是 Packet 的指针,内含有请求数据、应执行的指令、状态等。然而实际上 sendTimingReq(pkt) 的实现就是调用 peer->recvTimingReq(pkt) 并返回该函数的返回值:

1
2
3
4
5
6
7
8
9
10
11
12
bool RequestPort::sendTimingReq(PacketPtr pkt) {
try {
return TimingRequestProtocol::sendReq(_responsePort, pkt);
} catch (UnboundPortException) {
reportUnbound();
}
}

bool TimingRequestProtocol::sendReq(TimingResponseProtocol *peer, PacketPtr pkt) {
assert(pkt->isRequest());
return peer->recvTimingReq(pkt);
}

于是,PacketPtr 通过函数参数的方式传给了 RespondPort,而 RespondPort 事实上是处于从模块中,所以现在数据就移动到了下游从模块。注意,recvTimingReq() 的返回值给最终会 return 到 sendTimingReq 函数中。因此主模块可以知晓请求是否被从模块接收,true 表示该数据包已被收到。false 意味着从模块目前无法接收请求,必须在未来的某个时刻重试。

若 RespondPort 成功接收了 PacketPtr,此时主模块会继续自己的运行,从模块则会处理 Packet,双方都不会被阻塞。当从模块完成处理后,需向主模块发送响应:调用 sendTimingResp(pkt)(此时 pkt 是与请求相同的指针,但它现在指向一个响应包)。类似的是,sendTimingResp(pkt) 内部实现还是直接调用 peer->recvTimingResp(pkt) 并返回该函数的返回值。若 master 的 recvTimingResp() 函数返回 true,表明 master 已经收到应答,如此一来,该请求的交互就完成了(见下图)。

1
2
3
4
5
6
7
8
9
10
11
12
bool sendTimingResp(PacketPtr pkt) {
try {
return TimingResponseProtocol::sendResp(_requestPort, pkt);
} catch (UnboundPortException) {
reportUnbound();
}
}

bool TimingResponseProtocol::sendResp(TimingRequestProtocol *peer, PacketPtr pkt) {
assert(pkt->isResponse());
return peer->recvTimingResp(pkt);
}

之前说的都是非常顺利的情况,若出现从模块因某些原因暂无法接收。那么 recvTimingReq() 的返回值为 false,于是主模块会得知从模块正在忙碌,当从模块可以接受数据后,会调用 sendReqRetry() 函数来通知主模块再次发送请求。而主模块也只有在等到 recvReqRetry() 执行后,才能再次调用 sendTimingReq() 函数来发送请求。当然,第二次发送请求失败也是有可能的,因此上述过程可能会发生很多次。注意:主模块负责保存失败的 PacketPtr,而不是从模块,从模块不保留失败的 PacketPtr。

当然,也可能出现主模块因忙碌而无法接收应答的情况,从模块通过 sendTimingResp 的返回值可知应答未被主模块接收,那么从模块需要等待主模块调用 sendRespRetry() 函数,然后才能再次发送应答。

最后,补上官方文档中关于时序数据流控制的说明:

sendTiming() 函数返回 false 时,相同的 Packet 就不应当再次发送,直到 recvRetry() 函数被调用时,才可以再次调用 sendTiming() 函数,然而此时也不必一定要重发之前的 Packet,可以发送一个优先级更高的 Packet。Once sendTiming() returns true, the packet may still not be able to make it to its destination. For packets that require a response (i.e. pkt->needsResponse() is true), any memory object can refuse to acknowledge the packet by changing its result to Nacked and sending it back to its source. However, if it is a response packet, this can not be done. The true/false return is intended to be used for local flow control, while nacking is for global flow control. In both cases a response can not be nacked.

Packet

简介

如上文所述,Packet 类通常表示内存对象之间传输的数据。因此,单个 Request 从请求者一直传输到最终目的地,然后再返回,可能是由几个不同 Packet 在这个过程中传输的。

各种类中的 accessor 函数可以访问并使用 Packet 类中的信息,并进而验证读入的数据是否有效。比如,在 SimpleCache 的例子中,accessFunctional() 函数内使用了 Packet 类的地址、块大小、读/写操作等信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在cache中根据块的首地址 找出对应的块做读写操作
bool SimpleCache::accessFunctional(PacketPtr pkt) {
Addr block_addr = pkt->getBlockAddr(blockSize);
auto it = cacheStore.find(block_addr);
if (it != cacheStore.end()) {
if (pkt->isWrite()) {
// Write the data into the block in the cache
pkt->writeDataToBlock(it->second, blockSize);
} else if (pkt->isRead()) {
// Read the data out of the cache block into the packet
pkt->setDataFromBlock(it->second, blockSize);
} else {
panic("Unknown packet type!");
}
return true;
}
return false;
}

Packet 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Packet : public Printable {
private:
Flags flags; // 标志位
PacketDataPtr data; // 指向被传输数据的指针
public:
MemCmd cmd; // 包内要求内存对象执行的命令
const PacketId id; // 包 ID
RequestPtr req; // 指向原本请求的指针
Addr addr; // 请求的地址,虚拟地址或物理地址
unsigned size; // 请求的大小
uint32_t headerDelay; // 从看到包到发送报头的额外延迟。这个延迟是用来传递交叉转发延迟到相邻的对象(例如缓存),实际上使数据包等待。
uint32_t snoopDelay; // 在向内存系统发送请求之前,跟踪由向上窥探引起的额外延迟。这被相干交叉条用来解释额外的请求延迟。
uint32_t payloadDelay; // 从看到数据包到负载结束的额外流水线延迟。这包括报头延迟。与报头延迟类似,这是用来弥补交叉条不会使包等待的事实。

SenderState *senderState; // 此数据包的发送者状态。
}

其中 Printable 类是为更方便调试、打印信息而存在的抽象类。

而 Flags 类描述了 Packet 对象内具体状态信息,包括侦听、拷贝、应答、共享、有效位等:

符号 描述
COPY_FLAGS Flags to transfer across when copying a packet
RESPONDER_FLAGS used to create reponse packets
HAS_SHARERS packet have sharers (which means it should not be considered writable) or not.
EXPRESS_SNOOP Special timing-mode atomic snoop for multi-level coherence.
RESPONDER_HAD_WRITABLE Allow a responding cache to inform the cache hierarchy that it had a writable copy before responding.
CACHE_RESPONDING Snoop co-ordination flag to indicate that a cache is responding to a snoop.
WRITE_THROUGH The writeback/writeclean
SATISFIED Response co-ordination flag for cache maintenance
FAILS_TRANSACTION Indicates that this packet/request has returned from the cache hierarchy in a failed transaction.
FROM_TRANSACTION Indicates that this packet/request originates in the CPU executing in transactional mode
VALID_ADDR addr valid fields
VALID_SIZE size valid fields
STATIC_DATA The data pointers to a value that shouldn’t be freed when the packet is destroyed.
DYNAMIC_DATA The data pointers to a value that should be freed when the packet is destroyed.
SUPPRESS_FUNC_ERROR suppress the error if this packet encounters a functional access failure.
BLOCK_CACHED Signal block present to squash prefetch and cache evict packets through express snoop flag

Memcmd

MemCmd 类定义了与命令相关的属性和其他数据。MemCmd 类中有所有关于cache/memory 的操作和属性。关于cache的命令操作,可分为以下几大类:

  • 无效
  • 读取
  • 预取
  • 写入
  • 清除
  • 升级
  • 同步

这些命令操作也会配上数据包的属性,且命令与数据通常有固定搭配,不完全举例如下:

命令 属性字符 应答命令 描述
InvalidCmd - InvalidCmd(即不应答) 无效命令
ReadReq IsRead, IsRequest, NeedsResponse ReadResp 由非缓存代理(例如 CPU 或设备)发出的读取,对对齐没有限制
ReadResp IsRead, IsResponse, HasData InvalidCmd 从 requester 到 responder 的数据流
ReadRespWithInvalidate IsRead, IsResponse, HasData, IsInvalidate InvalidCmd 是否是要升级的数据
WriteReq IsWrite, NeedsWritable, IsRequest, NeedsResponse, HasData WriteResp
WriteResp IsWrite, IsResponse InvalidCmd
WriteCompleteResp IsWrite, IsResponse InvalidCmd
WritebackDirty IsWrite, IsRequest, IsEviction, HasData, FromCache InvalidCmd
WritebackClean IsWrite, IsRequest, IsEviction, HasData, FromCache InvalidCmd
WriteClean IsWrite, IsRequest, HasData, FromCache InvalidCmd
CleanEvict IsRequest, IsEviction, FromCache InvalidCmd
SoftPFReq IsRead, IsRequest, IsSWPrefetch, NeedsResponse SoftPFResp
SoftPFExReq IsRead, NeedsWritable, IsInvalidate, IsRequest, IsSWPrefetch, NeedsResponse SoftPFResp
HardPFReq IsRead, IsRequest, IsHWPrefetch, NeedsResponse, FromCache HardPFResp
SoftPFResp IsRead, IsResponse, IsHWPrefetch, HasData InvalidCmd
HardPFResp IsRead, IsResponse, IsHWPrefetch, HasData InvalidCmd
WriteLineReq IsWrite, NeedsWritable, IsRequest, NeedsResponse, HasData WriteResp
UpgradeReq IsInvalidate, NeedsWritable, IsUpgrade, IsRequest, NeedsResponse, FromCache UpgradeResp
SCUpgradeReq IsInvalidate, NeedsWritable, IsUpgrade, IsLlsc, IsRequest, NeedsResponse, FromCache UpgradeResp IsUpgrade, IsResponse
SCUpgradeFailReq sRead, NeedsWritable, IsInvalidate, IsLlsc, IsRequest, NeedsResponse, FromCache UpgradeFailResp
UpgradeFailResp IsRead, IsResponse, HasData InvalidCmd
ReadExReq IsRead, NeedsWritable, IsInvalidate, IsRequest, NeedsResponse, FromCache ReadExResp
ReadExResp IsRead, IsResponse, HasData InvalidCmd
ReadCleanReq IsRead, IsRequest, NeedsResponse, FromCache ReadResp
ReadSharedReq IsRead, IsRequest, NeedsResponse, FromCache ReadResp
LoadLockedReq IsRead, IsLlsc, IsRequest, NeedsResponse
StoreCondReq sWrite, NeedsWritable, IsLlsc, IsRequest, NeedsResponse, HasData
StoreCondFailReq IsWrite, NeedsWritable, IsLlsc, IsRequest, NeedsResponse, HasData
StoreCondResp IsWrite, IsLlsc, IsResponse
SwapReq IsRead, IsWrite, NeedsWritable, IsRequest, HasData, NeedsResponse
SwapResp IsRead, IsWrite, IsResponse, HasData
MemFenceReq
MemSyncReq
MemSyncResp
MemFenceResp
CleanSharedReq
CleanSharedResp
CleanInvalidReq
CleanInvalidResp

gem5 中为方便两者相连,使用了很有技巧的编程手段。定义 MemCmd::CommandInfo 结构体如下,位域中通过位图表示的多个属性,在初始化时命令和属性就会被捆绑起来,保存在静态变量数组 commandInfo[] 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct CommandInfo {
/// Set of attribute flags.
const std::bitset<NUM_COMMAND_ATTRIBUTES> attributes;
/// Corresponding response for requests; InvalidCmd if no
/// response is applicable.
const Command response;
/// String representation (for printing)
const std::string str;

CommandInfo(std::initializer_list<Attribute> attrs,
Command _response, const std::string &_str) :
attributes(buildAttributes(attrs)), response(_response), str(_str)
{}
};

使用 testCmdAttrib 函数和静态类数组 commandInfo(包含了所有命令),就可以简洁地实现测试标志位的函数,如 isRead()

1
2
3
4
5
6
7
static const CommandInfo commandInfo[];

bool testCmdAttrib(MemCmd::Attribute attrib) const {
return commandInfo[cmd].attributes[attrib] != 0;
}

bool isRead() const { return testCmdAttrib(IsRead); }

SenderState 类描述 Packet 的发送者状态。对于看到该 PacketSimObject 对象而言,SenderState 类可以用于保存与 Packet 相关的状态(例如,MSHR)。

MSHR(Miss Status and handling Register) 保存并处理缓存丢失所需的所有信息,包括要请求的目标列表。

指向 SenderState 类的指针会在应答 Packet 的函数中被返回,如此一来,SimObject 对象可以迅速查看 Packet 中的状态位,并进行相应的处理(见 findNextSenderState() 函数)。 SenderState 类以链表的形式相串起来:

1
2
3
4
5
struct SenderState {
SenderState* predecessor;
SenderState() : predecessor(NULL) {}
virtual ~SenderState() {}
};

在响应该 Packet 时,会返回一个 SenderState* 类型的指针,以便 SimObject 对象可以快速查找处理它所需的状态。要遍历发送者组成的链表,返回第一个符合类型T的实例,需使用 findNextSenderState() 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 遍历发送者组成的链表,返回第一个符合类型T的实例
* @return The topmost state of type T
*/
template <typename T>
T * findNextSenderState() const {
T *t = NULL;
SenderState* sender_state = senderState;
while (t == NULL && sender_state != NULL) {
t = dynamic_cast<T*>(sender_state);
sender_state = sender_state->predecessor;
}
return t;
}

有时,为处理特殊发送设备的状态,程序员也可以从该类中派生出相对应的子类。由于多个 SimObject 对象都可以从自己的视角出发来添加新的 SenderState,只要在响应返回时,能恢复之前的 SenderState 对象即可。因此,在修改 Packet 类中的 SenderState 字段之前,应该始终维护 SenderState 链表。

Packet 功能

通常来说,Packet 类中包含了以下内容,可被函数使用:

  • 地址。 通过 getAddr() 函数获得。该地址将用于将 Packet 路由到其目的地(若未明确设置目的地)并在目标处处理 Packet 的地址。通常,它是发起请求对象的物理地址,某些情况下也可能是虚拟地址:在执行地址转换之前访问虚拟 Cache。但有时也可能是需要获取的数据地址:例如,在 Cache 未命中时,Packet 地址可能是要获取的块的地址,而非请求地址。
  • 请求或包的大小。 通过 getSize() 获得。请求所占的空间大小
  • 指向 Packet 中数据的指针。在不同层次结构中,数据可能是不同的,因此在设计上它位于 Packet 对象,而不是 request。
    • dataStatic() dataDynamic() 函数设置的数据,在 Packet 对象被 free 时,其内的数据分别应:不被 free、不使用 delete [] 进行 free。
    • allocate() 函数分配空间时,数据会在 Packet 被释放时 free
    • 通过 getPtr() 获得指针
    • 使用 get() 函数获取,set() 函数设置
  • 状态 包括以下几种:Success, BadAddress, Not Acknowleged, and Unknown.
  • List of command attributes 需要对 Packet 施加的命令和属性,由 MemCmd 维护。注意:状态字段和命令属性中的数据有一些重叠。这在很大程度上是为了使包在打包时可以很容易地重新初始化,或者在原子访问或函数访问时很容易重用。
  • Pointer to SenderState 携带特定的发送设备的状态。在包的响应中返回一个指向该状态的指针,以便发送方可以快速查找处理它所需的状态。
  • Pointer to CoherenceState 用于保存 Coherence 一致性相关的状态。
  • Pointer to request 指向请求的指针

Request

Request 对象封装了 CPU 或 I/O 设备发出的原始请求。Request 的参数在整个事务中是持久的。因此对于一给定的 Request,其字段最多只需写入一次。但也有一些构造函数和 update 方法允许在不同时间(或根本不)写入对象的某些字段。用户可通过 accessor() 函数获取 Request 字段的读取权限,同时也可验证正在读取的字段中的数据是否有效。注意,Request 中的字段通常不适用于真实系统中的设备,通常用于统计或调试,不能作为真实的系统架构。

Request 对象的字段包括了:

  • Virtual Address 虚拟地址。当该请求直接表示为物理地址时该字段无效(如 DMA I/O 设备发出的请求)
  • Physical Address 物理地址
  • Data Size 数据大小
  • Time the request was created 创建时间
  • The ID of the CPU/thread that caused this request. 创建该请求的 CPU 或线程 ID
  • The PC that caused this request 产生该请求的指令 PC 值。若不是由 CPU 发送的,那么该字段无效

Request 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
class Request {
private:
// The physical address of the request.
Addr _paddr = 0;

// The virtual address of the request.
Addr _vaddr = MaxAddr;

// The size of the request. Always valid as long as vir/phy address fields is valid.
unsigned _size = 0;

/** Byte-enable mask for writes. */
std::vector<bool> _byteEnable;

// The requestor ID which is unique in the system for all ports
// that are capable of issuing a transaction
RequestorID _requestorId = invldRequestorId;

/** Flag structure for the request. */
Flags _flags;

/** Flags that control how downstream cache system maintains coherence*/
CacheCoherenceFlags _cacheCoherenceFlags;

/** Private flags for field validity checking. */
PrivateFlags privateFlags;

// The time this request was started. Used to calculate latencies.
Tick _time = MaxTick;

// The task id associated with this request
uint32_t _taskId = context_switch_task_id::Unknown;

/**
* The stream ID uniquely identifies a device behind the
* SMMU/IOMMU Each transaction arriving at the SMMU/IOMMU is
* associated with exactly one stream ID.
*/
uint32_t _streamId = 0;

/**
* The substream ID identifies an "execution context" within a
* device behind an SMMU/IOMMU. It's intended to map 1-to-1 to
* PCIe PASID (Process Address Space ID). The presence of a
* substream ID is optional.
*/
uint32_t _substreamId = 0;

/**
* Extra data for the request, such as the return value of
* store conditional or the compare value for a CAS. */
uint64_t _extraData = 0;

/** The context ID (for statistics, locks, and wakeups). */
ContextID _contextId = InvalidContextID;

/** program counter of initiating access; for tracing/debugging */
Addr _pc = MaxAddr;

/** Sequence number of the instruction that creates the request */
InstSeqNum _reqInstSeqNum = 0;

/** A pointer to an atomic operation */
AtomicOpFunctorPtr atomicOpFunctor = nullptr;

LocalAccessor _localAccessor;

/** The instruction count at the time this request is created */
Counter _instCount = 0;
};


Request(Addr vaddr, unsigned size, Flags flags,
RequestorID id, Addr pc, ContextID cid,
AtomicOpFunctorPtr atomic_op=nullptr)
{
setVirt(vaddr, size, flags, id, pc, std::move(atomic_op));
setContext(cid);
_byteEnable = std::vector<bool>(size, true);
}


void
setVirt(Addr vaddr, unsigned size, Flags flags, RequestorID id, Addr pc,
AtomicOpFunctorPtr amo_op=nullptr)
{
_vaddr = vaddr;
_size = size;
_requestorId = id;
_pc = pc;
_time = curTick();

_flags.clear(~STICKY_FLAGS);
_flags.set(flags);
privateFlags.clear(~STICKY_PRIVATE_FLAGS);
privateFlags.set(VALID_VADDR|VALID_SIZE|VALID_PC);
depth = 0;
accessDelta = 0;
translateDelta = 0;
atomicOpFunctor = std::move(amo_op);
_localAccessor = nullptr;
}

void
setContext(ContextID context_id)
{
_contextId = context_id;
privateFlags.set(VALID_CONTEXT_ID);
}

深入理解 Gem5 之四
https://dingfen.github.io/2022/04/02/2022-4-2-gem5-4/
作者
Bill Ding
发布于
2022年4月2日
更新于
2024年4月9日
许可协议