1 Excel CPU
作者链接:
https://github.com/InkboxSoftware/excelCPU
https://www.youtube.com/watch?v=MNRKi7Rum_c
最开始看到这个是觉得很神奇,用excel就可以模拟CPU,正好当时刚刚开始全面投入嵌入式,对寄存器这些也比较有兴趣。
遇到的困难主要是对excel的很多高级操作还是不熟悉,尤其是宏,搞了很久才知道是按F9运行整个程序。另外一个困难是我的PC上现在没有office,找一个D版的现在好像很困难,找了半天都没看到。只能在笔记本上运行。
运行的界面大概这样的:


可以看到,上面有fetch unit,control unit,MUX,ALU,REGISTER FILE等等。
这部分的解释如下:
在 CPU(中央处理器)内部,各个组件协同工作以执行指令和处理数据。以下是 **Fetch Unit(取指单元)、Control Unit(控制单元)、MUX(多路复用器)、ALU(算术逻辑单元)** 和 **Register File(寄存器文件)** 的核心功能及作用:
---
### **1. Fetch Unit(取指单元)**
- **功能**:
负责从 **内存(Memory)** 中读取下一条要执行的指令。
- **工作流程**:
1. 根据 **程序计数器(PC, Program Counter)** 中的地址,从内存或指令缓存(I-Cache)获取指令。
2. 将指令送入 **指令译码器(Instruction Decoder)**。
3. 更新 **PC**(通常指向下一条指令地址,分支/跳转指令除外)。
- **关键组件**:
- **PC(Program Counter)**:存储当前指令地址。
- **指令缓存(I-Cache)**:加速指令读取。---
### **2. Control Unit(控制单元)**
- **功能**:
解析指令并生成控制信号,协调 CPU 各部件的工作。
- **工作流程**:
1. 接收来自 Fetch Unit 的指令,通过 **指令译码器(Decoder)** 解析指令类型(如加法、加载、存储等)。
2. 生成控制信号(如 `RegWrite`、`ALUOp`、`MemRead` 等),控制数据流向和操作。
- **关键信号示例**:
- `RegWrite`:是否写入寄存器文件。
- `ALUSrc`:ALU 的第二个操作数来源(立即数 or 寄存器)。
- `Branch`:是否执行分支跳转。---
### **3. MUX(多路复用器,Multiplexer)**
- **功能**:
从多个输入信号中选择一个输出,类似于“数据开关”。
- **典型应用场景**:
- **选择 ALU 的输入**:
- 输入1:寄存器文件的数据。
- 输入2:立即数(来自指令)。
- 由 `ALUSrc` 控制信号决定选择哪个输入。
- **选择下一条指令地址**:
- 正常情况:`PC + 4`(顺序执行)。
- 分支情况:`PC + 偏移量`(跳转执行)。---
### **4. ALU(算术逻辑单元,Arithmetic Logic Unit)**
- **功能**:
执行所有算术和逻辑运算(如加减乘除、AND/OR/XOR 等)。
- **输入与输出**:
- **输入**:
- 操作数1:通常来自寄存器文件。
- 操作数2:来自寄存器文件或立即数(由 MUX 选择)。
- `ALUOp` 控制信号:指定运算类型(如加法、减法)。
- **输出**:
- 运算结果(写入寄存器或内存)。
- 标志位(如 `Zero`、`Overflow`,用于分支判断)。---
### **5. Register File(寄存器文件)**
- **功能**:
存储 CPU 当前正在使用的数据(高速、低延迟访问)。
- **组成**:
- 一组寄存器(如 32 个 32 位寄存器,MIPS 架构)。
- 支持 **同时读取两个寄存器**(用于 ALU 操作)和 **写入一个寄存器**。
- **关键控制信号**:
- `ReadReg1/ReadReg2`:指定要读取的寄存器编号。
- `WriteReg`:指定要写入的寄存器编号。
- `RegWrite`:是否允许写入数据。---
### **协同工作示例(一条加法指令)**
1. **Fetch Unit** 从内存读取指令 `ADD R1, R2, R3`(将 R2 + R3 存入 R1)。
2. **Control Unit** 解析指令,生成控制信号:
- `ALUSrc=0`(选择寄存器文件输入)。
- `ALUOp=ADD`。
- `RegWrite=1`(允许写入 R1)。
3. **Register File** 读取 R2 和 R3 的值,送入 ALU。
4. **ALU** 执行加法,输出结果。
5. **Register File** 将结果写入 R1。---
### **总结:CPU 数据流关键路径**
```
Fetch Unit → Control Unit → Register File → MUX → ALU → Memory/Register File
```
- **Fetch Unit**:取指令。
- **Control Unit**:发号施令。
- **MUX**:数据路由选择。
- **ALU**:干脏活累活(计算)。
- **Register File**:临时存储数据。这些组件共同构成 CPU 的 **经典五级流水线(Fetch-Decode-Execute-Memory-Writeback)**,是现代处理器的基础架构。
然后支持的指令如下:

好吧,内容还是挺多的,后面有时间还会再看的。。。
2 C++模拟版本1
其实如果不想用那个excel来弄,也是完全可以直接用C++来干。
下面就是一个支持4个寄存器,R1~R3,数组模拟内存,支持一些简单指令的CPU。
LOAD reg, addr // 把内存 addr 位置的值加载到 reg
STORE reg, addr // 把 reg 里的值存入 addr
ADD reg1, reg2 // reg1 = reg1 + reg2
SUB reg1, reg2 // reg1 = reg1 - reg2
JMP addr // 跳转到 addr
HALT // 结束程序
#include <iostream>
#include <vector>
#include <unordered_map>
#include <sstream>
using namespace std;
// CPU 结构体
struct SimpleCPU {
int registers[4] = {0}; // 4 个通用寄存器 R0-R3
vector<string> memory; // 指令存储
int pc = 0; // 程序计数器
// 解析指令并执行
void execute() {
while (pc < memory.size()) {
stringstream ss(memory[pc]);
string op;
ss >> op;
if (op == "LOAD") {
int reg, addr;
ss >> reg >> addr;
registers[reg] = stoi(memory[addr]);
}
else if (op == "STORE") {
int reg, addr;
ss >> reg >> addr;
memory[addr] = to_string(registers[reg]);
}
else if (op == "ADD") {
int r1, r2;
ss >> r1 >> r2;
registers[r1] += registers[r2];
}
else if (op == "SUB") {
int r1, r2;
ss >> r1 >> r2;
registers[r1] -= registers[r2];
}
else if (op == "JMP") {
int addr;
ss >> addr;
pc = addr;
continue; // 直接跳转
}
else if (op == "HALT") {
break; // 终止
}
else {
cout << "未知指令: " << op << endl;
}
pc++; // 继续下一条指令
}
}
};
int main() {
SimpleCPU cpu;
// 加载程序到内存
cpu.memory = {
"LOAD 0 10", // R0 = memory[10] (5)
"LOAD 1 11", // R1 = memory[11] (3)
"ADD 0 1", // R0 = R0 + R1 (5 + 3 = 8)
"STORE 0 12", // memory[12] = R0
"HALT" // 停止
};
// 初始化内存
cpu.memory.resize(20);
cpu.memory[10] = "5";
cpu.memory[11] = "3";
cout << "程序开始执行...\n";
cpu.execute();
cout << "程序结束!\n";
cout << "R0: " << cpu.registers[0] << endl;
cout << "内存[12]: " << cpu.memory[12] << endl; // 应该存 8
return 0;
}
3 C++模拟版本2
这个版本支持CPU 5级处理。
1️⃣ 取指 (IF: Instruction Fetch)
-
从 指令存储器 取出指令
2️⃣ 译码 (ID: Instruction Decode) -
解析指令,读取 寄存器
3️⃣ 执行 (EX: Execute) -
ALU 执行运算(加法、减法等)
4️⃣ 访存 (MEM: Memory Access) -
访问 数据存储器(LOAD、STORE)
5️⃣ 写回 (WB: Write Back)
上面的5个指令可以并行执行。代码如下:
#include <iostream>
#include <queue>
#include <vector>
#include <unordered_map>
using namespace std;
// 指令类型
enum class OpType { NOP, ADD, SUB, LOAD, STORE, HALT };
// 指令结构体
struct Instruction {
OpType type;
int dest, src1, src2, addr;
};
// CPU 模拟类
class PipelineCPU {
private:
int registers[4] = {0}; // 4 个通用寄存器 R0-R3
unordered_map<int, int> memory; // 模拟内存
queue<Instruction> pipeline[5]; // 5 级流水线队列
vector<Instruction> instructions; // 指令存储
int pc = 0;
bool running = true;
public:
// 加载指令
void loadProgram(vector<Instruction> instrs) {
instructions = instrs;
}
// 运行流水线
void run() {
while (running || !pipeline[4].empty()) {
writeBack();
memoryAccess();
execute();
decode();
fetch();
printState();
}
}
private:
// **阶段 1: 取指 (IF)**
void fetch() {
if (pc < instructions.size()) {
pipeline[0].push(instructions[pc++]);
}
}
// **阶段 2: 译码 (ID)**
void decode() {
if (!pipeline[0].empty()) {
Instruction instr = pipeline[0].front();
pipeline[0].pop();
pipeline[1].push(instr);
}
}
// **阶段 3: 执行 (EX)**
void execute() {
if (!pipeline[1].empty()) {
Instruction instr = pipeline[1].front();
pipeline[1].pop();
if (instr.type == OpType::ADD)
instr.dest = registers[instr.src1] + registers[instr.src2];
else if (instr.type == OpType::SUB)
instr.dest = registers[instr.src1] - registers[instr.src2];
pipeline[2].push(instr);
}
}
// **阶段 4: 访存 (MEM)**
void memoryAccess() {
if (!pipeline[2].empty()) {
Instruction instr = pipeline[2].front();
pipeline[2].pop();
if (instr.type == OpType::LOAD)
instr.dest = memory[instr.addr];
else if (instr.type == OpType::STORE)
memory[instr.addr] = registers[instr.dest];
pipeline[3].push(instr);
}
}
// **阶段 5: 写回 (WB)**
void writeBack() {
if (!pipeline[3].empty()) {
Instruction instr = pipeline[3].front();
pipeline[3].pop();
if (instr.type == OpType::ADD || instr.type == OpType::SUB || instr.type == OpType::LOAD)
registers[instr.dest] = instr.dest;
if (instr.type == OpType::HALT)
running = false;
pipeline[4].push(instr);
}
}
// 打印状态
void printState() {
cout << "PC: " << pc << " | ";
for (int i = 0; i < 4; i++)
cout << "R" << i << ": " << registers[i] << " | ";
cout << endl;
}
};
// **测试 CPU**
int main() {
PipelineCPU cpu;
vector<Instruction> program = {
{OpType::LOAD, 0, 0, 0, 100}, // R0 = memory[100] (5)
{OpType::LOAD, 1, 0, 0, 101}, // R1 = memory[101] (3)
{OpType::ADD, 2, 0, 1, 0}, // R2 = R0 + R1 (5 + 3 = 8)
{OpType::STORE, 2, 0, 0, 102}, // memory[102] = R2 (8)
{OpType::HALT, 0, 0, 0, 0} // 停机
};
cpu.loadProgram(program);
cpu.run();
return 0;
}
4 后记
最近真的精力有限,只能简单看看了。
记得之前看过pyboy模拟器,就是真的从汇编指令开始模拟的。当时叹为天人。。。每一个设备的指令集架构 (ISA, Instruction Set Architecture)都可能不同,要模拟的东西也非常多,有时候还要把它的BUG模拟处理出来。想想还真的佩服那些极客,理想当饭吃。有这些功夫干点啥挣钱的行当不行。。。
参考:
在 Excel 中构建 16 位 CPU!国外大牛极限“整活”:128KB RAM、16 色显示,还有自定义汇编...-优快云博客
此博客围绕算法展开,但具体内容为空,推测可能会涉及算法的原理、应用等信息技术领域相关内容。
1403






