0%

EDA 仿真之 Memory

Memory

Memory = 存储 + 访问逻辑

存储

  • 在仿真里,memory 本质上就是一个数组(Array)或者向量(Vector),每个元素对应一个存储单元(bit/byte/word)。
  • 例:一个 8 位 × 1024 深度的 RAM,可以在仿真里用 uint8_t mem[1024]; 表示。

访问逻辑

  • 读(Read):根据地址返回对应的数据。
  • 写(Write):根据地址和写使能信号,将数据写入存储单元。
  • 可能涉及 时序:同步(clock 边沿写入)或异步(立即生效)。

时序和延迟

  • 在硬件里,memory 访问不是瞬间的:存在 读延迟、写延迟。
  • 仿真时,可以用 延时事件 或 clock 边沿触发 来模拟。

仿真代码

功能说明

  • 多端口读写:支持同时多个端口访问 memory
  • 写冲突仲裁:写优先策略或延迟写,可扩展读优先 / 轮询
  • 读延迟 pipeline:延迟由 read_delay 控制
  • Burst / wrap-around:访问超出末尾自动回绕
  • 简单 Cache/Tag:模拟命中 / 未命中
  • 异步端口:不同端口调用 read/write 可在不同 tick,模拟异步时钟
  • 随机 bit flip / SEU:1% 概率错误注入
  • 统计与功耗估算基础:记录读写次数、命中数、平均延迟
EdaMemoryFull.cppview raw
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include <random>
#include <map>
#include <algorithm>
#include <fstream>

struct Event {
int time;
std::function<void()> action;
bool operator<(const Event& other) const { return time > other.time; }
};

class EdaMemoryFull {
std::vector<uint8_t> mem;
std::priority_queue<Event> event_queue;
int sim_time = 0;
int read_delay;
std::mt19937 gen;

int cache_size;
struct CacheLine { bool valid=false; int tag=-1; };
std::vector<CacheLine> cache;

int max_bus_access_per_cycle = 2;
int current_cycle_access = 0;

// 统计信息
std::map<int,int> read_count, write_count, read_hits, write_hits;
std::map<int,int> read_delay_total;
int dynamic_power = 0; // 动态功耗
int static_power; // 静态功耗
std::vector<int> dynamic_power_per_cycle; // 每周期动态功耗

// 动态功耗公式(单位:动态功耗单位 = 每bit切换一次算1)
//
// P_dyn = α * C * V^2 * f
// 其中,α 开关活动因子(switching activity)
// C 负载电容
// V 电源电压
// f 时钟频率
// 简化为每次 bit 改变增加一个单位动态功耗
//
// 静态功耗公式(单位:静态功耗单位 = 每8bit存储单元算1)
//
// P_static = I_leak * V * N
// 简化为每8bit存储单元增加1单位静态功耗
//
// 总功耗
//
// P_total = P_dyn + P_static

public:
enum WritePriority { WRITE_FIRST, READ_FIRST, ROUND_ROBIN } write_prio;

EdaMemoryFull(size_t size, int delay, int c_size, WritePriority prio=WRITE_FIRST)
: mem(size,0), read_delay(delay), gen(std::random_device{}()),
cache_size(c_size), cache(c_size), static_power(size/8), write_prio(prio)
{
std::uniform_int_distribution<> dis(0,255);
for(auto &v: mem) v = dis(gen);
}

void write(int port,int addr,const std::vector<uint8_t>& data){
write_count[port]++;
if(current_cycle_access>=max_bus_access_per_cycle){
event_queue.push({sim_time+1,[this,port,addr,data](){ write(port,addr,data); }});
return;
}
current_cycle_access++;
if(write_prio==WRITE_FIRST){
apply_write(addr,data);
} else {
event_queue.push({sim_time+1,[this,addr,data](){ apply_write(addr,data); }});
}
update_cache(port, addr,data,true);
}

void read(int port,int addr,size_t length,std::function<void(std::vector<uint8_t>)> callback){
read_count[port]++;
if(current_cycle_access>=max_bus_access_per_cycle){
event_queue.push({sim_time+1,[this,port,addr,length,callback](){ read(port,addr,length,callback); }});
return;
}
current_cycle_access++;
int trigger_time = sim_time+read_delay;
bool hit = check_cache(port, addr,length);
if(hit) read_hits[port]++;
event_queue.push({trigger_time,[this,addr,length,callback,port,trigger_time]() {
std::vector<uint8_t> data;
for(size_t i=0;i<length;i++){
uint8_t val = mem[(addr+i)%mem.size()];
if(random_bit_flip()) val ^= (1<<(gen()%8));
dynamic_power += count_bit_changes(val, mem[(addr+i)%mem.size()]);
data.push_back(val);
}
read_delay_total[port] += (trigger_time - sim_time);
callback(data);
}});
}

void tick(){
sim_time++;
current_cycle_access = 0;
int cycle_dyn_power = 0;

while(!event_queue.empty() && event_queue.top().time <= sim_time){
auto e = event_queue.top(); event_queue.pop();
int before = dynamic_power;
e.action();
cycle_dyn_power += (dynamic_power - before);
}

dynamic_power_per_cycle.push_back(cycle_dyn_power);

// ASCII 可视化每周期动态功耗
int scale = 50;
int bar_len = *std::max_element(dynamic_power_per_cycle.begin(), dynamic_power_per_cycle.end())>0 ?
cycle_dyn_power*scale/(*std::max_element(dynamic_power_per_cycle.begin(), dynamic_power_per_cycle.end())) : 0;
std::cout << "Cycle " << sim_time << " dyn power: " << cycle_dyn_power
<< " total power: " << dynamic_power + static_power << " ";
for(int i=0;i<bar_len;i++) std::cout<<"#";
std::cout << std::endl;
}

void print_stats() const {
std::cout<<"Simulation stats:\n";
for(auto& [port,cnt]: read_count)
std::cout<<"Port "<<port<<" read count: "<<cnt
<<", hits: "<<read_hits.at(port)
<<", avg delay: "<<(cnt?read_delay_total.at(port)/cnt:0)<<"\n";
for(auto& [port,cnt]: write_count)
std::cout<<"Port "<<port<<" write count: "<<cnt
<<", hits: "<<write_hits.at(port)<<"\n";
std::cout<<"Dynamic power units: "<<dynamic_power<<"\n";
std::cout<<"Static power units: "<<static_power<<"\n";
std::cout<<"Total power units: "<<dynamic_power + static_power<<"\n";
}

void export_power_csv(const std::string &filename) const {
std::ofstream ofs(filename);
if(!ofs.is_open()) {
std::cerr << "Failed to open file: " << filename << std::endl;
return;
}
ofs << "Cycle,DynamicPower,StaticPower,TotalPower\n";
for(size_t i=0;i<dynamic_power_per_cycle.size();i++){
int dyn = dynamic_power_per_cycle[i];
int total = dyn + static_power;
ofs << (i+1) << "," << dyn << "," << static_power << "," << total << "\n";
}
ofs.close();
std::cout << "Power data exported to " << filename << std::endl;
}

private:
bool random_bit_flip(){
std::uniform_real_distribution<> dis(0.0,1.0);
return dis(gen)<0.01;
}

int count_bit_changes(uint8_t a,uint8_t b){
uint8_t diff = a^b;
int count=0;
while(diff){ count+=diff&1; diff>>=1; }
return count;
}

bool check_cache(int port, int addr,size_t length){
int line = addr % cache_size;
int tag = addr / cache_size;
return cache[line].valid && cache[line].tag==tag;
}

void update_cache(int port, int addr,const std::vector<uint8_t>& data,bool write=false){
int line = addr % cache_size;
int tag = addr / cache_size;
cache[line].valid=true;
cache[line].tag=tag;
if(write) write_hits[port]++;
}

void apply_write(int addr,const std::vector<uint8_t>& data){
for(size_t i=0;i<data.size();i++) mem[(addr+i)%mem.size()]=data[i];
}
};

// 示例主程序
int main(){
EdaMemoryFull mem(1024,2,16,EdaMemoryFull::WRITE_FIRST);

mem.write(0,10,{42,43,44});
mem.write(1,11,{99});
mem.read(0,10,3,[](std::vector<uint8_t> data){
std::cout<<"Port0 read burst: ";
for(auto v:data) std::cout<<(int)v<<" ";
std::cout<<std::endl;
});
mem.read(1,11,1,[](std::vector<uint8_t> data){
std::cout<<"Port1 read: "<<(int)data[0]<<std::endl;
});

for(int i=0;i<10;i++) mem.tick();
// 导出功耗数据
mem.export_power_csv("memory_power.csv");

mem.print_stats();
return 0;
}

输出的 memory_power.csv 文件内容示例:

1
2
3
4
5
6
7
8
9
10
11
Cycle,DynamicPower,StaticPower,TotalPower
1,3,128,131
2,0,128,128
3,5,128,133
4,2,128,130
5,7,128,135
6,1,128,129
7,4,128,132
8,0,128,128
9,2,128,130
10,3,128,131

每列含义:

  • Cycle:周期号
  • DynamicPower:每周期动态功耗单位
  • StaticPower:静态功耗单位(固定)
  • TotalPower:总功耗单位

功耗分析

report_power.pyview raw
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
import pandas as pd
import matplotlib.pyplot as plt

# 读取 CSV
df = pd.read_csv('memory_power.csv')

# 找到动态功耗峰值周期
peak_cycle = df['DynamicPower'].idxmax() + 1
peak_value = df['DynamicPower'].max()

plt.figure(figsize=(12,6))

# 绘制条形背景(ASCII风格效果)
for i, val in enumerate(df['DynamicPower']):
bar_len = int(val / peak_value * 50) # 50字符最大长度
plt.text(i+1, 0, '#' * bar_len, fontsize=8, color='grey', va='bottom')

# 绘制曲线
plt.plot(df['Cycle'], df['DynamicPower'], label='Dynamic Power', marker='o', color='blue')
plt.plot(df['Cycle'], df['StaticPower'], label='Static Power', linestyle='--', color='orange')
plt.plot(df['Cycle'], df['TotalPower'], label='Total Power', linestyle='-.', color='green')

# 高亮峰值
plt.scatter(peak_cycle, peak_value, color='red', s=100, label='Peak Dynamic Power')

plt.title('Memory Power Simulation with Peak Highlight & ASCII-style Bars')
plt.xlabel('Cycle')
plt.ylabel('Power Units')
plt.grid(True, linestyle=':')
plt.legend()
plt.tight_layout()
plt.show()

burst/multi-port 总线冲突

在多端口系统中,尤其是在使用总线结构的系统中,总线冲突(Bus contention)是一个常见的问题。总线冲突通常发生在多个设备尝试同时访问总线上的同一资源时。这种情况可能会导致数据损坏、系统性能下降或甚至系统崩溃。下面是一些解决和缓解总线冲突的策略:

  1. 仲裁机制
    仲裁是解决总线冲突的一种常用方法。它通过一个仲裁器(Arbiter)来决定哪个设备可以访问总线。常见的仲裁策略有:

优先级仲裁:根据预先设定的优先级顺序决定哪个设备可以访问总线。

轮询仲裁:轮流让每个设备访问总线。

基于请求的仲裁(如请求共享(Request-for-Shared, RFS)和请求独占(Request-for-Exclusive, RFE)):设备首先请求对资源的访问,然后根据请求的类型(共享或独占)来决定访问权限。

  1. 分时复用
    通过时间分割(Time Division Multiplexing, TDM)或频率分割(Frequency Division Multiplexing, FDM),可以允许多个设备在不同的时间或频率上使用总线,从而减少冲突。例如,可以使用时分多路复用将总线的不同时间段分配给不同的设备。

  2. 编码和解码技术
    使用特殊的编码和解码技术,如霍纳编码(Hornar code)或格雷码(Gray code),可以减少在总线上传输数据时的错误,并帮助检测和纠正数据冲突。

  3. 总线锁定
    在访问总线期间,通过总线锁定机制确保没有其他设备可以访问总线。这可以通过在总线上设置一个锁定信号来实现,该信号在访问期间保持激活状态。

  4. 缓存和缓冲
    为每个设备提供局部缓存或缓冲机制,可以减少对总线的直接访问次数,从而降低冲突的可能性。当一个设备需要与总线上的另一个设备通信时,它可以先将数据写入自己的缓存,然后再由缓存同步到总线上。

  5. 使用更宽的总线
    增加总线的宽度可以允许在同一时间内传输更多的数据,从而减少对总线的需求,降低冲突的可能性。

实施步骤
评估系统需求:确定哪些类型的设备将使用总线,以及它们对带宽的需求。

选择仲裁策略:根据设备的优先级和带宽需求选择合适的仲裁策略。

设计硬件:根据选定的策略设计硬件,包括添加仲裁器、缓存和适当的控制逻辑。

测试和优化:实施后进行系统测试,根据测试结果调整策略或硬件设计。

通过上述方法,可以有效管理和减少多端口系统中的总线冲突问题,提高系统的稳定性和性能。

Cache Tag(缓存标记)

Cache Tag(缓存标记)是高速缓存(Cache)中的关键组成部分,用于存储数据在主存中的地址信息,以便快速定位数据位置。 ‌

核心功能
Tag字段存储了主存中数据的地址信息,当CPU访问主存时,首先通过Tag字段判断数据是否存在于Cache中。若存在,则直接从Cache读取;若不存在,则访问主存。 ‌

结构组成

  • ‌Tag‌:记录数据在主存的地址信息。
  • ‌Data‌:存储实际数据。
  • ‌Valid Bit‌:标记数据是否有效。
  • ‌Dir‌:目录信息,用于区分不同数据块。 ‌

应用场景

现代处理器通常采用多级Cache结构(如L1、L2、L3),其中Tag与Data共同构成Cache Line,用于快速访问和存储数据。例如,ARMv8-A架构的处理器包含独立的I-Cache和D-Cache,分别存储指令和数据。

Cache Tag 仿真代码

FIXME: 该代码会 coredump 。

cache_simulator.hview raw
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
#ifndef CACHE_SIMULATOR_H
#define CACHE_SIMULATOR_H

#include <vector>
#include <unordered_map>
#include <mutex>
#include <atomic>

// 缓存行结构体
struct CacheLine {
bool valid = false;
bool dirty = false;
uint32_t tag = 0;
uint64_t last_used = 0; // 用于LRU替换策略
std::vector<uint8_t> data;
};

// 端口访问请求结构体
struct PortRequest {
uint32_t port_id;
bool is_write;
uint32_t addr;
uint8_t* data_ptr;
size_t data_size;
};

class CacheSimulator {
public:
CacheSimulator(uint32_t line_size, uint32_t num_lines, uint32_t num_ports);

// 多端口访问接口
void process_request(const PortRequest& req);

// 缓存配置
void set_write_policy(bool write_back);
void set_replacement_policy(int policy); // 0:LRU, 1:FIFO, 2:Random

private:
// 内部缓存操作
bool access_cache(uint32_t port_id, uint32_t addr, bool is_write, uint8_t* data, size_t size);
void handle_miss(uint32_t port_id, uint32_t addr);
void evict_line(uint32_t set_idx, uint32_t way_idx);

// 多端口同步
std::atomic<uint64_t> global_counter_{0}; // 原子计数器
std::vector<std::mutex> port_locks_;

// 缓存结构
uint32_t line_size_;
uint32_t num_sets_;
std::vector<std::vector<CacheLine>> cache_;

// 策略配置
bool write_back_;
int replacement_policy_;
};
#endif
cache_simulator.cppview raw
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
#include "cache_simulator.h"
#include <algorithm>
#include <random>
#include <cstring>

CacheSimulator::CacheSimulator(uint32_t line_size, uint32_t num_lines, uint32_t num_ports)
: port_locks_(num_ports), line_size_(line_size) {
num_sets_ = num_lines; // 简单实现,可扩展为组相联
cache_.resize(num_sets_, std::vector<CacheLine>(1)); // 直接映射
}

void CacheSimulator::process_request(const PortRequest& req) {
std::lock_guard<std::mutex> lock(port_locks_[req.port_id]);
access_cache(req.port_id, req.addr, req.is_write, req.data_ptr, req.data_size);
}

bool CacheSimulator::access_cache(uint32_t port_id, uint32_t addr, bool is_write,
uint8_t* data, size_t size) {
uint32_t tag = addr / line_size_;
uint32_t set_idx = tag % num_sets_;

// 查找命中
for (auto& line : cache_[set_idx]) {
if (line.valid && line.tag == tag) {
line.last_used = ++global_counter_;
if (is_write) {
memcpy(line.data.data(), data, size);
line.dirty = true;
} else {
memcpy(data, line.data.data(), size);
}
return true;
}
}

// 未命中处理
handle_miss(port_id, addr);
return false;
}

void CacheSimulator::handle_miss(uint32_t port_id, uint32_t addr) {
uint32_t tag = addr / line_size_;
uint32_t set_idx = tag % num_sets_;

// 查找可替换行
auto& lines = cache_[set_idx];
auto victim = std::min_element(lines.begin(), lines.end(),
[](const CacheLine& a, const CacheLine& b) {
return a.last_used < b.last_used; // LRU策略
});

// 写回脏数据
if (write_back_ && victim->dirty) {
// 模拟写回主存操作
}

// 加载新数据
victim->valid = true;
victim->tag = tag;
victim->dirty = false;
victim->last_used = ++global_counter_;
// 模拟从主存加载数据
victim->data.resize(line_size_);
}
main.cppview raw
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
#include "cache_simulator.h"
#include <thread>
#include <iostream>

void port_thread(CacheSimulator& cache, uint32_t port_id) {
for (int i = 0; i < 1000; ++i) {
PortRequest req;
req.port_id = port_id;
req.is_write = (i % 3 == 0);
req.addr = rand() % 0xFFFF;
uint8_t data[64] = {0};
req.data_ptr = data;
req.data_size = sizeof(data);

cache.process_request(req);
}
}

int main() {
CacheSimulator cache(64, 1024, 4); // 64B行, 1024行, 4端口

std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back(port_thread, std::ref(cache), i);
}

for (auto& t : threads) {
t.join();
}

std::cout << "Cache simulation completed" << std::endl;
return 0;
}
Makefileview raw
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
# 编译器配置
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -O3 -pthread
LDFLAGS := -pthread

# 项目结构
SRC_DIR := .
BUILD_DIR := build
TARGET := $(BUILD_DIR)/cache_simulator

# 源文件列表
SRCS := $(wildcard $(SRC_DIR)/*.cpp)
OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS))
DEPS := $(OBJS:.o=.d)

# 默认目标
all: $(BUILD_DIR) $(TARGET)

# 创建构建目录
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)

# 主目标链接
$(TARGET): $(OBJS)
$(CXX) $(LDFLAGS) $^ -o $@

# 编译规则
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp
$(CXX) $(CXXFLAGS) -MMD -c $< -o $@

# 包含依赖关系
-include $(DEPS)

# 清理
clean:
rm -rf $(BUILD_DIR)

.PHONY: all clean