diff --git a/articles/20230119-tinyemu-introduction.md b/articles/20230119-tinyemu-introduction.md
new file mode 100644
index 0000000000000000000000000000000000000000..48ed2103f6e762fa7ae2473d1aaae1da5ea24a4b
--- /dev/null
+++ b/articles/20230119-tinyemu-introduction.md
@@ -0,0 +1,234 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1 - [spaces urls]
+> Author: YJMSTR
+> Date: 2023/01/19
+> Revisor: Bin Meng, Falcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Sponsor: PLCT Lab, ISCAS
+
+# 从零开始,徒手写一个 RISC-V 模拟器(1)——简介与基本框架
+
+## 前言
+
+之前 [翻译了一篇博客][1],博主受到 [RVEMU 项目][2] 的启发,尝试通过用 C 语言写一个 RISC-V emulator,来学习 RISC-V 与计算机体系结构。但该博主在实现了 RISC-V 的整数、乘法和 Zicsr 模块之后便没有后续了。
+
+本项目旨在延续该博主的工作,开发一个简单的教学用途 RISC-V 模拟器 —— TinyEMU,与此同时学习 RISC-V、计算机系统架构相关的知识。
+
+## 模拟器与仿真器
+
+2002 年全国科学技术名词审定委员会公布出版的《计算机科学技术名词》(第二版)把 simulation 翻译为模拟,emulation 翻译为仿真,造成了极大的混淆。我在翻译前言中提到的博客时也将 emulator 译为了模拟器。
+
+模拟器(emulator)是使一个计算机系统(称为 host)表现得像另一个计算机系统(称为 guest)的硬件或软件,常用于调试,其使得 host 能够运行或使用为 guest 设计的软件或外设。
+
+而仿真器(simulator)是对真实情景进行模拟的工具,但许多仿真器被译成了模拟器,比如 Flight Simulator 被译为了飞行模拟器。
+
+正如 TinyEMU 的名字(Tiny Emulator)所示,它是一个迷你的 RISC-V 模拟器。
+
+## 指令执行方式
+
+目前模拟器 [执行指令的方式][4] 主要有以 Spike 为主的解释型,和以 QEMU 为主的翻译型。
+
+解释型直接用高级语言模拟指令的行为,比如 RISC-V ISA 中的 `add rd, rs1, rs2` 指令,它的含义是将 rs1 和 rs2 寄存器中的值相加后存入寄存器 rd 中,在解释型的模拟器里可以直接进行模拟:
+
+```c
+decode_result = decode(inst); //译码
+switch (decode_result) {
+ case ADD: {R(rd) = R(rs1) + R(rs2); pc += 4; break;} //执行
+ ...;
+}
+```
+
+解释型的优点是方便分析,缺点是性能较低。
+
+翻译型的模拟器会将指令翻译成本机可以直接执行的指令序列,每次会翻译若干条指令,并一次性执行。其优点是运行速度快,可以结合编译技术对翻译过程进行优化,缺点是分析较为困难。
+
+TinyEMU 采用的指令执行方式是解释型。
+
+## 基本框架
+
+要想运行模拟器,至少要实现本小节中包含的内容(监视器、内存、CPU)。
+
+TinyEMU 的根目录结构如下:
+
+- TinyEmu/
+ - src/
+ - includes/
+ - main.c
+ - Makefile
+
+### 监视器
+
+监视器为 TinyEMU 提供与用户进行交互的命令行。监视器有一个主循环不断监听键盘输入,并根据键盘的输入执行对应的操作,我们将在 main.c 中实现它。
+
+监视器还提供调试器,用于调试在 TinyEMU 中运行的客户程序。
+
+要想实现和常见的命令行程序相同的基本功能,比如命令补全,快速输入历史命令等功能,可以使用 GNU Readline 库。如果想简单些也可以直接使用 scanf 读入命令进行处理。
+
+监视器还要负责读入 guest 程序的路径。Guest 程序以二进制文件(.bin)的形式传入,存放于模拟器的内存中。
+
+目前我们先为监视器实现如下基本功能:
+
+- q:退出
+- c:执行程序
+- r:读入程序
+- h:输出帮助文本
+
+main.c 中的主循环如下:
+
+```c
+while (1) {
+ scanf("%s", &opt);
+ switch (opt[0]) {
+ case 'q':
+ return 0;
+ case 'c':
+ cmd_c();
+ case 'h':
+ cmd_h();
+ case 'r':
+ cmd_r();
+ default:
+ puts("invalid command");
+ puts("input \'h\' for help");
+ break;
+ }
+}
+```
+
+其中 cmd_r() 的代码如下,它负责读入模拟器要执行的程序:
+
+```c
+void cmd_r() {
+ scanf("%s", img_path);
+ FILE *fp = fopen(img_path, "rb");
+ assert(fp);
+ fseek(fp, 0, SEEK_END);
+ long size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ fread(guest_to_host(RESET_VECTOR), fp, 1, size);
+ fclose(fp);
+}
+```
+
+### 内存
+
+DRAM 是系统内存,其中存放有指令和数据。此外,还需要为内存映射 IO(MMIO)留出足够的地址空间,以方便后续添加外设。
+
+这里以 QEMU RISC-V 'virt' 平台作为参考,DRAM 的起始地址为 0x80000000,更低的地址留给外设。RESET_VECTOR 的地址默认为 DRAM 起始地址:
+
+```c
+#define DRAM_SIZE 1024*1024*128ull //128 MiB
+#define DRAM_BASE 0x80000000ull
+#ifndef RESET_VECTOR_OFFSET
+#define RESET_VECTOR_OFFSET 0
+#endif
+#define RESET_VECTOR DRAM_BASE + RESET_VECTOR_OFFSET
+```
+
+如果之后要运行 xv6 等特定程序,需要对内存大小进行对应修改。
+
+在真实的计算机系统中,信息流是通过总线进行传输的。而在模拟器中 CPU 等设备要想取得内存中的数据,可以直接访问内存,不需要经由总线。
+
+访存函数实现如下:
+
+```c
+void dram_write(uint64_t addr, int length, uint64_t val) {
+ assert (length == 1 || length == 2 || length == 4 || length == 8);
+ switch (length) {
+ case 1:
+ dram[addr] = val & 0xff;
+ return;
+ case 2:
+ dram[addr] = val & 0xff;
+ dram[addr + 1] = (val & 0xff00) >> 8;
+ return;
+ case 4:
+ dram[addr] = val & 0xff;
+ dram[addr + 1] = (val & 0xff00) >> 8;
+ dram[addr + 2] = (val & 0xff0000) >> 16;
+ dram[addr + 3] = (val & 0xff000000) >> 24;
+ return;
+ case 8:
+ dram_write(addr, 4, val & 0xffff);
+ dram_write(addr + 4, 4, (val & 0xffff0000) >> 32);
+ return;
+ }
+}
+
+uint64_t dram_read(uint64_t addr, int length) {
+ assert (length == 1 || length == 2 || length == 4 || length == 8);
+ switch (length) {
+ case 1:
+ return dram[addr];
+ case 2:
+ return (dram[addr + 1] << 8) | dram[addr];
+ case 4:
+ return (dram[addr + 3] << 24) | (dram[addr + 2] << 16) | (dram[addr + 1] << 8) | (dram[addr]);
+ case 8:
+ return (dram_read(addr + 4, 4) << 32) | dram_read(addr, 4);
+ }
+}
+```
+
+这里使用 `mem_read` 等辅助函数将 guest 程序的内存地址映射到 TinyEMU 的内存数组的下标中:
+
+```c
+uint64_t mem_read(uint64_t addr, int length) {
+ return dram_read(addr - DRAM_BASE, length);
+}
+
+void mem_write(uint64_t addr, int length, uint64_t val) {
+ dram_write(addr - DRAM_BASE, length, val);
+}
+```
+
+### CPU
+
+CPU 部分包括 RISC-V 通用寄存器组。CPU 需要实现取指、译码、执行这几个步骤:
+
+- 取指:根据 PC 寄存器中的值在指令内存中取得数据,存到指令寄存器 IR 中。RISC-V 使用小端序存储数据,低地址存放低字节。
+- 译码:根据机器码判断是哪种类型的哪一条指令,并根据指令类型提取指令中的寄存器编号、立即数等信息。译码结果存放在 `DECODER` 结构体中。
+- 执行:直接用 C 代码模拟指令的行为。
+
+CPU 结构体如下:
+
+```c
+typedef struct CPU {
+ uint64_t regs[32];
+ uint64_t pc;
+} CPU;
+```
+
+可以将指令的译码结果封装成结构体 `DECODER`,将提取出的立即数,源地址,目的地址等数据存在里面供模拟该指令行为的函数使用。
+
+取指->译码->执行三级流水:
+
+```c
+void exec_once(CPU *cpu) { // CPU 执行一条指令
+ uint32_t inst = inst_fetch(cpu); //取指
+ DECODER decoder = decode(inst); //译码
+ inst_handle[decoder->inst_name](&decoder);// 执行
+ cpu->pc = decoder->dnpc; //更新 PC
+}
+```
+
+其中 `inst_handle` 是存放函数指针的数组,`inst_handle[decoder->inst_name](&decoder)` 是执行指令的函数,`DECODER decoder` 作为该函数的参数。执行函数会对 `decoder` 进行修改,最终处理器根据执行结果对 PC 进行更新。
+
+## 总结
+
+本文介绍了模拟器与仿真器的区别,模拟器的指令执行方式以及 TinyEMU 的基本框架,主要包扩监视器、内存、CPU 三大模块。其中监视器负责提供和用户进行交互的命令行,以及提供调试客户程序的功能;内存是一个大数组,存放有要执行的指令。CPU 具有经典的取指、译码、执行三级流水。
+
+下一篇文章将进一步介绍 RISC-V 指令集与 CPU 模块、并在模拟器上运行程序。
+
+## 参考资料
+
+1. [用纯 C 语言写一个简单的 RISC-V 模拟器(支持基础整数指令集,乘法指令集与 CSR 指令)][1]
+2. [RVEMU][2]
+3. [RVEMU 开发教程][3]
+4. [【余子濠】NEMU:一个效率接近 QEMU 的高性能解释器 - 第一届 RISC-V 中国峰会][4]
+5. [NEMU][5]
+
+[1]: https://tinylab.org/writing-a-simple-riscv-emulator-in-plain-c/
+[2]: https://github.com/d0iasm/rvemu
+[3]: https://book.rvemu.app/
+[4]: https://www.bilibili.com/video/BV1Zb4y1k7RJ
+[5]: https://github.com/NJU-ProjectN/nemu
\ No newline at end of file