系列文章 : [gem5] 從零開始的 gem5 學習筆記
關於 TimeBuffer 以及 Latch,參考連結 的文章已經寫的很詳盡了,
這邊簡單做些筆記。
TimeBuffer 是一個 Buffer,裡面總共會有 past + future + 1 個元素。
+1,代表的正是當下的資料在建構子裡面可以看到,這邊用了 C++ 的 placement new。也就是,當我們 new 一個 class 的時候,不是像 malloc 一樣在 heap section 裡面拿記憶體,而是在我們的 data 指標指向的記憶體裡面去 new class。
雖然 data 的型態是 char *, 但這裡實際上會用 placement new 的方式把這一個空間變成 class T 的陣列。
然後 index[i] 則是指向的 i 個 class T 元素。
{ gem5/src/cpu/timebuf.hh }
template <class T>
class TimeBuffer
{
protected:
int past;
int future;
unsigned size;
int _id;
char *data;
std::vector<char *> index;
unsigned base;
TimeBuffer(int p, int f)
: past(p), future(f), size(past + future + 1),
data(new char[size * sizeof(T)]), index(size), base(0)
{
assert(past >= 0 && future >= 0);
char *ptr = data;
for (unsigned i = 0; i < size; i++) {
index[i] = ptr;
std::memset(ptr, 0, sizeof(T));
new (ptr) T;
ptr += sizeof(T);
}
_id = -1;
}
protected:
//Calculate the index into this->index for element at position idx
//relative to now
inline int calculateVectorIndex(int idx) const
{
//Need more complex math here to calculate index.
valid(idx);
int vector_index = idx + base;
if (vector_index >= (int)size) {
vector_index -= size;
} else if (vector_index < 0) {
vector_index += size;
}
return vector_index;
}
advance 的時候,會將 base + 1。
例如說原本 base 為 0,那經過 advance 之後, base = 1
這代表, index 為 0 的資料,成為了過去的資料,而 index 為 1 的資料,從原本未來的資料變成現在的資料。
最後會把最舊的資料用 placement new 的方式給重新初始化。
{ src/cpu/timebuf.hh }
advance()
{
if (++base >= size)
base = 0;
int ptr = base + future;
if (ptr >= (int)size)
ptr -= size;
(reinterpret_cast<T *>(index[ptr]))->~T();
std::memset(index[ptr], 0, sizeof(T));
new (index[ptr]) T;
}
wire 的 index 對 TimeBuffer 而言,是相對於 base 而言的
所以可以看到當 wire 要取用 TimeBuffer 的資料的時候,會需要先用 calculateVectorIndex ,從 wire 的 index 值去計算出在 TimeBuffer 內部真正的索引值。
{ gem5/src/cpu/timebuf.hh }
T &operator*() const { return *buffer->access(index); }
T *operator->() const { return buffer->access(index); }
public:
T *access(int idx)
{
int vector_index = calculateVectorIndex(idx);
return reinterpret_cast<T *>(index[vector_index]);
}
而 Latch 是怎麼去使用 TimeBuffer 的呢 ? 這邊用 Latch<ForwardLineData> f1ToF2 為例子。
{ gem5/src/cpu/minor/pipeline.cc }
f1ToF2(cpu.name() + ".f1ToF2", "lines",
params.fetch1ToFetch2ForwardDelay),
{ gem5/src/cpu/minor/BaseMinorCPU.py }
fetch1ToFetch2ForwardDelay = Param.Cycles(
1, "Forward cycle delay from Fetch1 to Fetch2 (1 means next cycle)"
)
{ gem5/src/cpu/minor/buffers.hh }
public:
/** forward/backwardDelay specify the delay from input to output in each
* direction. These arguments *must* be >= 1 */
Latch(const std::string &name,
const std::string &data_name,
Cycles delay_ = Cycles(1),
bool report_backwards = false) :
delay(delay_),
buffer(name, data_name, delay_, 0, (report_backwards ? -delay_ : 0),
(report_backwards ? 0 : -delay_))
{ }
public:
MinorBuffer(const std::string &name,
const std::string &data_name,
int num_past, int num_future,
int report_left = -1, int report_right = -1) :
Named(name), TimeBuffer<ElemType>(num_past, num_future),
reportLeft(report_left), reportRight(report_right),
dataName(data_name)
{ }
從 BaseMinorCPU.py,我們可以知道預設的 num_past 參數值為 1。
於是 TimeBuffer 裡面的 char *data 其實只會有兩個 class T 的大小。( 這邊的 class T 為 class ForwardLineData )
這裡可以看到,f1ToF2.input() 會交給 fetch1 stage
而 f1ToF2.output() 會交給 fetch2 stage
{ gem5/src/cpu/minor/pipeline.cc }
fetch2(cpu.name() + ".fetch2", cpu, params,
f1ToF2.output(), eToF1.output(), f2ToF1.input(), f2ToD.input(),
decode.inputBuffer),
fetch1(cpu.name() + ".fetch1", cpu, params,
eToF1.output(), f1ToF2.input(), f2ToF1.output(), fetch2.inputBuffer),
當我們呼叫 f1ToF2.input() 的時候,
會去呼叫 TimeBuffer.getWire(0)。
代表 f1ToF2.input() 永遠指向,TimeBuffer 內相對於 base ,偏差為 0 的位子。
簡而言之就是永遠指向 base,永遠指向當下的資料。
當我們呼叫 f1ToF2.output() 的時候,
會去呼叫 TimeBuffer.getWire(-delay)。而這邊的 delay 用的是預設值 1,
會去呼叫 TimeBuffer.getWire(-1)。
代表 f1ToF2.output() 永遠指向,TimeBuffer 內相對於 base ,偏差為 -1 的位子。
簡而言之就是永遠指向過去的第一筆資料。
{ gem5/src/cpu/minor/buffers.hh }
class Input
{
public:
typename Buffer::wire inputWire;
public:
Input(typename Buffer::wire input_wire) :
inputWire(input_wire)
{ }
};
class Output
{
public:
typename Buffer::wire outputWire;
public:
Output(typename Buffer::wire output_wire) :
outputWire(output_wire)
{ }
};
{ gem5/src/cpu/timebuf.hh }
wire getWire(int idx)
{
valid(idx);
return wire(this, idx);
}
所以每當我們在 fetch1 向 Latch 丟任何一筆資料,都需要經過一拍之後
fetch2 才有辦法拿到 fetch1 給的資料。