最近在学习SpinalHDL,这是一门基于Scala的硬件描述语言,粒度与Verilog/VHDL相当,但是利用了scala的许多面向对象特性以及高级抽象语言的特性,可以大大提高数字IC前端设计的效率。
由于项目需要,决定从一个最简单的加法树结构入手,练习SpinalHDL。
一个典型八输入加法树的结构如下:
通过观察可以知道,加法树是通过将输入两两相加得到中间部分和,再将部分和两两相加,直至获得最终输出。这在verilog中的实现不难,只需要定义一些存放中间部分和的信号,再写一些两两相加的逻辑即可。
但是,spinalHDL在这个例子中展现了一个非常不错的地方,即它可以使用高级抽象语言的一些特性,比如递归来实现加法树(加法树的树形结构非常适合用递归来做),且其对数据位宽的自动推断使得无需考虑的中间部分和信号位宽,代码如下:
package mylib
import spinal.core._
//inputNum为参数,代表加法树输入数据个数
class addtree (inputNum: Int) extends Component {
require(isPow2(inputNum)) //本代码只支持inputNum为2的幂次情况,实际上任意数量实现也不难
val tree_stage = log2Up(inputNum) //加法树级数
val io = new Bundle{
val ppen = in Bool
val data_in = in Vec(SInt(32 bits),inputNum) //使用Vec类型实现可变数量的port
val sum_out = out(Reg(SInt(32+tree_stage bits)))
}
//函数式编程思想
def tree(op: Vec[SInt]): Vec[SInt] = {
val n = op.length
if(n == 1) op
else {
val psum = Vec((SInt),n/2)
for(i <- 0 until n/2){
psum(i) := op(i) +^ op(i + n/2) //两两相加
}
tree(psum) //递归
}
}
when(io.ppen){
io.sum_out := tree(io.data_in)(0).resized //寄存器输出
}
}
所生成的Verilog代码如下:
module addtree (
input io_ppen,
input [31:0] io_data_in_0,
input [31:0] io_data_in_1,
input [31:0] io_data_in_2,
input [31:0] io_data_in_3,
input [31:0] io_data_in_4,
input [31:0] io_data_in_5,
input [31:0] io_data_in_6,
input [31:0] io_data_in_7,
output reg [34:0] io_sum_out,
input clk,
input reset
);
wire [32:0] _zz_7;
wire [32:0] _zz_8;
wire [32:0] _zz_9;
wire [32:0] _zz_10;
wire [32:0] _zz_11;
wire [32:0] _zz_12;
wire [32:0] _zz_13;
wire [32:0] _zz_14;
wire [33:0] _zz_15;
wire [33:0] _zz_16;
wire [33:0] _zz_17;
wire [33:0] _zz_18;
wire [34:0] _zz_19;
wire [34:0] _zz_20;
wire [32:0] _zz_1;
wire [32:0] _zz_2;
wire [32:0] _zz_3;
wire [32:0] _zz_4;
wire [33:0] _zz_5;
wire [33:0] _zz_6;
assign _zz_7 = {io_data_in_0[31],io_data_in_0};
assign _zz_8 = {io_data_in_4[31],io_data_in_4};
assign _zz_9 = {io_data_in_1[31],io_data_in_1};
assign _zz_10 = {io_data_in_5[31],io_data_in_5};
assign _zz_11 = {io_data_in_2[31],io_data_in_2};
assign _zz_12 = {io_data_in_6[31],io_data_in_6};
assign _zz_13 = {io_data_in_3[31],io_data_in_3};
assign _zz_14 = {io_data_in_7[31],io_data_in_7};
assign _zz_15 = {_zz_1[32],_zz_1};
assign _zz_16 = {_zz_3[32],_zz_3};
assign _zz_17 = {_zz_2[32],_zz_2};
assign _zz_18 = {_zz_4[32],_zz_4};
assign _zz_19 = {_zz_5[33],_zz_5};
assign _zz_20 = {_zz_6[33],_zz_6};
assign _zz_1 = ($signed(_zz_7) + $signed(_zz_8));
assign _zz_2 = ($signed(_zz_9) + $signed(_zz_10));
assign _zz_3 = ($signed(_zz_11) + $signed(_zz_12));
assign _zz_4 = ($signed(_zz_13) + $signed(_zz_14));
assign _zz_5 = ($signed(_zz_15) + $signed(_zz_16));
assign _zz_6 = ($signed(_zz_17) + $signed(_zz_18));
always @ (posedge clk) begin
if(io_ppen)begin
io_sum_out <= ($signed(_zz_19) + $signed(_zz_20));
end
end
endmodule
由于模块比较简单,就不放仿真波形啦(坏习惯
这是我使用spinalHDL写的第一个模块,模块本身虽然很简单,但是帮助我快速理解了spinalHDL相比于Verilog的一些优势。此处还要感谢Github:@RicardoNid 同学给予的帮助。
接下来还可以思考如何写:非2的幂次数量输入的加法树、每级插流水的加法树、每n级插流水的加法树。