前言
在经典的硬件设计中,众多设备可以用分时复用的方案共享同一组数据总线,这大大简化了设备间的通信接口设计,同时还能很容易的实现设备间一对多或者多对一的通信。在FPGA的HDL程序设计中,有时我们也希望能够应用数据总线方案以降低设计难度并充分利用这一方案的便利性。
在应用Verilog或者SystemVerilog编程时,映射于FPGA外部管脚的接口连线可以通过/CS、/RE、/WE等信号配合模块实现数据总线的读/写控制,在模块未取得数据总线的控制权前,模块一般向连向数据总线的输出接口赋值高阻态:'z
。然而在FPGA内部一般高阻态的赋值是不被允许的,即使编译综合通过,高阻态的赋值也会被综合器综合成0
或者1
,这会对连接在同一数据总线上的不同设备产生冲突。
为了解决这一冲突,在FPGA内部模块之间的数据总线必须使用选择器(mux)来实现.
Verilog应用选择器实现数据总线功能
所谓的选择器,实际上就是根据不同的输入选通信号选择不同模块的输出数据接口连接至数据总线。
为了应用选择器选择特定模块的输出到数据总线,需要为每个模块分配一个选通信号,这里我们约定选通信号的值为1时表示模块选通,为0时则表示模块未选通。
示例代码如下:
module dbus_module #(
parameter DATAOUT
)(
output wire[31:0] o_data
);
assign o_data = 32'(DATAOUT);
endmodule
module tb_verilog_dbus(
input wire[2:0] ics,
output wire[31:0] odata
);
wire[31:0] odata_sel[2:0]
dbus_module #(.DATAOUT(0)) dm_0(.o_data(odata_sel[0]));
dbus_module #(.DATAOUT(1)) dm_1(.o_data(odata_sel[1]));
dbus_module #(.DATAOUT(2)) dm_2(.o_data(odata_sel[2]));
assign odata = ics[0] ? odata_sel[0] :
ics[1] ? odata_sel[1] :
ics[2] ? odata_sel[2] : 32'hFFFFFFFF;
endmodule
应用SystemVerilog的interface简化数据总线的实现
观察上面的代码可见,上面的verilog代码虽然实现了数据总线的功能,但如果要增加或减少挂载至数据总线的模块,就不得不更改数据总线模块的代码。虽然可以使用parameter来参数化模块数量,但仍然缺少一些必要的灵活性,比如:挂载输出相同类型数据的不同类型的模块至数据总线,这使得数据总线模块tb_verilog_dbus的代码不得不根据需求更改。
SystemVerilog语言提供的interface恰好可以解决这一问题,interface可以添加verilog代码,且可以作为module的端口参数传递,因此我们可以将与数据总线相关的选择器和选通信号归纳至interface中,并将module的数据输出端口连接至interface的待选数据连线。示例代码如下:
interface if_dbus #(
parameter LINKED_MDL_CNT
) (
output logic[31:0] odbus
);
logic[LINKED_MDL_CNT-1:0] ocs;
wire[31:0] data2sel[LINKED_MDL_CNT-1:0];
always_comb begin
odbus = '0;
for (int i = 0; i < LINKED_MDL_CNT; i++) begin
if (ocs[i]) odbus = data2sel[i];
end
end
endinterface
应用if_dbus
示例代码如下:
module dbus_mdl1 #(
parameter DATAOUT
)(
output wire[31:0] o_data
);
assign o_data = 32'(DATAOUT);
endmodule
module dbus_mdl2 #(
parameter DATAOUT
)(
output wire[31:0] data_o
);
assign data_o = 32'(DATAOUT);
endmodule
module tb_if_dbus;
wire[31:0] dbus2exam;
if_dbus #(
.LINKED_MDL_CNT(5)
) dbus_ifi(
.odbus(dbus2exam)
);
genvar i;
generate
for (i= 0; i < 3; i++) begin:MDL1_GEN
dbus_mdl1 #(
.DATAOUT(i)
) im(
.o_data(dbus_ifi.data2sel[i])
);
end
for (i = 3; i < 5; i++) begin:MDL2_GEN
dbus_mdl2 #(
.DATAOUT(i)
) im2(
.o_data(dbus_ifi.data2sel[i])
);
end
endgenerate
// 测试总线输出数据的代码,查看dbus2exam
reg clk;
initial begin
clk = 0;
end
always #1 clk = ~clk;
reg[2:0] idxcntr;
initial begin
idxcntr = '0;
end
always_ff @(posedge clk) begin
idxcntr = idxcntr + 1;
end
always_comb begin
dbus_ifi.ocs = '0;
if (idxcntr < 5) dbus_ifi.ocs[i] = 1'b1;
end
endmodule
进阶:使用参数化的interface适配不同数据位宽的数据总线
SystemVerilog的interface是可以参数化的,这给了我们做泛化编程的机会。将数据总线所传输数据的位宽以参数的形式在interface中体现,即可用同一个模块定义来适配不同数据位宽的总线。示例代码为:
interface if_dbus #(
parameter DATA_BITW,
parameter LINKED_MDL_CNT
) (
output logic[DATA_BITW:0-1] odbus
);
logic[LINKED_MDL_CNT-1:0] ocs;
wire[DATA_BITW-1:0] data2sel[LINKED_MDL_CNT-1:0];
always_comb begin
odbus = '0;
for (int i = 0; i < LINKED_MDL_CNT; i++) begin
if (ocs[i]) odbus = data2sel[i];
end
end
endinterface
应用if_dbus
示例代码则变更如下:
module dbus_mdl1 #(
parameter DATAOUT
)(
output wire[31:0] o_data
);
assign o_data = 32'(DATAOUT);
endmodule
module dbus_mdl2 #(
parameter DATAOUT
)(
output wire[31:0] data_o
);
assign data_o = 32'(DATAOUT);
endmodule
module tb_if_dbus;
wire[31:0] dbus2exam;
if_dbus #(
.DATA_BITW(32), // 增加对参数DATA_BITW的实例化
.LINKED_MDL_CNT(5)
) dbus_ifi(
.odbus(dbus2exam)
);
genvar i;
generate
for (i= 0; i < 3; i++) begin:MDL1_GEN
dbus_mdl1 #(
.DATAOUT(i)
) im(
.o_data(dbus_ifi.data2sel[i])
);
end
for (i = 3; i < 5; i++) begin:MDL2_GEN
dbus_mdl2 #(
.DATAOUT(i)
) im2(
.o_data(dbus_ifi.data2sel[i])
);
end
endgenerate
// 测试总线输出数据的代码,查看dbus2exam
reg clk;
initial begin
clk = 0;
end
always #1 clk = ~clk;
reg[2:0] idxcntr;
initial begin
idxcntr = '0;
end
always_ff @(posedge clk) begin
idxcntr = idxcntr + 1;
end
always_comb begin
dbus_ifi.ocs = '0;
if (idxcntr < 5) dbus_ifi.ocs[i] = 1'b1;
end
endmodule