12 个 Verilog 文件搭出可仿真 GPU:tiny-gpu 从跑通到读懂的教程
2026/9/13 8:24:03 网站建设 项目流程

12 个 Verilog 文件搭出可仿真 GPU:tiny-gpu 从跑通到读懂的教程

【免费下载链接】tiny-gpuA minimal GPU design in Verilog to learn how GPUs work from the ground up项目地址: https://gitcode.com/GitHub_Trending/ti/tiny-gpu

两个 2×2 矩阵、4 个线程,一次内核就算完整个矩阵乘法。这是 tiny-gpu 的日常场景:一个用 Verilog 写成的最小 GPGPU,能在仿真里跑矩阵加法、乘法内核,并逐周期打印执行轨迹。我们走的路径和常见架构阅读相反——先跑仿真看结果,再钻进 12 个 Verilog 模块里。

跑起来:3 条命令跑通矩阵乘法仿真

仿真需要三样工具:iverilog(Icarus Verilog 编译器)、cocotb 测试框架、sv2v(SystemVerilog 转 Verilog 的转换器,README 的 Simulation 一节有安装指引)。前两者走常规包管理器安装即可。

git clone https://gitcode.com/GitHub_Trending/ti/tiny-gpu brew install icarus-verilog pip3 install cocotb

进入仓库后建好构建目录,触发 Makefile 里的测试目标:

mkdir build make test_matmul

测试会在test/logs下写一份日志:文件开头是初始内存状态,两行 2×2 矩阵 [1,2,3,4] 并排放着;结尾是最终状态,乘积 [7,10,15,22] 落在地址 8~11。中间是逐周期执行轨迹——每个核心、每个线程每拍执行了什么指令、PC 和寄存器是什么值,全被记录。

看内部:数据像快递一样被分拣

把整台机器想象成快递站。内核是一张订单,线程是包裹,调度器(dispatch)负责把包裹按 4 个一箱打成 block,再整箱交给空闲的核心(core)——一箱只派一次车,不混装。

数据流转分四段:

  1. 装载:指令进程序内存(16 位一条,共 256 行),数据进数据内存(8 位一个),内核的线程数写进 DCR(设备控制寄存器)。
  2. 派送:start 信号拉高后,DCR 交出线程总数,dispatch 把它切成 block,分发给 2 个核心。
  3. 执行:核心里每个线程都有一整套"私人装备"——ALU(做算术)、LSU(访存)、PC(程序计数器)、寄存器堆,大家执行同一条指令、各算各的数据。这就是 SIMD(一条指令同时算多个数)的硬件形态。
  4. 回传:核心发出的访存请求要过内存控制器,它有固定数量的通道(数据内存 4 条、程序内存 1 条),像限行车道一样限速放行,再把响应转回发起线程。

为什么这么设计?一切为了"看得清"。一次只跑一个内核、核心一次只处理一个 block、线程不允许分支发散(每条指令后都回到同一 PC)、缓存标注为开发中——每砍一刀,都在 README 里留了可扩展的锚点。

拆解关键模块:三个文件讲清 8 成机制

读懂 scheduler.sv 的六段状态机

输入是三样:start 信号、译码器控制信号、取指器与各线程 LSU 的当前状态。处理是一套八状态 FSM:IDLE 等启动;FETCH 等指令取回;DECODE、REQUEST 各占一拍;WAIT 是唯一可能"卡住"的阶段,轮询直到没有任何 LSU 还在等内存响应(访存是异步的,其余步骤全部一拍过);EXECUTE 跑 ALU 与 PC 运算;UPDATE 写回结果,遇到 RET 就进入 DONE 并拉高 done。输出是 core_state(告诉外部核心此刻在哪一步)、当前 PC 和 done 信号。

妙处在 WAIT:它不是睡死等待,而是"轮询即放行"——调度器只看所有 LSU 是否都脱手,脱手立刻推进。异步内存延迟就这样被同步状态机吸收掉了。

文件:src/scheduler.sv

读懂 decoder.sv 的 11 条指令

输入是一条 16 位指令和 core_state,译码器只在 DECODE 拍动作。高 4 位是操作码,全部 11 条指令都在里面:BRnzp(按 NZP 标志条件跳转)、CMP(比较)、ADD/SUB/MUL/DIV(四则运算)、LDR/STR(取数/存数)、CONST(灌立即数)、RET(线程退出)。低 12 位切成源/目的寄存器号(各 4 位,共 16 个寄存器)、立即数和分支条件。

输出不是"理解",而是一组 0/1 控制信号:寄存器写不写、内存读不读、ALU 选哪种运算、下一拍 PC 从哪来。其中 R13~R15 只读,分别放着 %blockIdx、%blockDim、%threadIdx——同一条指令靠这三个值让每个线程算到"自己的那份数据"。

文件:src/decoder.sv

读懂 gpu.sv 的内核启动

顶层 gpu 是参数化拼装:NUM_CORES 默认 2,THREADS_PER_BLOCK 默认 4,数据内存 4 条读写通道。启动链路只有一条线:DCR 交出线程数 → dispatch 切块派核 → 各核心调度器把 block 跑完报 DONE → done 拉高。

核心的"私人装备"来自 src/core.sv 里按线程数生成的实例化——每个线程的寄存器堆、PC、ALU、LSU 都是实体硬件,不是分时复用。这也是它能"真并行"的原因:4 个线程同一拍里真的在算 4 份数据。

文件:src/gpu.sv

折腾指南:改什么、会怎样

  • 换派送策略src/dispatch.sv里把 block 到核心的分配方式从顺序改为轮询。效果:两个核心分单更均匀,轨迹日志里能直接对比两种策略的活跃周期。
  • 压状态机src/scheduler.sv里给非访存指令跳过 WAIT。效果:每条 LDR/STR 之外的指令少等一拍,日志尾部的 Completed in N cycles 数字变小。
  • 调并行度src/gpu.sv把 NUM_CORES 从 2 改成 4。效果:8 线程内核从 2 个 block 变 1 个,周期数下降;而 matmul 只有 1 个 block,加核毫无收益——这恰好是"block 是调度单位"最直观的证明。

一句话收束与资源入口

tiny-gpu 把 GPGPU 压缩成一次能读完的 12 个文件:快递分拣、六段状态机、11 条指令译码,加上可跑的矩阵乘法仿真,就是理解"GPU 如何执行一条指令"的最短路径。

  • 整体架构、内核源码与进阶方向:README.md
  • 矩阵乘法仿真脚本(含 27 条指令的机器码表):test/test_matmul.py
  • 计算核心顶层模块:src/core.sv

【免费下载链接】tiny-gpuA minimal GPU design in Verilog to learn how GPUs work from the ground up项目地址: https://gitcode.com/GitHub_Trending/ti/tiny-gpu

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询