本文档旨在记录个人参与"一生一芯"的探索历程,通过实践笔记为初学者提供参考实现路径。需要特别说明的是,本实录完整呈现了实现细节,若您认为文中直接呈现的模块设计思路与验证方案会削弱实践探索的乐趣,请谨慎阅读。为保证项目实践的新鲜体验,文档已通过目录结构进行关键节点隔离设计,读者可灵活选择。
具体请参考 官方网站
预学习
官方推荐环境为Ubuntu 22.04,这里是使用 Vmware + Ubuntu22.04
一、配置 SSH Key
1. 生成 SSH 密钥对
ssh-keygen -t ed25519 -C "your_email@example.com" # 推荐使用 Ed25519 算法(更新更安全)
# 如果系统不支持 Ed25519,改用 RSA 4096:
# ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
- 按回车键接受默认保存路径(
~/.ssh/id_ed25519或~/.ssh/id_rsa)。 - 设置密钥密码(可选):若留空则每次使用密钥无需密码,但安全性较低 。
2. 复制公钥内容
cat ~/.ssh/id_ed25519.pub # 或 id_rsa.pub
- 复制以
ssh-ed25519或ssh-rsa开头到邮箱结尾的全部内容 。
3. 添加公钥到 GitHub
- 登录 GitHub → 点击右上角头像 → Settings → SSH and GPG keys → New SSH key。
- Title:输入描述(如“My Laptop”)。
- Key:粘贴复制的公钥内容,确保无多余空格或换行。
- 点击 Add SSH key 完成添加 。
4. 验证 SSH 连接
ssh -T git@github.com
- 若显示
You've successfully authenticated表示配置成功
二、克隆“一生一芯”仓库
执行克隆命令
git clone -b master git@github.com:OSCPU/ysyx-workbench.git
三、安装 Verilator
一生一芯预学习中需要安装5.008版本Verilator,通过git方式下载所需要版本。
# 准备工作
sudo apt-get install git perl python3 make autoconf g++ flex bison ccache
sudo apt-get install libgoogle-perftools-dev numactl perl-doc
sudo apt-get install libfl2 # Ubuntu only (ignore if gives error)
sudo apt-get install libfl-dev # Ubuntu only (ignore if gives error)
sudo apt-get install zlibc zlib1g zlib1g-dev # Ubuntu only (ignore if gives error)
sudo apt-get install help2man
下载代码
git clone https://github.com/verilator/verilator ### Only first time
# Every time you need to build:
unset VERILATOR_ROOT # For bash
# unset VERILATOR_ROOT 的作用是
# 清除系统中已存在的 VERILATOR_ROOT 环境变量,
# 确保 Verilator 的编译和安装过程不受旧版本或外部配置的干扰。
更换版本
cd verilator
git pull # Make sure git repository is up-to-date
git checkout v5.008 # Switch to specified release version
配置安装
autoconf # Create ./configure script
./configure # Configure and create Makefile
make -j `nproc` # Build Verilator itself (if error, try just 'make')
sudo make install
下载完后,主目录下的examples目录是一些例程。
四、完成示例:双控开关
1.完成代码部分
进入我们的一生一芯仓库ysyx-workbench
在/csrc创建sim_main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Vtop.h" // 包含 Verilator 生成的头文件
#include "verilated.h"
int main(int argc, char** argv) {
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vtop* top = new Vtop{contextp}; // 实例化顶层模块
while (!contextp->gotFinish()) { // 仿真循环
int a = rand() & 1; // 随机生成 a 信号
int b = rand() & 1; // 随机生成 b 信号
top->a = a; // 驱动输入端口 a
top->b = b; // 驱动输入端口 b
top->eval(); // 更新电路状态
printf("a = %d, b = %d, f = %d\n", a, b, top->f); // 打印结果
assert(top->f == (a ^ b)); // 检查输出是否正确
}
delete top;
delete contextp;
return 0;
}
在/vsrc下创建top.v
module top(
input a,
input b,
output f
);
assign f = a ^ b; // 双控开关逻辑
endmodule
最后我们需要将其使用Verilator生成的 C++ 模型和测试代码,并且将其编译为可执行文件。
在ysyx-workbench目录下运行
verilator --cc -y vsrc/ vsrc/top.v --exe csrc/sim_main.cpp --build
运行文件
./obj_dir/Vtop
运行结果为:
大家根据代码自行理解即可,取消请使用ctrl + c
2.打印并且查看波形
1).安装依赖和gtkwave
sudo apt install libcanberra-gtk-module libcanberra-gtk3-module
sudo apt install gtkwave
2).新建代码 sim_main_trace.cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "Vtop.h" // 包含 Verilator 生成的头文件
#include "verilated.h"
#include "verilated_vcd_c.h" // 添加波形支持头文件
int main(int argc, char** argv) {
VerilatedContext* contextp = new VerilatedContext;
contextp->commandArgs(argc, argv);
Vtop* top = new Vtop{contextp};
// 初始化波形记录
VerilatedVcdC* tfp = new VerilatedVcdC;
contextp->traceEverOn(true); // 启用追踪功能
top->trace(tfp, 0); // 绑定追踪层级(0 表示所有信号)
tfp->open("wave.vcd"); // 指定波形文件名(VCD格式)
while (!contextp->gotFinish()) {
int a = rand() & 1;
int b = rand() & 1;
top->a = a;
top->b = b;
top->eval();
printf("a = %d, b = %d, f = %d\n", a, b, top->f); // 打印结果
assert(top->f == (a ^ b)); // 检查输出是否正确
// 记录当前时间点的波形
tfp->dump(contextp->time());
contextp->timeInc(1); // 推动仿真时间
}
tfp->close(); // 关闭波形文件
delete top;
delete contextp;
return 0;
}
3).重新编译并生成波形
在编译命令中增加 --trace 参数:
verilator --trace --cc -y vsrc/ vsrc/top.v --exe csrc/sim_main_trace.cpp --build
这会生成支持波形追踪的可执行文件 obj_dir/Vtop
4).运行仿真生成波形
./obj_dir/Vtop
后使用 ctrl + c 结束
5).运行结果
gtkwave wave.vcd

3.生成FST格式的波形(可跳过)
FST格式的波形文件大致是VCD格式的1/50, 但它仅能被GTKWave支持. 尽管如此, 我们还是推荐你使用它.
但是请自行修改代码,使用for循环代替原来的while循环,限制仿真周期
VCD 格式:生成 VCD 波形时,Verilator 可能在仿真结束后自动释放资源,进程能正常响应 Ctrl+C。
FST 格式:FST 生成依赖更复杂的压缩库(如 zlib),若资源未及时释放或存在死锁,可能导致进程无法响应 Ctrl+C
1). 生成 FST 格式波形(体积更小)
# FST 波形格式的生成依赖 zlib.h 头文件
sudo apt install zlib1g-dev
修改代码以支持 FST 格式:
#include "verilated_fst_c.h" // 替换为 FST 头文件 // 替换 VerilatedVcdC 为 FST 类型
VerilatedFstC* tfp = new VerilatedFstC;
tfp->open("wave.fst"); // 文件名后缀改为 .fst
编译时需增加 --trace-fst 参数:
verilator --trace --trace-fst --cc -y vsrc/ vsrc/top.v --exe csrc/sim_main_trace.cpp --build
2). 查看 FST 格式波形
gtkwave wave.fst # 操作界面与 VCD 相同
4.编写Makefile
将cpp中的while循环改成for循环,循环十次
修改npc/Makefile:
# npc/Makefile
# 定义关键路径变量
VERILATOR := verilator
TOP_MODULE := top
VSRC_DIR := vsrc
CSRC_DIR := csrc
BUILD_DIR := obj_dir
WAVEFORM := wave.vcd
FILE_NAME := sim_main_trace.cpp
# 默认目标
all:
@echo "Usage: make sim - Run simulation and generate waveform"
@echo " make clean - Clean build artifacts"
# 仿真目标 (保留git追踪命令)
sim:
$(call git_commit, "sim RTL") # DO NOT REMOVE THIS LINE!!!
@echo "[SIM] Starting simulation..."
# 清理旧构建
rm -rf $(BUILD_DIR) $(WAVEFORM)
# 编译Verilog生成C++模型
$(VERILATOR) --trace --cc -y $(VSRC_DIR) $(VSRC_DIR)/$(TOP_MODULE).v \
--exe $(CSRC_DIR)/$(FILE_NAME) --build
# 运行仿真生成波形
@echo "\n[SIM] Running simulation..."
./$(BUILD_DIR)/V$(TOP_MODULE)
# 自动打开波形 (可选)
@echo "\n[SIM] Opening waveform..."
gtkwave $(WAVEFORM) || true
# 清理构建产物
clean:
rm -rf $(BUILD_DIR) $(WAVEFORM) *.vcd *.fst
# 包含上级Makefile配置
include ../Makefile
运行代码
make sim
5.接入NVBoard
NVBoard虽然是南京大学的教学项目, 但它却与参加"一生一芯"的各位有着一种特殊的联系: 在第三期"一生一芯"的流片名单当中有两位特殊的同学, 他们报名的时候还只是大一, 而其中一位同学sjr就是NVBoard的第一作者.
事实上, 也正是sjr同学在参加"一生一芯"时锻炼出的独立解决问题的能力和自信, 帮助他成功开发NVBoard项目. 如今NVBoard项目又反过来帮助"一生一芯"改进学习效果, NVBoard承载的除了虚拟FPGA板卡的功能之外, 还有"一生一芯"秉承的独立解决问题的理念.
这些离你其实并不遥远, 当你愿意自主学习而不再等着别人给你答案的时候, 你的未来也会充满无限的可能.
准备
# 1.设置git账号
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
# 2.下载且source
cd ysyx-workbench
bash init.sh nvboard
source ~/.bashrc
# 3.下载必要环境
sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev
部分参考了【一生一芯笔记】Verilator和NVBoard简单使用-CSDN博客
可以尝试跑一下example
## 示例
`example`目录下包含一个示例项目,在该目录下通过 `make run` 命令可运行该项目。
如果报错找不到 /scripts/nvboard.mk
那可能是环境变量没有生效,则需要 source ~/.bashrc
实操
1).检查
先检查 gedit ~/.bashrc ,查看环境是否已经export
![]()
2).编写约束文件
npc 下新建 constr/top.nxdc 如下:
top=top
# Line comment inside nxdc
a SW0
b SW1
f LD0
3).编写新的cpp代码
main.cpp,并且csrc中其他cpp文件都删除掉

#include <nvboard.h> // 新增NVBoard头文件
#include "Vtop.h" // Verilator生成的头文件
#include "verilated.h"
static TOP_NAME dut;
void nvboard_bind_all_pins(TOP_NAME* top);
int main() {
nvboard_bind_all_pins(&dut);
nvboard_init();
while(1) {
nvboard_update();
dut.eval();
}
}
4).修改Makefile
这里使用的是源码中的Makefile,可以正常使用
TOPNAME = top
NXDC_FILES = constr/top.nxdc #约束文件路径
INC_PATH ?= #?=的意义是,INC_PATH如果被未被定义,则为?=后的值,如果被定义过,则保持原来的值
VERILATOR = verilator
VERILATOR_CFLAGS += -MMD --build -cc \
-O3 --x-assign fast --x-initial fast --noassert
BUILD_DIR = ./build
OBJ_DIR = $(BUILD_DIR)/obj_dir #OBJ_DIR = ./build/obj_dir
BIN = $(BUILD_DIR)/$(TOPNAME) #BIN = ./build/top
default: $(BIN) #终极目标为default,依赖./build/top
$(shell mkdir -p $(BUILD_DIR))
# 约束文件
SRC_AUTO_BIND = $(abspath $(BUILD_DIR)/auto_bind.cpp)
$(SRC_AUTO_BIND): $(NXDC_FILES)
python3 $(NVBOARD_HOME)/scripts/auto_pin_bind.py $^ $@
# 项目源文件
VSRCS = $(shell find $(abspath ./vsrc) -name "*.v")
# 在vsrc目录中寻找所有的.v文件,并保存在变量VSRCS中
CSRCS = $(shell find $(abspath ./csrc) -name "*.c" -or -name "*.cc" -or -name "*.cpp")
# 在csrc目录中寻找所有的.c/.cc/.cpp文件,并保存在变量CSRCS中
CSRCS += $(SRC_AUTO_BIND)
# 将auto_bind.cpp文件,追加在变量CSRCS中
# NVBoard的规则
include $(NVBOARD_HOME)/scripts/nvboard.mk
# 包含NVBoard相关Makelile规则
# Verilator的规则
INCFLAGS = $(addprefix -I, $(INC_PATH))
CXXFLAGS += $(INCFLAGS) -DTOP_NAME="\"V$(TOPNAME)\""
$(BIN): $(VSRCS) $(CSRCS) $(NVBOARD_ARCHIVE)
@rm -rf $(OBJ_DIR)
$(VERILATOR) $(VERILATOR_CFLAGS) \
--top-module $(TOPNAME) $^ \
$(addprefix -CFLAGS , $(CXXFLAGS)) $(addprefix -LDFLAGS , $(LDFLAGS)) \
--Mdir $(OBJ_DIR) --exe -o $(abspath $(BIN))
all: default
run: $(BIN)
@$^
clean:
rm -rf $(BUILD_DIR)
.PHONY: default all clean run
5).运行
make clean && make run
运行图:

这里我们可以点击 0 和 1 按钮,观察 LED0 的变化。
五、流水灯
参考文章:【一生一芯】---预学习阶段--veriltor仿真环境搭建--流水灯(百分百实现!!!!!!!)-CSDN博客
相信大家看到现在,应该能总结出一套使用NVBoard的流程
1. 确定模块与外设接口
1).功能规划
- 明确需要虚拟化的外设类型(LED、开关、VGA等)
- 定义Verilog模块的输入输出端口(如时钟、复位、开关信号)
- 示例:流水灯需16位LED输出,双控开关需要2个输入和1个输出
2).硬件映射
- 查阅NVBoard支持的虚拟外设类型及引脚编号(如SW0对应开关,LD0对应LED灯)
2. 编写约束文件(.nxdc)
top=top
ledr (LD15, LD14, LD13, LD12, LD11, LD10, LD9, LD8, LD7, LD6, LD5, LD4, LD3, LD2, LD1, LD0)
3. 编写Verilog代码(.v)
module top(
input clk,
input rst,
output [15:0] ledr
);
reg [31:0] count; // 计数器,用于控制流水灯的速度
reg [15:0] led; // 控制流水灯的 LED 状态,16 位宽度
// 时钟上升沿处理逻辑
always @(posedge clk or posedge rst) begin
if (rst) begin
count <= 32'b0; // 复位时清空计数器
led <= 16'b0000000000000001; // 初始化,点亮最右边的 LED
end else begin
// 计数器控制流水灯的速度
if (count >= 5000000) begin // 这里的5000000根据你的时钟频率调整
count <= 32'b0; // 计数器达到阈值时重置
led <= {led[14:0], led[15]}; // 左移操作,最左边的 LED 滚动到右边
end else begin
count <= count + 1; // 增加计数器的值
end
end
end
// 组合逻辑,赋值给 ledr 输出
assign ledr = led; // 直接将 16 位的 led 值赋给 ledr 输出
endmodule
功能实现:
- 组合逻辑使用assign或always @(*)
- 时序逻辑使用always @(posedge clk)和寄存器操作
- 确保模块端口名与约束文件中的信号名完全一致
4. 编写C++仿真代码
#include <nvboard.h>
#include <Vtop.h>
static TOP_NAME dut;
void nvboard_bind_all_pins(TOP_NAME* top);
static void single_cycle() {
dut.clk = 0; dut.eval();
dut.clk = 1; dut.eval();
}
static void reset(int n) {
dut.rst = 1;
while (n -- > 0) single_cycle();
dut.rst = 0;
}
int main() {
nvboard_bind_all_pins(&dut);
nvboard_init();
reset(10);
while(1) {
nvboard_update();
single_cycle();
}
}
-
初始化NVBoard并绑定引脚:
- 通过 nvboard_bind_all_pins(&dut) 绑定引脚
- 使用 nvboard_init() 初始化NVBoard -
添加时钟驱动逻辑:
PA1
预备且试玩
让我们先把代码克隆,观赏一下
git clone -b 2024 git@github.com:NJU-ProjectN/ics-pa.git ics2024
git branch -m master
bash init.sh nemu
bash init.sh abstract-machine
bash init.sh am-kernels
source ~/.bashrc # 更新环境
ics2024
├── abstract-machine # 抽象计算机
├── am-kernels # 基于抽象计算机开发的应用程序
├── fceux-am # 红白机模拟器
├── init.sh # 初始化脚本
├── Makefile # 用于工程打包提交
├── nemu # NEMU
└── README.md
这里提供了超级玛丽的rom 供大家下载,大家请仔细查看 fceux 下的 readme 查看使用方法。
提取码:jygk

任务概述
实现一个支持RV32IM的NEMU
nemu 框架代码初探
目前我们需要关心NEMU子项目中的内容
nemu
├── configs # 预先提供的一些配置文件
├── include # 存放全局使用的头文件
│ ├── common.h # 公用的头文件
│ ├── config # 配置系统生成的头文件, 用于维护配置选项更新的时间戳
│ ├── cpu
│ │ ├── cpu.h
│ │ ├── decode.h # 译码相关
│ │ ├── difftest.h
│ │ └── ifetch.h # 取指相关
│ ├── debug.h # 一些方便调试用的宏
│ ├── device # 设备相关
│ ├── difftest-def.h
│ ├── generated
│ │ └── autoconf.h # 配置系统生成的头文件, 用于根据配置信息定义相关的宏
│ ├── isa.h # ISA相关
│ ├── macro.h # 一些方便的宏定义
│ ├── memory # 访问内存相关
│ └── utils.h
├── Kconfig # 配置信息管理的规则
├── Makefile # Makefile构建脚本
├── README.md
├── resource # 一些辅助资源
├── scripts # Makefile构建脚本
│ ├── build.mk
│ ├── config.mk
│ ├── git.mk # git版本控制相关
│ └── native.mk
├── src # 源文件
│ ├── cpu
│ │ └── cpu-exec.c # 指令执行的主循环
│ ├── device # 设备相关
│ ├── engine
│ │ └── interpreter # 解释器的实现
│ ├── filelist.mk
│ ├── isa # ISA相关的实现
│ │ ├── mips32
│ │ ├── riscv32
│ │ ├── riscv64
│ │ └── x86
│ ├── memory # 内存访问的实现
│ ├── monitor
│ │ ├── monitor.c
│ │ └── sdb # 简易调试器
│ │ ├── expr.c # 表达式求值的实现
│ │ ├── sdb.c # 简易调试器的命令处理
│ │ └── watchpoint.c # 监视点的实现
│ ├── nemu-main.c # 你知道的...
│ └── utils # 一些公共的功能
│ ├── log.c # 日志文件相关
│ ├── rand.c
│ ├── state.c
│ └── timer.c
└── tools # 一些工具
├── fixdep # 依赖修复, 配合配置系统进行使用
├── gen-expr
├── kconfig # 配置系统
├── kvm-diff
├── qemu-diff
└── spike-diff
1. 运行 run nemu
# 在NEMU模拟器中,monitor 模块是调试和运行监控的核心基础设施
# 在NEMU模拟器框架中,TRM 是计算机系统的最基础抽象模型,用于模拟一个极简的计算机硬件环境。
# 安装相关资源
sudo apt install libncurses5-dev libncursesw5-dev
sudo apt install libreadline-dev
# 配置,请将终端调大,否则无法运行
# 成功配置后会生成 nemu/include/generated/autoconf.h
make menuconfig
# 运行
make run
运行NEMU之后你应该能看到相应的欢迎信息, 以及你选择的ISA. 请务必确认输出的ISA信息与你选择的ISA一致. 不过你会看到如下的错误信息:
[src/monitor/monitor.c:20 welcome] Exercise: Please remove me in the source code and compile NEMU again.
riscv32-nemu-interpreter: src/monitor/monitor.c:21: welcome: Assertion `0' failed.
进入 src/monitor/monitor 中的welcome 函数查看排除错误,重新运行即可

只需要注释// assert(0); 即可
2. 基础设施: 简易调试器
参考【一生一芯01】预学习-PA1总结_nemu pa1-CSDN博客
PA1--实现基础设施、表达式求值和监视点-CSDN博客 以及 网站
sdb 详细的内容参考 基础设施: 简易调试器 | 官方文档 (oscc.cc)
sdb 的功能代码在 nemu/src/monitor/sdb/sdb.c中。框架代码已经提供了help,c,q指令的实现,仿照框架代码在cmd_table内添加,完成我们后面的内容。
static struct {
const char *name;
const char *description;
int (*handler) (char *);
} cmd_table [] = {
{ "help", "Display information about all supported commands", cmd_help },
{ "c", "Continue the execution of the program", cmd_c },
{ "q", "Exit NEMU", cmd_q },
/* TODO: Add more commands */
{ "si", "Let the programexcute N instuctions and then suspend the excution,while the N is not given, the default value is 1", cmd_si},
{ "info", "The 'r' key prints the register status. The 'w' key prints the watchpoint information.", cmd_info},
{ "x", "Scan memory", cmd_x},
};
单步执行
在 nemu/src/monitor/sdb/sdb.c 中添加代码,后面同理
static int cmd_si(char *args){
char *arg = strtok(NULL, " ");
int steps = 1; // 默认步数为1
if (args != NULL) {
char *endptr;
steps = strtol(args, &endptr, 10); // 转换为十进制整数
// 参数有效性检查
if (*endptr != '\0' || steps <= 0) {
printf("Invalid step count (must be positive integer)\n");
return -1; // 返回错误状态
}
}
cpu_exec(steps); // 执行指定步数
return 0;
}
实验结果:

打印寄存器
找到正确的 isa_reg_display() 修改,我们默认为 riscv32

void isa_reg_display() {
for (int i = 0; i < 32; i++) {
printf("%-4s\t0x%08x\n", regs[i], cpu.gpr[i]); // 正确访问寄存器数值
}
printf("pc\t0x%08x\n", cpu.pc); // 输出PC寄存器(RISC-V需要)
}
添加代码块
static int cmd_info(char *args) {
char *arg = strtok(NULL, " ");
// 参数检查
if (arg == NULL) {
printf("Usage: info r|w\n");
return -1;
}
if (strcmp(arg, "r") == 0) {
// 调用ISA抽象接口
isa_reg_display();
// printf("in here\n");
} else if (strcmp(arg, "w") == 0) {
print_wp(); //此部分是后期用来打印监测点状态使用,前期可以先注释掉。
} else {
// 错误处理
printf("Unknown info subcommand '%s'\n", arg);
printf("Supported subcommands: r, w\n");
return -1;
}
return 0;
}
结果:

扫描内存
#include <memory/vaddr.h> // 添加头文件
static int cmd_x(char *args){
char *arg = strtok(NULL, " ");
int N;
if(arg!=NULL){
sscanf(arg,"%d",&N);
}
else{
printf("arg1 need num\n");
return 0;
}
char *arg2 = strtok(NULL," ");
if(arg2==NULL){
printf("Need addr\n");
return 0;
}
if(isxdigit(arg2[0])){
}
else{
printf("EXPR need 16\n");
return 0;
}
int addr;
sscanf(arg2,"%x",&addr);
char *arg3 = strtok(NULL," "); //find more argument
if(arg3!=NULL){
printf("Too much args \n");
}
else if(arg!=NULL && arg2!=NULL){
printf("addr = %x\n",addr+N*4);
printf("%u\n",vaddr_read(addr+N*4,4));
}
return 0;
}
不重要的修改:
// 修改代码后q退出不再报错
void sdb_mainloop()
int i;
for (i = 0; i < NR_CMD; i ++) {
if (strcmp(cmd, cmd_table[i].name) == 0) {
if (cmd_table[i].handler(args) < 0) {
// 新增:处理退出命令
if (strcmp(cmd, "q") == 0) {
nemu_state.state = NEMU_QUIT; // 标记为正常退出
}
return; // 退出 sdb_mainloop
}
break;
}
}
3. 表达式求值
4. 监视点
5. 指令集
介绍:指令集ISA简介(CISC和RISC阵营,X86、ARM、MIPS架构)——Linux应用与开发技术笔记(2)_ia-32指令系统、mips指令系统、龙芯指令系统的总结对比分析-CSDN博客
本课程采用RISC-V,查看基本指令集:
RISC-V处理器的设计与实现(一)—— 基本指令集_risc-v基本指令级-CSDN博客
未完待续




3529

被折叠的 条评论
为什么被折叠?



