2个版本

0.0.2 2024年7月28日
0.0.1 2024年7月27日

#74编程语言

Download history 254/week @ 2024-07-27 7/week @ 2024-08-03

261 每月下载次数

GPL-3.0-or-later

1.5MB
11K SLoC

SUS语言

SUS是一种新的RTL硬件设计语言,类似于Verilog或VHDL,专注于简化高性能计算FPGA加速器的开发,同时不牺牲任何设计自由度。

安装

安装通过Rust的包管理器cargo(cargo安装信息)完成。

cargo install sus_compiler

要使用附带的VSCode扩展(sus-lsp)进行语法高亮和代码建议,请通过VSCode扩展探索器安装SUS硬件设计语言,或使用F1安装命令

ext install LennartVanHirtum.sus-lsp

核心理念

SUS旨在成为可综合Verilog和VHDL的直接竞争对手。其主要目标是提供一个直观且简洁的语法来构建网表,以便传统的综合工具仍然可以用来分析生成的硬件。SUS不对硬件设计者强加任何范式,例如要求特定的通信协议或迭代结构。换句话说,SUS不是为了抽象复杂性,而是为了使硬件设计的固有复杂性更易于管理。

SUS对Verilog和VHDL的一个限制是,它要求硬件在至少一个时钟上保持同步。因此,异步硬件是不可表示的,这使得SUS不太适合ASIC开发。

SUS与其他语言区别的三个主要特性

  • 生成变量和类型可以自由组合。通过在实例化后进行主要类型检查来避开由此引起的任何“依赖类型”问题。
  • 通过称为“延迟计数”的正交语言结构轻松进行流水线化。‘正交’意味着添加流水线寄存器不会干扰其他语言功能,如生成或条件代码。
  • 通过接口分离流水线。这防止用户意外交叉具有无逻辑关系的信号。在此级别上实现了时钟域交叉。

最后,SUS的一个重要考虑因素是用户界面。SUS附带一个VSCode IDE插件,允许在IDE中完全使用编译器。编译、类型检查和实例化在用户编写代码时进行,从而实现非常紧密的开发反馈循环。

SUS为您带来的好处

  • 从代码到网表的直接1:1映射
  • 带有边界整数的类型安全[^待办]
  • 时钟域交叉安全
  • 一个不强制结构约束的流水线内置语法
  • 常见结构(如有效信号、复位和子模块通信)的语法糖
  • IDE内编译错误和综合信息
  • 硬件生成器
  • 形式验证集成[^待办]
  • 将一些[^定时]定时约束移动到源文件

[^待办]:尚未实现[^定时]:某些定时约束会影响设计的周期性工作,例如同步时钟的相对速度和假/多周期路径约束。因为它们会影响设计的周期性行为,所以它们应该作为语言的一部分提供,并纳入模拟。当然,像实际时钟速度、边缘模式和外部组件定时这样的定时约束仍然合理地属于定时约束文件。不应该能够表达在模拟和综合之间表现不同的SUS代码。

SUS不做什么

  • 提供握手协议(如AXI)的抽象
  • 运行时迭代结构
  • 自动流水线和重新定时

当然,虽然该语言在语法上不直接支持这些协议,因为这会给输出硬件带来不必要的额外约束,但标准库中提供了处理这些模块的[^待办]。

SUS VSCode语言服务器中SUS代码的示例。

SUS LSP Example

与其他HDL的比较

在我看来,现在的HDL有几个类别。我将依次介绍它们。

老式保护者:(系统-)Verilog和VHDL

这些语言最初被设计为硬件描述语言,旨在精确描述手绘硬件组件的功能。后来,从这些语言中创建了一个“可综合子集”,以便实际上从它们创建硬件。问题是,这些老语言仍然保留着以模拟为首要核心设计的特性。实际上可用于综合的功能集相当小,常见的结构如流水线通常会引入错误。甚至像输入和输出意味着什么这样的东西也留下了模糊的。

高级综合:BlueSpecIntel OneAPIXilinx Vitis

这种方法试图从现有软件语言(通常是C++)中的命令描述生成硬件。它们依赖于控制流分析和聪明的编译器将这种描述转换为实际执行操作硬件。这些方法的核心问题是过度依赖这种编译器智能。这通常意味着需要调整编译器指令,直到编译器实际输出你最初想要的硬件。在某些情况下,甚至可能无法表达你打算表达的硬件,因为编译器设计者没有提供它。最后一击是,对这种生成的硬件进行优化几乎是不可能的。像Intel Quartus和Vivado这样的强大综合工具及其定时分析器都是不可用的。权衡是资源使用效率低下和较低的时钟速度。

关于企业HLS流程必须说的最后一件事是,“可移植性”的承诺是绝对不真实的。这些系统只是围绕各自平台构建围墙花园的更多尝试。从英特尔放弃他们曾经更开放的OpenCL前端,转而采用他们平台绑定的Intel OneAPI(有趣的是,这只是一个围绕旧OpenCL代码库的薄包装)这一事实就可以看出。如果我觉得有点酸,那是因为我是。

嵌入式语言,如ChiselSpinalHDL

如果过于挑剔,实际上它们并不是真正的“语言”,而更像是现有软件语言(通常是Scala)中的硬件构造库。尽管如此,这种风格也有其合理的论据。为什么要在已经存在广泛使用的软件语言时,还要发明一种新的元语言来生成硬件呢?以下是我反对这种方法的论点,但可以总结为语言设计者通过减少编译器开发时间来牺牲最终产品的可用性。

  • 没有特定的硬件语言抽象。抽象必须建立在Scala的面向对象或函数式基础之上。常规的Scala构造不对应于硬件,因此必须引入函数(如when()用于“if”)来模仿硬件。
  • 为这种语言提供特定的硬件工具很困难。无法在代码中悬停变量并从中获取特定的硬件信息,因为LSP是针对Scala的,而不是Chisel。此外,由于没有直接的编辑器反馈来验证错误的硬件,因此编辑-测试-调试周期会更长。
  • 最后,有一个哲学问题:“现代软件语言的全部功能对于硬件设计真的有必要吗?”高阶函数、动态内存分配或面向对象编程对于生成硬件是必要的吗?在实践中,99%的硬件生成代码都是简单的循环和条件语句。任何比这更复杂的东西都不应该在每次编译时都发生。

新的硬件设计语言,如TL-VerilogSpadeFilamentRustHDL以及现在的SUS

上述关于其他硬件设计风格的看法与构建这些新硬件设计语言的同事的看法相同。它们之间的主要区别是哲学上的:应该抽象哪些常见的硬件构造和概念?

这些(包括SUS)都做出了一个重大决定,那就是全力以赴于同步硬件。时钟成为一个基本语言构造,而不是一条普通线。它们大多数也共享的一个特点是受Rust启发的语法,并且是用Rust编写的。

通过示例展示主要功能

通过延迟计数进行流水线化

module pow17 {
    interface pow17 : int i -> int o 
        int i2  = i * i
    reg int i4  = i2 * i2
        int i8  = i4 * i4
    reg int i16 = i8 * i8
            o   = i16 * i
}

Registers can be inserted

使用生成代码的FIZZ-BUZZ查找表

module fizz_buzz_gen {
    interface fizz_buzz_gen : int v -> int fb 
    gen int FIZZ = 15
    gen int BUZZ = 11
    gen int FIZZ_BUZZ = 1511
    gen int TABLE_SIZE = 256

    gen int[TABLE_SIZE] lut
    
    for int i in 0..TABLE_SIZE {
        gen bool fizz = i % 3 == 0
        gen bool buzz = i % 5 == 0
        
        gen int tbl_fb
        if fizz & buzz {
            tbl_fb = FIZZ_BUZZ
        } else if fizz {
            tbl_fb = FIZZ
        } else if buzz {
            tbl_fb = BUZZ
        } else {
            tbl_fb = i
        }

        lut[i] = tbl_fb
    }

    fb = lut[v]
}

最终,生成代码被执行,结果是查找表。

(时钟-)域用于分离逻辑上不同的流水线

要使用这个功能,您真的必须使用LSP。编译器的语义分析在编程时提供了重要的视觉反馈,这使得理解这一点变得容易得多。

在这个示例中,我们创建了一个具有读取端口和写入端口的内存块。该模块有两个域:读取接口域和写入接口域。设计中的每个线都是这些域之一的一部分(如果没有连接到任一接口,则是匿名域)。除非显式通过域交叉原语传递,否则不允许信号从一个域跨越到另一个域。

Dual Port Memory

路线图

主要里程碑

  • Tree Sitter作为解析前端
  • 任意流水线全流程
  • 任意单时钟全流程
  • 任意多时钟全流程
  • 生成代码
  • 生成参数
  • 类型模板

语言功能

  • 基本分词器
  • 基本语法错误报告
  • 带信息的语法错误报告
  • 在终端中的基本分词器高亮显示
  • 局部变量和类型名称高亮
  • 数组语法
  • 函数调用语法
  • 一元和二元运算符
  • 可解析乘加流水线
  • 可解析Blur2滤波器
  • 条件语句
  • 延迟指定符
  • 删除分号
  • 通过字段名访问模块的输入/输出
  • 数组切片
  • 边界指定符
  • 结构体
  • 条件绑定
  • 生成变量和赋值
  • 生成条件
  • 生成for循环
  • 生成while循环
  • 生成参数
  • 生成默认参数
  • 类型参数
  • 生成断言
  • 多接口语法
  • 本地模块集成语法
  • 固有模块
  • 可解析FIFO实现
  • 时钟域交叉
  • 节奏语法
  • 接口生成器语法

性能、链接和名称解析

  • 命名空间
  • 单文件名称解析
  • 多文件名称解析
  • 增量解析
  • 增量编译
  • 多线程解析
  • 多线程编译

安全性

  • 基本类型检查(bools,ints,arrays等)
  • 接口的类型
  • 整数和数组边界检查
  • 冲突赋值(如在单个周期内调用同一模块两次,对单个变量的多次赋值)

推断

  • 模板类型推断
  • 生成参数推断
  • 延迟计数推断

延迟计数

  • 基本延迟赋值算法
  • 净正延迟周期错误
  • 不连续节点错误
  • 不可确定的端口延迟
  • 延迟计数使用延迟指定符
  • 仅输出模块的延迟
  • 延迟计数在任意算法起始节点上是不可变的(并非完全如此,某些起始节点可能出错。但那些不出错的节点是等效的!)
  • 集成到Verilog生成中
  • 延迟切割
  • 延迟偏移
  • “不连续输入-输出块”的延迟切割和延迟计数
  • 分割延迟

LSP

  • VSCode集成的LSP的基本LSP
  • 语法高亮
  • 域线着色
  • 错误和警告报告
  • 悬停类型信息
  • 悬停文档
  • 转到定义
  • 文件创建/删除/重命名
  • 显示最后生成值
  • 查找所有引用
  • 高亮显示
  • 重命名
  • 基本代码补全
  • 端口代码补全
  • 每行资源利用率报告

代码生成

  • 表达式展平
  • 可生成乘加流水线的Verilog
  • 可生成Blur2滤波器的Verilog
  • 可生成FIFO的Verilog
  • 多时钟模块
  • 子模块的时钟跟踪

SUS中可以做的有趣项目

  • 位串行矩阵乘法
  • Dedekind内核端口
  • 稀疏矩阵乘法
  • RISC-V CPU

通过接口断言实现安全性(PDL风格的断言)

  • btor2?
  • 语言语法
  • 它有多强大?
  • 从供应商工具中提取时序故障

模拟

  • 基本测试平台
  • “可视化”

架构

Architecture of the SUS Compiler

长期策略

https://www.youtube.com/watch?v=XZ3w_jec1v8(Evan Czaplicki(Strange Loop 2023)的《程序设计语言的经济学》)

依赖项

~6.5–9MB
~168K SLoC