如果关注 FPGA 开源生态,你会发现近几年越来越多开发者开始重新思考 HDL 语言本身。

一方面,越来越多的新项目开始尝试突破传统 Verilog 的开发方式。例如 Chisel 借助 Scala 构建硬件描述语言,让参数化设计、模块复用和代码维护变得更加容易;SpinalHDL 同样基于 Scala,强调工程化开发和代码可维护性。此外,还有 Amaranth(原 nMigen) 等项目,都在尝试让硬件开发拥有更高层次的抽象能力。
不过,这些方案大多建立在宿主语言(Scala、Python 等)之上,学习门槛依然不低。
最近,一个名为 Spade 的开源项目进入了不少 FPGA 开发者的视野,它选择了一条不同的路线——从零设计一门专门面向数字电路开发的编程语言。

什么是 Spade?

Spade 是一门专门用于数字硬件设计的开源语言,由 Rust 社区开发者发起,目前已经发布可用编译器,并完全开源。
它最大的特点是:不是 DSL(领域特定语言),而是一门真正独立的 HDL。
与 Chisel、SpinalHDL 不同,Spade 并不依赖 Scala 或 Python,而是拥有自己的语法、类型系统和编译器。
开发完成后,可以直接编译生成 Verilog,再交给 Vivado、Quartus、Yosys 等传统 FPGA 工具链综合。
为什么又要造一门 HDL?
传统 Verilog 已经发展几十年,但很多问题大家都很熟悉:
类型系统非常弱; 参数化设计容易写出大量重复代码; 模块接口容易连接错误; 编译器难以及时发现很多设计错误。
Spade 的目标,就是尽可能把这些问题放到编译阶段解决。
例如:
强类型检查 位宽自动推导 更严格的接口匹配 编译期错误检查 更现代的软件工程语法
对于大型 FPGA 项目来说,这意味着很多原本需要跑综合甚至上板才能发现的问题,可以在编译阶段直接报错。
更像现代编程语言
如果第一次看到 Spade 的代码,大多数人的第一感觉可能是:
它更像 Rust,而不是 Verilog。
例如模块定义更加清晰:
entity adder(a: uint<8>, b: uint<8>) -> uint<8> {
a + b
}
面向 FPGA,也兼容开源工具链
Spade 最终生成标准 Verilog,因此可以继续使用现有 FPGA 开发流程。
目前支持包括:
Vivado Quartus Yosys Verilator
以及其他支持 Verilog 的工具链
也就是说,Spade 更像是 Verilog 的"前端语言",并不会改变后端综合流程。
一个设计实例
官方有个在线网址:
https://play.spade-lang.org/
同时官方博客上也有一些设计实例:

在线设计的网页上有个实例:
use vga::vga::VgaTiming;
use vga::vga::vga_fsm;
use vga::vga::vga_output;
use std::conv::int_to_uint;
use std::io::rising_edge;
struct Color {
r: uint<8>,
g: uint<8>,
b: uint<8>
}
// ***********************************************************************
// This is the best place to start playing around with changing the output
// ***********************************************************************
entity pixel_to_color(
clk: clock,
rst: bool,
new_frame: bool,
pixel: (uint<15>, uint<15>),
sw: [bool; 8]
) -> Color {
let (x, y) = pixel;
// In order to get some movement in our design, we'll keep track of
// how many frames have passed since the start of the frame.
reg(clk) counter: uint<15> reset(rst: 0) = if new_frame {
if sw[1] {
trunc(counter + 1)
} else {
trunc(counter - 1)
}
} else {
counter
};
// Use the x and y-coordinates to generate a nice pattern. We shift it left
// to scale it a bit. This is cheaper than multiplying in hardware.
Color$(
r: trunc(x + (counter << 2)),
g: trunc(y + (counter << 3)),
b: if sw[0] {255} else {0}
)
}
// ***********************************************************************
// The pixel_to_color entity above is used together with a library for
// driving the VGA monitor. That driving happens here. You probably don't
// need to modify it
// ***********************************************************************
// This is the top module that we will connect to the simulator here,
// or to a real monitor on an FPGA. #[no_mangle] tells the Spade compiler
// to not change any names which makes mapping the signals to simulator
// or physical outputs easier.
#[no_mangle]
entity top(
// Digital hardware is driven by a clock. Every time it flips from 0 to 1,
// all the signals in our design will be re-computed
#[no_mangle] clk: clock,
#[no_mangle] sw: [bool; 8],
// These are the signals we'll send to the display
#[no_mangle] hsync: inv bool,
#[no_mangle] vsync: inv bool,
#[no_mangle] r: inv uint<8>,
#[no_mangle] g: inv uint<8>,
#[no_mangle] b: inv uint<8>,
) {
// In order to set our circuit back to its initial state on startup, we'll
// generate a reset signal to use later
let rst = inst power_on_reset(clk);
// We use a VGA library to generate the signals for driving the VGA display.
// That library is specified in swim.toml
let vga_state = inst vga_fsm(clk, rst, true, vga_timing());
let vga_out = vga_output(vga_state);
// The VGA library gives us an x and y coordinate, and it is our job to fill in
// the color at that pixel here. Some clock cycles, VGA doesn't output any pixels
// because it is busy 'syncing', which is why we need to do a match here.
let color = match vga_out.pixel {
Some((x, y)) => {
inst pixel_to_color$(
clk,
rst,
new_frame: inst rising_edge(clk, vga_out.vsync),
pixel: (int_to_uint(x), int_to_uint(y)),
sw
)
},
None => Color(0, 0, 0)
};
// Finally, we can set the VGA signals
set r = color.r;
set g = color.g;
set b = color.b;
set hsync = vga_out.hsync;
set vsync = vga_out.vsync;
}
fn vga_timing() -> VgaTiming {
VgaTiming$(
x_pixels: 640,
x_front_porch: 16,
x_sync_width: 96,
x_back_porch: 48,
y_pixels: 480,
y_front_porch: 10,
y_sync_width: 12,
y_back_porch: 33,
)
}
entity power_on_reset(clk: clock) -> bool {
reg(clk) rst_counter: uint<4> initial(5) =
if rst_counter == 0 {
0
} else {
trunc(rst_counter-1)
};
rst_counter != 0
}
通过simulate可以产看仿真结果:

还是有个问题,生成的Verilog可读性还是差一些:

其他大家自行探索吧~
一个值得关注的新方向
过去几年,HDL 正在经历一轮新的演进:
Chisel 将 Scala 引入硬件设计; SpinalHDL 强调工程化与可维护性; Amaranth 让 Python 成为硬件描述语言; ChipShell 等项目探索更现代的 SoC 开发方式;
Spade 则直接打造了一门全新的 HDL,希望从语言层面提升硬件开发体验。
它距离成为主流还有一段距离,但这种探索方向值得关注。随着 FPGA 设计规模越来越大,未来 HDL 的竞争,也许不再只是 Verilog 与 VHDL,而是谁能提供更好的抽象能力、更安全的类型系统,以及更符合现代软件工程的开发体验。
参考
项目主页:
https://spade-lang.org/
GitHub:
https://github.com/spade-lang/spade
- -THE END- -
往期精选


FPGA技术江湖广发江湖帖
无广告纯净模式,给技术交流一片净土,从初学小白到行业精英业界大佬等,从军工领域到民用企业等,从通信、图像处理到人工智能等各个方向应有尽有,QQ微信双选,FPGA技术江湖打造最纯净最专业的技术交流学习平台。
FPGA技术江湖微信交流群

加群主微信,备注姓名+学校/公司+专业/岗位进群
FPGA技术江湖QQ交流群

备注姓名+学校/公司+专业/岗位进群
