身为逻辑工程师总免不了信号连接,在写Verilog的时候通过VSCode的插件或者脚本来处理模块的例化,而在SpinalHDL中自然可以更便捷,本篇就SpinalHDL中如何更近一步解放生产力(偷懒)来聊一聊。
先说问题
在SpinalHDL中,信号的连接是很方便的事情,像两个AxiLite总线之间的连接一个“<>”方法即可:
case class demo extends Component{
val io=new Bundle{
val liteIn=slave(AxiLite4(32,32))
val liteOut=master(AxiLite4(32,32))
}
io.liteOut<>io.liteIn
}
像在Verilog中还是有点儿费劲的(当然SystemVerilog 中的Interface也可以做到),个人在SpinalHDL中也一直很喜欢这种极简的方式,然而像下面的例子像下面的情况就不行了:
case class demo extends Component{
val io=new Bundle{
val liteIn=slave(AxiLite4(32,32))
val liteOut=master(AxiLite4(64,64))
}
io.liteOut<>io.liteIn
当两个接口配置不一样时那么自动总线互联便会出现问题了。在SpinalHDL在两个信号进行连接的时候必须要求类型相同且位宽保持一致,否则将会报错。
如果两组来自于同一个接口类型的信号只是部分信号配置有些不同,那么在代码里一个个信号展开连接你是否愿意呢?
背后的故事
SpinalHDL的优势是当你觉得某处设计不爽时完全可以通过Scala的隐式扩展来自定义方法来使用,这一点SystemVerilog则是原不能比的。像上面的场景既然无法满足我们的需求那么不妨先看看<>操作符都做了什么。
阅读SpinalHDL中的源代码,<>的背后的autoConnectBaseImpl方法核心实现代码段为:
在<>方法的实现上,SpinalHDL会对两组接口进行展开对每个信号组执行上面的代码段进行连接。其最终调用的是":="赋值。看过文档或者熟悉SpinalHDL的小伙伴都知道:=左右两端电路类型必须相同且位宽一致,这也就是为什么当位宽不一致时无法通过<>进行连接。上面的代码里两个信号的连接主要通过判断两个信号的方向来决定是谁驱动谁。在SpinalHDL中,通过getDirection方法可以获取信号的方向(in,out,inout,null)。分析上面的代码,当两个信号都没有方向声明时那么将无法进行连接。
assignSomeByName
我们在定义接口类型信号的时候往往通过集成Bundle来实现。在Bundle类中为方便信号的连接,其定义了assignSomeByName和assignAllByName方法:
我们所在Bundle中所定义的电路类型均在elements中有所记录:
override def elements: ArrayBuffer[(String, Data)] = elementsCache
上面两种方法均是通过name来进行索引来进行信号名称的连接,从而实现两组信号的快速连接,但该方法仍无法解决当信号端口位宽不一致的场景,且只能单方向连接。
照猫画虎DIY
知道了别人是怎么做的那么我们就可以自定义的实现我们想要的功能。我们不妨通过自定义隐式扩展的形式来自定义一个更加强大的方法来实现总线的连接。
以dst表示待连接总线的信号,以src表示源段总线信号,那么则有三种情况:
- 1、dst信号存在而src对应的信号不存在
这种情况下针对dst中方向为output或者没有方向信号驱动为0。 - 2、src信号存在而对应的dst信号不存在
该情况下src信号若为output或者没有方向声明则驱动为0。 - 3、src信号、dst信号均存在
两端信号参照autoConnect方式进行连接。
通过隐式扩展,来实现上面的功能:
什么效果
来实现两个Demo:
module autoConnectDemo (
input io_liteIn_aw_valid,
output io_liteIn_aw_ready,
input [31:0] io_liteIn_aw_payload_addr,
input [2:0] io_liteIn_aw_payload_prot,
input io_liteIn_w_valid,
output io_liteIn_w_ready,
input [31:0] io_liteIn_w_payload_data,
input [3:0] io_liteIn_w_payload_strb,
output io_liteIn_b_valid,
input io_liteIn_b_ready,
output [1:0] io_liteIn_b_payload_resp,
input io_liteIn_ar_valid,
output io_liteIn_ar_ready,
input [31:0] io_liteIn_ar_payload_addr,
input [2:0] io_liteIn_ar_payload_prot,
output io_liteIn_r_valid,
input io_liteIn_r_ready,
output [31:0] io_liteIn_r_payload_data,
output [1:0] io_liteIn_r_payload_resp,
output io_liteOut_aw_valid,
input io_liteOut_aw_ready,
output [63:0] io_liteOut_aw_payload_addr,
output [2:0] io_liteOut_aw_payload_prot,
output io_liteOut_w_valid,
input io_liteOut_w_ready,
output [63:0] io_liteOut_w_payload_data,
output [7:0] io_liteOut_w_payload_strb,
input io_liteOut_b_valid,
output io_liteOut_b_ready,
input [1:0] io_liteOut_b_payload_resp,
output io_liteOut_ar_valid,
input io_liteOut_ar_ready,
output [63:0] io_liteOut_ar_payload_addr,
output [2:0] io_liteOut_ar_payload_prot,
input io_liteOut_r_valid,
output io_liteOut_r_ready,
input [63:0] io_liteOut_r_payload_data,
input [1:0] io_liteOut_r_payload_resp
);
wire alite_aw_valid;
wire alite_aw_ready;
wire [63:0] alite_aw_payload_addr;
wire [2:0] alite_aw_payload_prot;
wire alite_w_valid;
wire alite_w_ready;
wire [31:0] alite_w_payload_data;
wire [3:0] alite_w_payload_strb;
wire alite_b_valid;
wire alite_b_ready;
wire [1:0] alite_b_payload_resp;
wire alite_ar_valid;
wire alite_ar_ready;
wire [63:0] alite_ar_payload_addr;
wire [2:0] alite_ar_payload_prot;
wire alite_r_valid;
wire alite_r_ready;
wire [31:0] alite_r_payload_data;
wire [1:0] alite_r_payload_resp;
assign alite_aw_valid = io_liteIn_aw_valid;
assign io_liteIn_aw_ready = alite_aw_ready;
assign alite_aw_payload_addr = {32'd0, io_liteIn_aw_payload_addr};
assign alite_aw_payload_prot = io_liteIn_aw_payload_prot;
assign alite_w_valid = io_liteIn_w_valid;
assign io_liteIn_w_ready = alite_w_ready;
assign alite_w_payload_data = io_liteIn_w_payload_data;
assign alite_w_payload_strb = io_liteIn_w_payload_strb;
assign io_liteIn_b_valid = alite_b_valid;
assign alite_b_ready = io_liteIn_b_ready;
assign io_liteIn_b_payload_resp = alite_b_payload_resp;
assign alite_ar_valid = io_liteIn_ar_valid;
assign io_liteIn_ar_ready = alite_ar_ready;
assign alite_ar_payload_addr = {32'd0, io_liteIn_ar_payload_addr};
assign alite_ar_payload_prot = io_liteIn_ar_payload_prot;
assign io_liteIn_r_valid = alite_r_valid;
assign alite_r_ready = io_liteIn_r_ready;
assign io_liteIn_r_payload_data = alite_r_payload_data;
assign io_liteIn_r_payload_resp = alite_r_payload_resp;
assign io_liteOut_aw_valid = alite_aw_valid;
assign alite_aw_ready = io_liteOut_aw_ready;
assign io_liteOut_aw_payload_addr = alite_aw_payload_addr;
assign io_liteOut_aw_payload_prot = alite_aw_payload_prot;
assign io_liteOut_w_valid = alite_w_valid;
assign alite_w_ready = io_liteOut_w_ready;
assign io_liteOut_w_payload_data = {32'd0, alite_w_payload_data};
assign io_liteOut_w_payload_strb = {4'd0, alite_w_payload_strb};
assign alite_b_valid = io_liteOut_b_valid;
assign io_liteOut_b_ready = alite_b_ready;
assign alite_b_payload_resp = io_liteOut_b_payload_resp;
assign io_liteOut_ar_valid = alite_ar_valid;
assign alite_ar_ready = io_liteOut_ar_ready;
assign io_liteOut_ar_payload_addr = alite_ar_payload_addr;
assign io_liteOut_ar_payload_prot = alite_ar_payload_prot;
assign alite_r_valid = io_liteOut_r_valid;
assign io_liteOut_r_ready = alite_r_ready;
assign alite_r_payload_data = io_liteOut_r_payload_data[31:0];
assign alite_r_payload_resp = io_liteOut_r_payload_resp;
endmodule
module autoConnectDemo1 (
output [7:0] io_inf1In_data1,
input [7:0] io_inf1In_data2,
input [7:0] io_inf2In_data0,
input [7:0] io_inf2In_data2,
output [7:0] io_inf1Out_data0,
output [7:0] io_inf1Out_data2,
input [7:0] io_inf2Out_data1,
output [7:0] io_inf2Out_data2
);
assign io_inf1Out_data0 = 8'h0;
assign io_inf1In_data1 = 8'h0;
assign io_inf1Out_data2 = io_inf1In_data2;
assign io_inf2Out_data2 = io_inf2In_data2;
endmodule
可以很方便的实现总线连接,哪怕总线部分定义也可以自动连接。
Yes Or No
通过如此方式定义,可以在做系统设计的时候更方便的实现总线连接,但也值得注意的是在灵活的同时也要尤为注意自己设计的本意,如何用就看各位自己的设计了。
END
作者:玉骐
原文链接:https://mp.weixin.qq.com/s/NkPp7k3XZ_ow25wNAj9X8g
微信公众号:
推荐阅读
更多SpinalHDL技术干货请关注Spinal FPGA专栏。