# Python 与 CIRCT 的融合：HLS新视角

**URL:** <https://1nfinite.ai/t/python-circt-hls/183>\
**Category:** CIRCT\
**Created:** [February 13, 2025, 5:10am UTC](https://1nfinite.ai/t/python-circt-hls/183 "2025-02-13T05:10:06Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![小松鼠](https://1nfinite.ai/user_avatar/1nfinite.ai/%E5%B0%8F%E6%9D%BE%E9%BC%A0/32/147_2.png) [@小松鼠](https://1nfinite.ai/u/%E5%B0%8F%E6%9D%BE%E9%BC%A0)\
**Post date:** [February 13, 2025, 5:10am UTC](https://1nfinite.ai/t/python-circt-hls/183/1 "2025-02-13T05:10:06Z")

</div>

## 引言

随着人工智能、高性能计算等领域对定制化硬件需求的日益增长，传统的手工编写 RTL 代码的设计方法面临着巨大的挑战。高层级综合（High-Level Synthesis, HLS）作为一种能够将高级语言描述转化为硬件电路的技术，受到了越来越多的关注。本文将深入探讨一种新颖的 HLS 视角，即如何利用 Python 这种解释性语言与 CIRCT 工具链的强大组合来实现 HLS 流程。我们将展示如何将 Python 代码转化为可在 FPGA 或 ASIC 上运行的硬件电路描述，从而加速硬件设计的迭代和验证过程。此外，本文还将介绍 PyCDE(Python CIRCT Design Entry)，这是一个用于 FPGA 设计和验证的 Python API，以及 ESI (Elastic Silicon Interconnect) Dialect，它能够辅助加速器系统的构建。通过结合二者，读者可以轻松地使用 Python 与 CIRCT 进行交互，构建各种硬件设计，从而开启 HLS 的新篇章。

## HLS 概述

HLS 是一种将 **算法级描述** （如 C/C++、Python 等高级语言代码）自动转换为 **硬件描述语言** （如 Verilog、VHDL）的技术。与传统的寄存器传输级（RTL）设计方法不同，HLS 允许开发者专注于功能逻辑而非底层硬件时序，通过编译器自动完成状态机生成、资源分配和时序优化等复杂任务。

### HLS VS 传统 RTL 设计

传统硬件设计通常使用 SystemVerilog 或 VHDL 编写 RTL 代码，需要手动实现时钟周期级的状态机、数据路径和时序约束。这种方法的局限性在于：

- **开发周期长** ：需逐行编写硬件控制逻辑，代码量庞大；
- **验证成本高** ：功能修改需重新调整时序和接口；
- **算法移植困难** ：算法工程师与硬件工程师存在语言鸿沟。

HLS 通过提高抽象层级解决了这些问题：

- **提高开发效率** ：开发者可使用高级语言描述硬件行为，无需关注底层的时序细节。这种抽象提高了代码密度，减少了代码量，并降低了出错的可能性；
- **快速迭代** ：功能验证可在高级语言层面完成，减少硬件仿真时间，从而加快了设计迭代的速度从而加快了设计迭代的速度；
- **跨领域协作** ：算法工程师可以使用熟悉的编程语言表达算法，HLS 工具可以将算法代码直接转换为硬件实现，消除了算法工程师与硬件工程师之间的语言障碍。

虽然 HLS 能显著提升硬件开发效率，但在实际应用中，相较于传统的 RTL 设计仍存在一些不足之处：

- **复杂性应对不足** ：在处理复杂控制逻辑（如嵌套条件分支）或非规则算法（如递归、动态内存操作）时，可能生成效率较低甚至错误的代码；
- **优化空间受限** ：HLS 工具通常通过编译指令指导或 API 驱动优化，但选项有限，难以覆盖所有硬件场景。例如，手动调整数据通路或存储器架构可能比工具自动优化更高效；
- **功耗优化薄弱** ：对于低功耗设计和特定架构（如异构计算单元）的支持相对有限，难以实现精细的时序控制和定制化的功耗优化。

HLS 与 RTL 特性对比如下列表 1 所示：

| 特性 | SystemVerilog (RTL) | HLS (C/SystemC) | HLS (Python) |
| --- | --- | --- | --- |
| 代码密度 | 低（需描述时序细节） | 中等 | 高 |
| 开发速度 | 慢 | 快 | 极快 |
| 硬件控制粒度 | 精细（手动优化） | 中等（指令引导） | 粗粒度（API 驱动） |
| 学习曲线 | 陡峭 | 中等 | 平缓 |
| 错误检测 | 弱 | 强 | 强 |
| 灵活性 | 更为灵活 | 较弱 | 较弱 |

表 1：特性对比 HLS vs RTL

### HLS 输入语言演进：C → SystemC → Python

早期 HLS 工具主要支持 C/C++ 和 SystemC，但不同语言存在显著差异：

- **C 语言** ：通用性强，但缺乏硬件原语（如并行性、接口协议），需通过 Pragma 指令标注硬件特性；
- **SystemC** ：扩展了 C++的硬件建模能力，支持模块化设计和事务级建模（TLM），但语法复杂，学习成本高；
- **Python** ：凭借简洁语法和丰富生态（如 NumPy、AI 框架），成为新兴 HLS 输入语言。例如，PyCDE 通过 Python 绑定 CIRCT 工具链，可直接将 Python 数据结构映射为硬件模块，同时利用 Python 的交互性实现实时硬件调试。

### Python+HLS 的独特优势

在 AI 加速器、信号处理等场景中，Python 的 HLS 价值尤为突出：

- **算法即硬件** ：AI 模型（如 PyTorch）可直接转换为 FPGA/ASIC 加速器；
- **动态优化** ：利用 Python 解释器实时调整硬件参数（如并行度、流水线深度）；
- **生态融合** ：结合 Jupyter Notebook 实现可视化硬件设计流程；
- **跨层级验证** ：同一套 Python 代码既可用于软件仿真，也可生成可综合的 RTL。

## PyCDE 概述

PyCDE 是一个 Python API，旨在简化硬件相关活动。它的主要目的是使 CIRCT 的功能更容易暴露给 Python 开发人员。PyCDE 通过少量语法糖将代码映射到 CIRCT 操作，其核心工作实际上完全由 CIRCT 底层完成。功能特点如下：

- 硬件模块：硬件模块继承自 pycde.Module，通过设置类成员来定义任意数量的类型化输入和输出；
- 生成器：@generator 装饰器用于表示模块构造代码，在每个模块创建时调用一次；
- 类型和信号：“由于 CIRCT 主要面向硬件而非软件，因此它定义了自己的类型系统来描述硬件信号。PyCDE 通过 pycde.types.Type 类层次结构将这些硬件类型暴露给 Python 开发者。PyCDE 中的信号代表了目标硬件上的值，每个信号都具有特定的 Type (即硬件类型，与 Python 对象自身的 type 不同)，该 Type 存储在信号对象的 type 成员中；
- 参数化模块：为了“参数化”一个模块，只需从一个函数返回一个模块。该函数必须使用 modparams 进行装饰，以告知 PyCDE 返回的模块是一个参数化模块。modparams 装饰器执行多项操作，包括存储参数化函数以及自动派生包含参数值的模块名称（用于模块名称唯一性）。

## Python Bindings 介绍

Python Bindings 是一种允许 Python 代码调用其他语言编写的库或函数技术。因为 Python 是解释型语言，计算密集型任务（如数值计算、图像处理）时效率较低，但可通过调用 C/C++ 等编译型语言的代码，以大幅提升性能；或将已有的 C/C++ 库（如 OpenCV、TensorFlow）暴露给 Python，进而实现代码复用；亦可直接调用操作系统或硬件相关的底层接口（如 GPU 加速库 CUDA）。

### CIRCT 中的 Python Bindings 工具：pybind11

pybind11 是一个用于在 Python 和 C++ 之间进行互操作的轻量级、仅头文件的库。它允许 C++ 代码被 Python 调用，反之亦然。pybind11 简化了创建 Python 绑定的过程，减少了所需的样板代码。其主要优势如下：

- 简洁的接口和代码：pybind11 利用 C++11 及更高版本的特性，如自动类型转换、lambda 表达式和可变参数模板，提供了简洁的接口和代码；
- 高性能：pybind11 使用 C++ 的编译器来生成 Python 的 C 扩展，因此性能非常高；
- STL 集成：pybind11 可以无缝处理 STL 数据结构，如 vectors 和 dictionaries，自动在 C++ 和 Python 等价物之间进行转换；

## ESI 项目的发展历程

在 FPGA/ASIC 设计领域，芯片内部模块间的互连和通讯通常是采用临时 (ad-hoc) 的方式，线缆信号协议也常常没有标准，这会导致许多问题，例如信号监听和手动调整时容易产生混淆和错误。虽然目前有一些标准化信号协议的尝试，但各种变体依然层出不穷。此外，由于 FPGA/ASIC 设计内部中的各个逻辑块之间，互连所传输的数据类型常没有正式、统一的定义方式，而是通过数据表等非正式的文档来约定，间接表明 RTL 支持的基本类型系统存在不足。

而为了解决上述问题，微软于 2019 年 7 月发起了 ESI 项目，该项目旨在提高数据类型和接口的标准。

- **在数据类型方面** ：ESI 定义了一个丰富的、以硬件为中心的类型系统，以允许更正式的数据类型定义和强大的静态类型安全；
- **在 ABI 方面** ：ESI 提供简单的、延迟不敏感的标准化接口，以便开发者使用，并负责处理信号细节和转换。

其根本目的是解耦物理信号层与消息层，以解决 FPGA/ASIC 设计中接口和数据类型方面的挑战，提高设计的可靠性和效率。

于 2020 年 10 月，该项目核心开发者 John Demme 在 LLVM 论坛上对 ESI 建模进行了讨论，其中提到 ESI 项目被定义为一个高级硅 (FPGA/ASIC) 互连生成器，旨在提高 inter/intra/CPU 通信的抽象级别。并且 ESI 使用类型化的、延迟不敏感的片上连接，连接各个启用了 ESI 的模块；它还桥接片外，并使用类型数据创建高级 API。在默认情况下，延迟不敏感（弹性）连接允许编译器做出优化决策。此外，ESI 还支持许多自动化任务，如下：

- 跨语言通信；
- 类型检查以减少接口边界处的错误；
- 通信结构（包括时钟域交叉）的正确构建；
- 自动决策模块之间的物理信号；
- 自动生成软件 API，桥接 PCIe、网络或仿真；
- 自动进行 endian 转换；
- 基于模块之间的布局规划自动流水线，以减少时序收敛压力；
- 兼容不同带宽的模块（自动变速）；
- 在通信结构中提供类型和信号感知的调试器/监视器；
- 通用板级支持包接口；
- 可扩展服务以支持全局资源（例如，遥测）；

讨论中还提到该项目正在逐步与 CIRCT 生态进行融合，CIRCT 官方最新 Python 转换流程图如下：

 ![图1：2025年Python转换路线图](https://1nfinite.ai/uploads/default/original/1X/456879870dfb9692f43de951c34d9d1d1e257dcd.jpeg)

于 2024 年，项目核心开发者在 Cornell 大学发表了有关 ESI 项目的论文，名为_ **The ESI System Construction Compiler in 2024** _，文中主要表述了在部署 FPGA 加速器时，仍然存在的一些问题，如片上信号问题、复位信号与时钟信号问题以及主机连接问题。

## Python 细节举例

如上图图 1 所示，Python 转换路线中涉及许多方言，下面将会以附带例子的形式进行一一介绍。值得注意的是，如果想要输出 IR，需要实例化 System 类，然后调用该类下的 print()方法。以下是针对 Python 源码通过导入 PyCDE API 转换到不同方言的举例。

- **ESI 方言** ：ESI 方言是在将 ESI 项目融入 CIRCT 生态进而产生的，其核心目的与 ESI 项目保持一致（同上 ESI 发展历程中所提）。

**Python 源码** ：

```Python
from pycde import Input, Output, Module, generator
from pycde import esi
from pycde.types import Bits, Bundle, BundledChannel, Channel, ChannelDirection

import pycde

class CoerceBundleTransform(Module):
  '''
  定义了一个名为 b_in 的输入端口。这个端口的类型是 Bundle，表示它是由多个信号组合在一起的。

  BundledChannel("req", ChannelDirection.TO, Channel(Bits(32))):
    定义了名为 req 的 Bundle 通道；方向 TO 表明数据从“发送者”到“接收者”；且通道宽度为32bits

  BundledChannel("resp", ChannelDirection.FROM, Channel(Bits(8)))：
    定义了名为 resp 的 Bundle 通道；方向 FROM 表明数据从“接收者”到“发送者”；且通道宽度为8bits
  '''
  b_in = Input(
      Bundle([
          BundledChannel("req", ChannelDirection.TO, Channel(Bits(32))),
          BundledChannel("resp", ChannelDirection.FROM, Channel(Bits(8))),
      ]))

  ''' 下列的输出端口与上面的输入端口类似 '''
  b_out = Output(
      Bundle([
          BundledChannel("arg", ChannelDirection.TO, Channel(Bits(24))),
          BundledChannel("result", ChannelDirection.FROM, Channel(Bits(16))),
      ]))

  @generator
  def build(ports):
    '''
    调用coerce方法，将双向通道转换为另一种双向通道
    lambda x : x[0:24] 表明取 reg 通道的 0-23bits，并赋给 arg
    lambda x : x[0:8] 表明取 resp 通道的 0-7bits，并赋给 result 的 0-7位
    '''
    ports.b_out = ports.b_in.coerce(CoerceBundleTransform.b_out.type,
                                    lambda x: x[0:24], lambda x: x[0:8])

system = pycde.System([CoerceBundleTransform], name="CBTDemo")
system.generate()
system.print()

```

**ESI IR** ：使用 `python3 xxx.py` 即可

```Python
# RUN: python3 %s

module {
  esi.manifest.sym @CoerceBundleTransform name "CoerceBundleTransform"
  hw.module @CoerceBundleTransform(in %b_in : !esi.bundle<[!esi.channel<i32> to "req", !esi.channel<i8> from "resp"]>,
                                   out b_out : !esi.bundle<[!esi.channel<i24> to "arg", !esi.channel<i16> from "result"]>)
                                   attributes {output_file = #hw.output_file<"CoerceBundleTransform.sv", includeReplicatedOps>} {
    '''从 ESI 通道中解包数据和 valid 信号 '''
    %rawOutput, %valid = esi.unwrap.vr %result, %ready : i16
    '''从0位开始提取 8bits 数据'''
    %0 = comb.extract %rawOutput from 0 : (i16) -> i8
    '''将数据和 valid 信号封装到 ESI 通道中，用于发送数据'''
    %chanOutput, %ready = esi.wrap.vr %0, %valid : i8
    '''将多个 ESI 通道打包到一起'''
    %req = esi.bundle.unpack %chanOutput from %b_in : !esi.bundle<[!esi.channel<i32> to "req", !esi.channel<i8> from "resp"]>
    %rawOutput_0, %valid_1 = esi.unwrap.vr %req, %ready_3 : i32
    %1 = comb.extract %rawOutput_0 from 0 : (i32) -> i24
    %chanOutput_2, %ready_3 = esi.wrap.vr %1, %valid_1 : i24
    %bundle, %result = esi.bundle.pack %chanOutput_2 : !esi.bundle<[!esi.channel<i24> to "arg", !esi.channel<i16> from "result"]>
    hw.output %bundle : !esi.bundle<[!esi.channel<i24> to "arg", !esi.channel<i16> from "result"]>
  }
}

```

**Core IR** ：使用`circt-opt --esi-clean-metadata --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw`

- `--esi-clean-metadata` 用于删掉 `esi.manifest.sym @xxx`，该 IR 在 core 层是非法的；
- `--lower-esi-bundles` 用于处理 `esi.bundle.(un)pack`；
- `--lower-esi-ports` 用于将 port 列表中的 esi 方言中的类型转换为能被 core 层方言识别的类型；
- `--lower-esi-to-hw` 用于将剩余的 esi 方言转换为 core 层方言。

```Python
# RUN: circt-opt --esi-clean-metadata --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw %s

module {
  hw.module @CoerceBundleTransform(in %b_in_req : i32, in %b_in_req_valid : i1, in %b_out_result : i16, in %b_out_result_valid : i1, in %b_in_resp_ready : i1, in %b_out_arg_ready : i1, out b_in_req_ready : i1, out b_out_result_ready : i1, out b_in_resp : i8, out b_in_resp_valid : i1, out b_out_arg : i24, out b_out_arg_valid : i1) attributes {output_file = #hw.output_file<"CoerceBundleTransform.sv", includeReplicatedOps>} {
    %0 = comb.extract %b_out_result from 0 : (i16) -> i8
    %1 = comb.extract %b_in_req from 0 : (i32) -> i24
    hw.output %b_out_arg_ready, %b_in_resp_ready, %0, %b_out_result_valid, %1, %b_in_req_valid : i1, i1, i8, i1, i24, i1
  }
}

```

- **Hwarith 方言** ：专用于表示处理不同 bit 类型的算术运算，其中仅包含四则运算、比较运算以及类型转换。

参考 [SystemVerilog 在 CIRCT 上的初步探索](https://1nfinite.ai/t/systemverilog-circt/166)文中的 Comb 方言，其中也包含这些算数运算。二者的区别在于，Hwarith 方言不要求运算中的 operands 的类型保持一致，但是 Comb 方言要求 operands 的类型必须相同。

**Python 源码** ：

```Python
from pycde import Input, Output, generator, Module, System
from pycde.types import types, UInt

class Arith(Module):
  in0 = Input(types.si15)
  in1 = Input(types.ui16)

  @generator
  def construct(ports):
    add = ports.in0 + ports.in1
    sub = ports.in0 - ports.in1
    mul = ports.in0 * ports.in1
    div = ports.in0 / ports.in1

system = System([Arith], name="Arith")
system.generate()
system.print()

```

**Hwarith IR** ：使用 python3 xxx.py 即可

```Python
# RUN: python3 %s

module {
  esi.manifest.sym @Arith name "Arith"
  hw.module @Arith(in %in0 : si15, in %in1 : ui16) attributes {output_file = #hw.output_file<"Arith.sv", includeReplicatedOps>} {
    '''这里的结果为有符号 18bits，是因为考虑了进位以及符号位'''
    %0 = hwarith.add %in0, %in1 {sv.namehint = "in0_plus_in1"} : (si15, ui16) -> si18
    %1 = hwarith.sub %in0, %in1 {sv.namehint = "in0_minus_in1"} : (si15, ui16) -> si18
    %2 = hwarith.mul %in0, %in1 {sv.namehint = "in0_mul_in1"} : (si15, ui16) -> si31
    %3 = hwarith.div %in0, %in1 {sv.namehint = "in0_div_in1"} : (si15, ui16) -> si15
    hw.output
  }
}

```

**Core IR** ：使用 `circt-opt --lower-hwarith-to-hw xxx.mlir`用于将 Hwarith 方言转换为 Core 层方言

```Python
#RUN: circt-opt --lower-hwarith-to-hw %s

module {
  esi.manifest.sym @Arith name "Arith"
  hw.module @Arith(in %in0 : i15, in %in1 : i16) attributes {output_file = #hw.output_file<"Arith.sv", includeReplicatedOps>} {
    '''
    add = ports.in0 + ports.in1
    下列的comb IR是为了保证操作数有相同的类型宽度
    '''
    %0 = comb.extract %in0 from 14 : (i15) -> i1
    %1 = comb.replicate %0 : (i1) -> i3
    %2 = comb.concat %1, %in0 : i3, i15
    %c0_i2 = hw.constant 0 : i2
    %3 = comb.concat %c0_i2, %in1 : i2, i16
    %4 = comb.add %2, %3 {sv.namehint = "in0_plus_in1"} : i18

    '''sub = ports.in0 - ports.in1'''
    %5 = comb.extract %in0 from 14 : (i15) -> i1
    %6 = comb.replicate %5 : (i1) -> i3
    %7 = comb.concat %6, %in0 : i3, i15
    %c0_i2_0 = hw.constant 0 : i2
    %8 = comb.concat %c0_i2_0, %in1 : i2, i16
    %9 = comb.sub %7, %8 {sv.namehint = "in0_minus_in1"} : i18

    '''mul = ports.in0 * ports.in1'''
    %10 = comb.extract %in0 from 14 : (i15) -> i1
    %11 = comb.replicate %10 : (i1) -> i16
    %12 = comb.concat %11, %in0 : i16, i15
    %c0_i15 = hw.constant 0 : i15
    %13 = comb.concat %c0_i15, %in1 : i15, i16
    %14 = comb.mul %12, %13 {sv.namehint = "in0_mul_in1"} : i31

    '''div = ports.in0 / ports.in1'''
    %15 = comb.extract %in0 from 14 : (i15) -> i1
    %16 = comb.replicate %15 : (i1) -> i2
    %17 = comb.concat %16, %in0 : i2, i15
    %false = hw.constant false
    %18 = comb.concat %false, %in1 : i1, i16
    %19 = comb.divs %17, %18 {sv.namehint = "in0_div_in1"} : i17
    %20 = comb.extract %19 from 0 {sv.namehint = "in0_div_in1_0_to_15"} : (i17) -> i15
    hw.output
  }
}

```

- **FSM（Finite-state machine）方言** ：对电路中的有限状态机进行建模

**Python 源码** ：

```Python
from pycde import System, Input, Output, generator, Module
from pycde.common import Clock
from pycde.dialects import comb
from pycde import fsm
from pycde.types import types

class F0(fsm.Machine):
  a = Input(types.i1)
  b = Input(types.i1)
  c = Input(types.i1)

  def maj3(ports):

    '''采用异或操作实现与非门'''
    def nand(*args):
      return comb.XorOp(comb.AndOp(*args), types.i1(1))

    '''构建三级与非门组合电路'''
    c1 = nand(ports.a, ports.b)
    c2 = nand(ports.b, ports.c)
    c3 = nand(ports.a, ports.c)
    return nand(c1, c2, c3)

  idle = fsm.State(initial=True)
  (A, B, C) = fsm.States(3)

  '''
  配置状态转移规则：
  idle -> A (无条件)
  A -> B (当输入a有效时)
  B -> C (根据maj3计算结果)
  C -> idle（基于c）或A (基于b的取反运算)
  '''
  idle.set_transitions((A,))
  A.set_transitions((B, lambda ports: ports.a))
  B.set_transitions((C, maj3))
  C.set_transitions((idle, lambda ports: ports.c),
                    (A, lambda ports: comb.XorOp(ports.b, types.i1(1))))

class FSMUser(Module):
  a = Input(types.i1)
  b = Input(types.i1)
  c = Input(types.i1)
  clk = Clock()
  rst = Input(types.i1)
  is_a = Output(types.i1)
  is_b = Output(types.i1)
  is_c = Output(types.i1)

  @generator
  def construct(ports):
    fsm = F0(a=ports.a, b=ports.b, c=ports.c, clk=ports.clk, rst=ports.rst)
    ports.is_a = fsm.is_A
    ports.is_b = fsm.is_B
    ports.is_c = fsm.is_C

system = System([FSMUser])
system.generate()
system.print()

```

**FSM IR** ：使用 `python3 xxx.py` 即可

```Python
# RUN: python3 %s

module {
  esi.manifest.sym @FSMUser name "FSMUser"
  hw.module @FSMUser(in %a : i1, in %b : i1, in %c : i1, in %clk : !seq.clock, in %rst : i1, out is_a : i1, out is_b : i1, out is_c : i1) attributes {output_file = #hw.output_file<"FSMUser.sv", includeReplicatedOps>} {
    %0:4 = fsm.hw_instance "F0" @F0(%a, %b, %c), clock %clk, reset %rst : (i1, i1, i1) -> (i1, i1, i1, i1)
    hw.output %0#1, %0#2, %0#3 : i1, i1, i1
  }
  fsm.machine @F0(%arg0: i1, %arg1: i1, %arg2: i1) -> (i1, i1, i1, i1) attributes {clock_name = "clk", in_names = ["a", "b", "c"], initialState = "idle", out_names = ["is_idle", "is_A", "is_B", "is_C"], reset_name = "rst"} {
    fsm.state @idle output {
      %true = hw.constant true
      %false = hw.constant false
      %false_0 = hw.constant false
      %false_1 = hw.constant false
      fsm.output %true, %false, %false_0, %false_1 : i1, i1, i1, i1
    } transitions {
      '''无条件跳转到 A'''
      fsm.transition @A
    }
    fsm.state @A output {
      %false = hw.constant false
      %true = hw.constant true
      %false_0 = hw.constant false
      %false_1 = hw.constant false
      fsm.output %false, %true, %false_0, %false_1 : i1, i1, i1, i1
    } transitions {
      '''根据 arg0（也就是a）的值进行判断是否跳转到B'''
      fsm.transition @B guard {
        fsm.return %arg0
      }
    }
    fsm.state @B output {
      %false = hw.constant false
      %false_0 = hw.constant false
      %true = hw.constant true
      %false_1 = hw.constant false
      fsm.output %false, %false_0, %true, %false_1 : i1, i1, i1, i1
    } transitions {
      '''根据 maj3 的计算结果进行判断是否跳转到C'''
      fsm.transition @C guard {
        %0 = comb.and bin %arg0, %arg1 : i1
        %true = hw.constant true
        %1 = comb.xor bin %0, %true : i1
        %2 = comb.and bin %arg1, %arg2 : i1
        %true_0 = hw.constant true
        %3 = comb.xor bin %2, %true_0 : i1
        %4 = comb.and bin %arg0, %arg2 : i1
        %true_1 = hw.constant true
        %5 = comb.xor bin %4, %true_1 : i1
        %6 = comb.and bin %1, %3, %5 : i1
        %true_2 = hw.constant true
        %7 = comb.xor bin %6, %true_2 : i1
        fsm.return %7
      }
    }
    fsm.state @C output {
      %false = hw.constant false
      %false_0 = hw.constant false
      %false_1 = hw.constant false
      %true = hw.constant true
      fsm.output %false, %false_0, %false_1, %true : i1, i1, i1, i1
    } transitions {
      '''根据 arg2（c）的值进行判断是否跳转到 idle 初始状态'''
      fsm.transition @idle guard {
        fsm.return %arg2
      }
      '''根据 arg1（b）的值取反进行判断是否跳转到 A'''
      fsm.transition @A guard {
        %true = hw.constant true
        %0 = comb.xor bin %arg1, %true : i1
        fsm.return %0
      }
    }
  }
}

```

- **Core IR + SV IR** ：使用 `circt-opt --convert-fsm-to-sv xxx.mlir`用于将 hwarith 方言转换为 Core 层方言 + SV 方言

```Python
# RUN: circt-opt --convert-fsm-to-sv %s

module {
  '''定义一个类型域，用于管理有限状态机(FSM)的状态枚举类型定义'''
  hw.type_scope @fsm_enum_typedecls {
    hw.typedecl @F0_state_t : !hw.enum<idle, A, B, C>
  }

  '''将类型域 @fsm_enum_typedecls 的定义输出到文件 "fsm_enum_typedefs.sv"'''
  emit.file "fsm_enum_typedefs.sv" {
    emit.ref @fsm_enum_typedecls
  }

  '''定义一个 emit 片段，用于在生成的 SystemVerilog 代码中包含'''
  emit.fragment @FSM_ENUM_TYPEDEFS {
    sv.verbatim "`include \22fsm_enum_typedefs.sv\22"
  }
  esi.manifest.sym @FSMUser name "FSMUser"
  hw.module @FSMUser(in %a : i1, in %b : i1, in %c : i1, in %clk : !seq.clock, in %rst : i1, out is_a : i1, out is_b : i1, out is_c : i1) attributes {output_file = #hw.output_file<"FSMUser.sv", includeReplicatedOps>} {
    %F0.out0, %F0.out1, %F0.out2, %F0.out3 = hw.instance "F0" @F0(in0: %a: i1, in1: %b: i1, in2: %c: i1, clk: %clk: !seq.clock, rst: %rst: i1) -> (out0: i1, out1: i1, out2: i1, out3: i1)
    hw.output %F0.out1, %F0.out2, %F0.out3 : i1, i1, i1
  }
  hw.module @F0(in %in0 : i1, in %in1 : i1, in %in2 : i1, out out0 : i1, out out1 : i1, out out2 : i1, out out3 : i1, in %clk : !seq.clock, in %rst : i1) attributes {emit.fragments = [@FSM_ENUM_TYPEDEFS]} {
    '''
    定义枚举常量 idle，类型为 F0_state_t，并定义一个寄存器 %to_idle, 并初始化为idle
    类似地，定义枚举常量 A，类型为 F0_state_t，以及对应的寄存器 %to_A
    定义枚举常量 B，类型为 F0_state_t，以及对应的寄存器 %to_B
    定义枚举常量 C，类型为 F0_state_t，以及对应的寄存器 %to_C
    '''
    %idle = hw.enum.constant idle : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %to_idle = sv.reg sym @idle : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>
    sv.assign %to_idle, %idle : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %0 = sv.read_inout %to_idle : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>
    %A = hw.enum.constant A : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %to_A = sv.reg sym @A : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>
    sv.assign %to_A, %A : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %1 = sv.read_inout %to_A : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>
    %B = hw.enum.constant B : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %to_B = sv.reg sym @B : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>
    sv.assign %to_B, %B : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %2 = sv.read_inout %to_B : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>
    %C = hw.enum.constant C : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %to_C = sv.reg sym @C : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>
    sv.assign %to_C, %C : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %3 = sv.read_inout %to_C : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>

    %state_next = sv.reg : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>
    %4 = sv.read_inout %state_next : !hw.inout<typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>>

    '''
    定义一个名为 state_reg 的 compreg, 它的输出为 %4，时钟为 %clk，复位信号为 %rst，复位值为 %0 (idle)
    '''
    %state_reg = seq.compreg sym @state_reg %4, %clk reset %rst, %0 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %true = hw.constant true
    %false = hw.constant false
    %false_0 = hw.constant false
    %false_1 = hw.constant false
    %false_2 = hw.constant false
    %true_3 = hw.constant true
    %false_4 = hw.constant false
    %false_5 = hw.constant false

    '''状态机状态转移逻辑'''
    %5 = comb.mux %in0, %2, %1 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %false_6 = hw.constant false
    %false_7 = hw.constant false
    %true_8 = hw.constant true
    %false_9 = hw.constant false
    %6 = comb.and bin %in0, %in1 : i1
    %true_10 = hw.constant true
    %7 = comb.xor bin %6, %true_10 : i1
    %8 = comb.and bin %in1, %in2 : i1
    %true_11 = hw.constant true
    %9 = comb.xor bin %8, %true_11 : i1
    %10 = comb.and bin %in0, %in2 : i1
    %true_12 = hw.constant true
    %11 = comb.xor bin %10, %true_12 : i1
    %12 = comb.and bin %7, %9, %11 : i1
    %true_13 = hw.constant true
    %13 = comb.xor bin %12, %true_13 : i1
    %14 = comb.mux %13, %3, %2 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %false_14 = hw.constant false
    %false_15 = hw.constant false
    %false_16 = hw.constant false
    %true_17 = hw.constant true
    %true_18 = hw.constant true
    %15 = comb.xor bin %in1, %true_18 : i1
    %16 = comb.mux %15, %1, %3 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
    %17 = comb.mux %in2, %0, %16 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>

    '''定义用于存储输出的寄存器'''
    %output_0 = sv.reg : !hw.inout<i1>
    %output_1 = sv.reg : !hw.inout<i1>
    %output_2 = sv.reg : !hw.inout<i1>
    %output_3 = sv.reg : !hw.inout<i1>

    '''描述状态机跳转关系'''
    sv.alwayscomb {
      sv.case %state_reg : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
      case idle: {
        sv.bpassign %state_next, %1 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
        sv.bpassign %output_0, %true : i1
        sv.bpassign %output_1, %false : i1
        sv.bpassign %output_2, %false_0 : i1
        sv.bpassign %output_3, %false_1 : i1
      }
      case A: {
        sv.bpassign %state_next, %5 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
        sv.bpassign %output_0, %false_2 : i1
        sv.bpassign %output_1, %true_3 : i1
        sv.bpassign %output_2, %false_4 : i1
        sv.bpassign %output_3, %false_5 : i1
      }
      case B: {
        sv.bpassign %state_next, %14 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
        sv.bpassign %output_0, %false_6 : i1
        sv.bpassign %output_1, %false_7 : i1
        sv.bpassign %output_2, %true_8 : i1
        sv.bpassign %output_3, %false_9 : i1
      }
      case C: {
        sv.bpassign %state_next, %17 : !hw.typealias<@fsm_enum_typedecls::@F0_state_t, !hw.enum<idle, A, B, C>>
        sv.bpassign %output_0, %false_14 : i1
        sv.bpassign %output_1, %false_15 : i1
        sv.bpassign %output_2, %false_16 : i1
        sv.bpassign %output_3, %true_17 : i1
      }
      default: {
      }
    }
    %18 = sv.read_inout %output_0 : !hw.inout<i1>
    %19 = sv.read_inout %output_1 : !hw.inout<i1>
    %20 = sv.read_inout %output_2 : !hw.inout<i1>
    %21 = sv.read_inout %output_3 : !hw.inout<i1>
    hw.output %18, %19, %20, %21 : i1, i1, i1, i1
  }
}

```

上面所举例子中均涉及 Core 层方言，可分为以下三种方言：

- Seq：用于表示带复位信号和使能信号的寄存器逻辑。例如`seq.comreg.ce` 用于描述具有使能信号和复位信号的同步时序逻辑，而`seq.firreg`则表示带有复位信号的同步或异步时序逻辑。
- HW：专用于表示硬件设计中的公共特性。该方言定义的 Operations 不涉及组合逻辑、时序逻辑和线网之间的连接逻辑，因此具有高度的通用性，并能够与其他方言灵活结合使用。这使得 HW 方言不依赖于特定的硬件语言。例如，它可以有效地表示硬件中的 module、instance 和 port；
- Comb：与 HW 方言类似，提供一种不依赖于特定硬件语言的通用表示方式。专注于表示硬件电路中的组合电路，其设计理念强调简洁性，力求用更少的 Operations 表达更多的功能。例如，通过将 x 与全 1 进行异或运算，可以有效地表示一元位运算符（~x）。

此外，在 FSM 转换路线中还涉及到 SV 方言，该方言旨在直接对 SystemVerilog 语言的语法进行建模，并通过 ExportVerilog pass 轻松生成 SystemVerilog。由于该方言专注于输出 SystemVerilog，因此生成的 IR 采用“AST”风格的表示形式。此外，该方言可以与 Core 层的其他方言混合使用，因此它并不具备独立的组合逻辑、时序逻辑以及模块等常见功能。

后续对 Core 层方言的转换逻辑可以参考 [SystemVerilog 在 CIRCT 上的初步探索](https://1nfinite.ai/t/systemverilog-circt/166)或 [Chisel 与 CIRCT 的无缝集成](https://1nfinite.ai/t/chisel-circt/169)中对 Johnson 计数器的举例。

## 总结

本文深入探讨了 Python 与 CIRCT 的融合在 HLS 领域带来的全新视角。通过 PyCDE 提供的 Python API，开发者可以方便地利用 Python 语言进行硬件设计描述和验证，从而加速 HLS 流程。 ESI Dialect 则为构建复杂加速器系统提供了强大的互连支持。 这种 Python + CIRCT 的组合不仅降低了硬件设计的门槛，还充分利用了 Python 语言的灵活性和丰富的生态系统，为 HLS 带来了前所未有的便利性和效率。 随着相关技术的不断发展，我们有理由相信，Python 与 CIRCT 的融合将在未来的 FPGA 和 ASIC 设计中发挥越来越重要的作用，推动硬件设计的创新和发展。

**参考资料**

1. [Elastic Silicon Interconnects Abstracting Communication in Accelerator Design](https://capra.cs.cornell.edu/latte21/paper/8.pdf)

2. [ESI Dialect](https://circt.llvm.org/docs/Dialects/ESI/)

3. [Elastic Silicon Interconnect (ESI) modeling](https://discourse.llvm.org/t/elastic-silicon-interconnect-esi-modeling/1992)

4. [高层次综合技术原理浅析](https://adaptivesupport.amd.com/s/article/1186018?language=zh_CN)

5. [HLS与RTL语言使用情况调查](https://mp.weixin.qq.com/s?__biz=MzU1MzI4ODA5OA==&mid=2247486786&idx=1&sn=c38a9f1a3007a5721a41345b2dfcb875&chksm=fbf4658fcc83ec991e264b5b7ed81975e3208a7bdab738b37eff3384db0eaf929308f75df295&mpshare=1&scene=1&srcid=&sharer_sharetime=1587267958615&sharer_shareid=0222f19fc021406bda28f00b8524255e&key=cf6ab1fd6f95a2707ac1f6e3d76abd596359d7cf0c85bb9923830ea952da7fcf32dea46c4c6dc5836a157b6eb49a39de4395ffa62e0c7c746f86a1a9fcaf85c6d8e526f2a4de784513fae4012eb85269&ascene=1&uin=MTk0NTA2NDc2OA%3D%3D&devicetype=Windows+10&version=62080079&lang=zh_CN&exportkey=Aeloywr%2FX6GtnP4lh9zT1wY%3D&pass_ticket=vCUUL2TzIKrqf%2FE02w22sPU7g8h%2FOgT1nmdi%2BDFRVajHsM5RrIsGMxkBylWuetT%2F)

6. [PyCDE Basics](https://circt.llvm.org/docs/PyCDE/basics/)

7. [The Elastic Silicon Interconnect dialect](https://circt.llvm.org/docs/Dialects/ESI/RationaleESI/)

8. [The ESI System Construction Compiler in 2024](https://capra.cs.cornell.edu/latte24/paper/7.pdf)

9. [What’s The Real Benefit Of High-Level Synthesis?](https://semiengineering.com/whats-the-real-benefit-of-high-level-synthesis/)
