spatial for parallel compute 使用简介

spatial 能干嘛

spatial 可以很方便地设计并行计算电路,内置了 并行计算语法和流水线语法支持。

下载与编译

官方下载地址是 https://github.com/stanford-ppl/spatial

国内的备份网址是 https://gitee.com/mirrors/Spatial

编译

简单来说,在项目目录下,直接运行sbt compile就可以了。

但实际会涉及到很多问题,比如相关库下载的问题,经常下载不了,需要使用国内的镜像,比如阿里和清华的镜像。

似乎也可以用IDEA来自动解决这些库的问题。

scala版本是scala2, scala3是不行的,因为现在spatial用了scala2的宏的一些机制,但是scala3在宏机制方面做了改变,跟scala2不一样了。

java的openjdk库的版本可能也有一些要求,具体看提示。

另外缺省的行为,很多都是基于linux或者unix的,要在windows下使用,要不装wsl,要不重新把那些sh脚本改成bat之类的脚本。简单起见,建议装wsl.

总之会要折腾一阵。

用spatial的工作过程是基本可以分为3步。

第1步,用spatial(一种scala方言)设计电路,

第2步,可以编译生成为scala代码,进行仿真运行。编译时的时候也能够检查一些错误。貌似也可以做一些时钟分析什么的,存疑。

运行 sbt "runMain apps.<AppName> --args=\"<参数>\""

# 在项目根目录下运行
sbt -mem 8192 # 建议分配足够内存

# 在 sbt 控制台中执行
run # 然后选择要运行的应用程序

# 方法1: 在 sbt 中指定
sbt "runMain <完整包名.应用名>"

# 示例
sbt "runMain apps.<AppName>"

spatial/
├── build.sbt
├── src/
│ └── main/
│ └── scala/
│ └── apps/
│ └── YourApp.scala
└── gen/ # 生成的代码和报告

带参数的仿真

# 在 sbt 控制台中
runMain apps.<AppName> --args="<参数>"

# 或者直接
sbt "runMain apps.<AppName> --args=\"<参数>\""

完整示例

# 进入 Spatial 项目目录
cd /path/to/spatial

# 启动 sbt(建议分配足够内存)
sbt -mem 8192

# 在 sbt 控制台中
> compile # 编译代码
> run # 交互式选择应用运行
> projects # 查看所有子项目
> project <项目名> # 切换到特定子项目

 

第3步,编译生成verilog,接入到后端的硬件设计平台,比如fpga或者asic.

# 在 Spatial 项目根目录下
sbt -mem 8192

# 在 sbt 控制台中运行
runMain apps.<YourAppName> --synth --fpga=<FPGA平台>

指定目标 FPGA 平台

# 对于 Xilinx FPGA
runMain apps.<AppName> --synth --fpga=ZCU

# 对于 AWS EC2 F1
runMain apps.<AppName> --synth --fpga=AWS

# 对于 DE1-SoC
runMain apps.<AppName> --synth --fpga=DE1

# 对于 Arria10
runMain apps.<AppName> --synth --fpga=A10

输出文件位置位于

spatial/gen/<AppName>/verilog/

支持的 FPGA 平台

常见的平台包括:

  • ZCU : Xilinx Zynq UltraScale+

  • AWS : Amazon EC2 F1

  • DE1 : Terasic DE1-SoC

  • A10 : Intel Arria 10

  • VCS : 用于仿真的 Verilog 编译仿真器

  • 验证生成结果

    bash
    # 检查是否生成成功
    ls -la gen/<AppName>/verilog/
    
    # 查看资源使用报告
    cat gen/<AppName>/verilog/reports/*.rpt

具体的设计也比较简单

下面简要介绍 Spatial 框架设计电路的核心概念和典型样例:

1. 项目文件结构

text
spatial-app/
├── build.sbt                 # 构建配置
├── src/main/scala/
│   └── apps/
│       ├── SimpleExample.scala    # 简单应用
│       ├── MatrixMult.scala       # 矩阵乘法
│       └── PipeLineExample.scala  # 流水线示例
└── resources/               # 资源文件

2. 基本应用模板

scala
import spatial.dsl._

@spatial object SimpleExample extends SpatialApp {
  def main(args: Array[String]): Unit = {
    // 1. 定义硬件参数
    val tileSize = 16
    
    // 2. 创建内存
    val src = ArgIn[Int]
    val dst = ArgOut[Int]
    
    // 3. 加速器设计
    Accel {
      // 硬件电路描述
      dst := src + 1
    }
    
    // 4. 设置参数并运行
    setArg(src, args(0).to[Int])
    println("Result: " + getArg(dst))
  }
}

3. 并行计算模块

DRAM 和 SRAM 使用

scala
Accel {
  // 片外内存
  val data = DRAM[Int](1024)
  // 片内缓存
  val sram = SRAM[Int](64)
  
  // 并行加载
  sram load data(0::64 par 16)  // 16路并行加载
  
  // 并行计算
  Foreach(0 until 64 par 8) { i =>
    sram(i) = sram(i) * 2
  }
  
  // 并行存储
  data(0::64 par 16) store sram
}

4. 流水线设计

简单流水线

scala
Accel {
  // 三级流水线
  Pipe.foreach(0 until 100) { i =>
    val a = i * 2        // 第1级
    val b = a + 1        // 第2级  
    val c = b * 3        // 第3级
  }
}

带并行度的流水线

scala
Accel {
  // 并行流水线
  Foreach(0 until 64 by 8 par 2) { i =>
    val tile = SRAM[Int](8)
    
    // 流水线处理每个tile
    Pipe.foreach(0 until 8) { j =>
      tile(j) = (i + j) * 2
    }
  }
}

5. 主要硬件模块

控制单元

scala
val ctrl = CtrlUnit {
  // 控制逻辑
  Sequential.Foreach(0 until 10) { i =>
    // 顺序执行
  }
}

计算单元

scala
// 向量计算
val result = Reg[Int]
Reduce(result)(0 until 64 par 8) { i =>
  i * i
} { _ + _ }

// 矩阵运算
val matA = SRAM[Int](64, 64)
val matB = SRAM[Int](64, 64)
val matC = SRAM[Int](64, 64)

Foreach(0 until 64 par 4, 0 until 64 par 4) { (i, j) =>
  matC(i, j) = matA(i, j) + matB(i, j)
}

存储层次

scala
// 多级存储
val dram = DRAM[Int](1024)        // 片外DRAM
val sram = SRAM[Int](256)         // 片内SRAM  
val regfile = RegFile[Int](32)    // 寄存器文件
val fifo = FIFO[Int](16)          // FIFO缓冲

6. 完整矩阵乘法示例

scala
@spatial object MatrixMult extends SpatialApp {
  def main(args: Array[String]): Unit = {
    val M = 64; val N = 64; val P = 64
    
    val a = DRAM[Int](M, P)
    val b = DRAM[Int](P, N) 
    val c = DRAM[Int](M, N)
    
    Accel {
      Foreach(0 until M by 16, 0 until N by 16) { (i, j) =>
        val tileC = SRAM[Int](16, 16)
        
        Foreach(0 until P by 16) { k =>
          val tileA = SRAM[Int](16, 16)
          val tileB = SRAM[Int](16, 16)
          
          tileA load a(i::i+16, k::k+16 par 8)
          tileB load b(k::k+16, j::j+16 par 8)
          
          // 矩阵乘计算
          MemReduce(tileC)(k until k+16 by 1 par 4) { kk =>
            val partial = SRAM[Int](16, 16)
            Foreach(0 until 16 par 2, 0 until 16 par 2) { (ii, jj) =>
              partial(ii, jj) = tileA(ii, kk) * tileB(kk, jj)
            }
            partial
          } { _ + _ }
        }
        
        c(i::i+16, j::j+16 par 8) store tileC
      }
    }
  }
}

7. 关键特性总结

  • 并行度控制:par N 指定并行度

  • 流水线:Pipe 自动插入流水线寄存器

  • 存储层次:DRAM → SRAM → RegFile → Scalar

  • 控制结构:ForeachReduceMemReduceFSM

  • 数据传输:load/store 实现内存传输

这种设计方式让你在高级语言层面描述算法,Spatial 自动生成对应的硬件电路,大大提高了硬件开发效率。

以上资料通过ai问答和部分实践获得的,没有完整验证过。不过认为是可信的和可行的,如遇问题或有心得,欢迎反馈交流,不过问题还请自行解决。

网上的手册和文件的缺省位置很容易掉坑,结合deepseek就好很多了,可以较快反馈查错,解决问题。希望花一些时间,把手册完善一些,争取一次过,节省一点大家的时间。

 

posted on 2025-10-07 10:04  高树岭软件实验室  阅读(2)  评论(0)    收藏  举报

导航