tiny-gpu:不到 15 个 Verilog 文件里藏着一个能算矩阵乘法的 GPU
tiny-gpu不到 15 个 Verilog 文件里藏着一个能算矩阵乘法的 GPU【免费下载链接】tiny-gpuA minimal GPU design in Verilog to learn how GPUs work from the ground up项目地址: https://gitcode.com/GitHub_Trending/ti/tiny-gpuCPU 内部可以看GPU 内部看不到。CPU 架构公开、文档详尽主流 GPU 的低层架构却全是商业机密会写 CUDA 的人往往不知道 warp 背后的硬件长什么样。tiny-gpu 补的就是这个缺口不到 15 个 Verilog 文件一个真能跑矩阵乘法内核的 GPU 设计每个线程的寄存器堆、每个状态机、每个内存通道都摊开给你看。它到底是什么一个能跑矩阵内核的最小 GPU一句话定位tiny-gpu 是一个用 Verilog 写的极简通用计算 GPUGPGPU目的不是画图而是把 GPU 硬件内部讲明白。它做了块Block/线程Thread两级并行模型4 线程同步的 SIMD 执行有限带宽下异步内存请求的仲裁外加矩阵加法、矩阵乘法两个可运行内核。它没做没有光栅化没有纹理单元一次只跑一个内核没有分支发散所有线程必须停在同一个程序计数器上缓存还没完成README 里标着 WIP。默认配置是 2 个计算核心每核 4 线程数据内存 256 行 × 8 位程序内存 256 行 16 位指令。小到可以画在一块白板上。核心机制拆解寄存器堆、六状态机与 11 条指令这个项目的机制都在回答三个问题SIMD 的多份数据从哪来内存慢怎么办内核最少要几条指令。4 个线程怎么算 4 份不同数据每线程一套寄存器堆SIMD 最容易理解错的一点一条指令算多份数据不是所有线程共用寄存器。共用的话大家只能算出同一个值。原理一句话给每个线程配一套独立寄存器堆同一条指令并行读写四套硬件操作码相同数据不同。src/core.sv 用 generate 循环为每核的 4 个线程各实例化一个 ALU、一个 LSU负责内存读写的部件、一个寄存器堆、一个程序计数器。src/registers.sv 里的寄存器堆有 16 个寄存器R13/R14/R15 是三个只读寄存器分别存 %blockIdx、%blockDim、%threadIdx。内核的前两条指令就用它们算出全局下标 i之后每个线程的 LDR 各走各的地址。软件不用写任何线程号判断硬件直接发给各自的寄存器。内存比 ALU 慢怎么办六状态机里唯一的变长状态ALU 运算一拍完成去全局内存取一次数据却要好几拍。流水线要是走到头load 的数据还没回来写回的就是垃圾值。原理一句话把发内存请求和执行拆成两个状态中间插一个 WAIT等所有线程的请求都回来才放行。scheduler.sv 让每条指令走 IDLE→FETCH→DECODE→REQUEST→WAIT→EXECUTE→UPDATEWAIT 是其中唯一长度不定的状态WAIT: begin reg any_lsu_waiting 1b0; for (int i 0; i THREADS_PER_BLOCK; i) begin if (lsu_state[i] 2b01 || lsu_state[i] 2b10) any_lsu_waiting 1b1; end if (!any_lsu_waiting) core_state EXECUTE; end它等的那头是内存控制器 src/controller.sv把各核心发来的请求排队按外部内存的实际带宽限流再把数据送回对应线程。11 条指令如何写出循环和矩阵乘指令集的目标不是覆盖所有场景是让两个示例内核跑得通。16 位定长指令高 4 位操作码中间 8 位两个源寄存器低 4 位目的寄存器低 8 位兼任立即数[11:9] 位存分支条件。全部 11 条ADD/SUB/MUL/DIV 做算术LDR/STR 读写全局内存CONST 装立即数CMP 和 BRnzp 一对负责比较与条件跳转——循环就靠它俩写。matmul 内核的写法4 个线程各领 2×2 结果矩阵的一个元素k 循环两遍做点积BRn 跳回循环头。注意这里有个前提分支必须对所有线程汇合到同一条指令调度器是这么假设的这是简化付出的代价。跑起来看结果make 一次生成逐周期执行轨迹前置三个工具iverilogIcarus Verilog 模拟器、cocotbPython 驱动的仿真框架、sv2vSystemVerilog 转 Verilog 的工具。装好后git clone https://gitcode.com/GitHub_Trending/ti/tiny-gpu cd tiny-gpu mkdir build make test_matmul跑完会在 test/logs 生成日志。开头打印数据内存初始状态A[[1,2],[3,4]] 和 B[[1,2],[3,4]] 并排摆着随后逐周期输出当前指令、每个线程的 PC、寄存器值和内存状态。结束前打出 Completed in XX cycles最终数据内存里地址 8 到 11 是 7、11、15、23——正是 C A×B。测试用 Python 算出期望值逐项比对错一个元素整条用例就挂。往哪走分支发散与内存合并项目自己列了若干扩展方向其中两个动手性价比最高。分支发散。现在 src/scheduler.sv 的 UPDATE 状态直接拿 next_pc[THREADS_PER_BLOCK-1] 当整组下一条 PC等于假设所有线程已汇合。想试的话加一个掩码记录哪些线程活跃线程 next_pc 不一致时拆成两路先后执行汇合后再合并掩码。从 pc.sv 和 scheduler.sv 的交互入手。内存合并。matmul 内核里同一拍的 4 个线程请求四个相邻地址却各自单独走通道。看 src/controller.sv在请求队列里发现两个请求地址只差 1就可以拼成一次更宽度的传输通道占用减半。回到开头那句话GPU 内部过去看不到现在这套就摆在眼前——4 个线程、6 个状态、11 条指令和商业架构文档里的术语一一对得上看完再回头看 warp、发散、合并这些词就不虚了。只打算做一件事的话打开 src/scheduler.sv 找到 WAIT 状态拿一条 LDR 指令推三拍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创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →