参考文献:《verilog高级数字系统设计技术与实例分析》
在两个电路模块的交界处,有时候会出现一个电路的输出数据位宽大于另一个模块的输入数据位宽的情况,此时就需要进行数据转换。例如,在SATA控制器中,内部数据位宽为32比特,但与外部物理收发器PHY的接口通常为16比特或8比特。同样的,从PHY接受到的数据也是16或者8比特,数据交给控制器后,在其内部使用之前转换为32比特。
1.由宽到窄数据转换
1.1 工作原理
下图是数据位宽由宽到窄的示意图和数据转换波形示意图,模块A中的clkx1是由B中的clkx2二分频得到的时钟,其中clk2x为模块B的工作时钟,clkx1和clkx2为同步时钟,两者之间有一个固定的相位差,clkx1始终在相位上滞后于clkx2,因此数据从时钟域clkx1传递到clkx2时不会存在问题。
具体的传输过程如下,结合代码和流程图进行理解:
用模块B中的clkx2上升沿对A中的32位数据进行采样,将data_in的值赋给data_in_sync,然后在当clkx1为0时,使用clkx2上升沿选择data_in_sync的低16比特数据寄存在data_in_nxt中;在clkx1为1时,就选择其高16比特,最终输出data_out。
1.2 代码和仿真设计
1.2.1 设计源码
module wide_to_narrow(
input clkx1,
input clkx2,
input [31:0] data_in,
output reg [15:0] data_out
);
reg [31:0] data_in_sync;
wire [15:0] data_in_nxt;
always@(posedge clkx2)
begin
data_in_sync <= data_in;
end
assign data_in_nxt = clkx1 ? data_in_sync[31:16] :data_in_sync[15:0];
always@(posedge clkx2)
begin
data_out <= data_in_nxt;
end
endmodule
1.2.2 testbench编写
编写testbench的时候要特别注意两个clk要满足上面提到的时序要求,不能只是简单的分频,还要能满足前后的相位关系,并且尽量不要让数据的变化时刻和clk变化时刻相同。代码如下:
module tb_wide_to_narrow( );
reg clkx1;
reg clkx2;
reg [31:0] data_in ;
wire [15:0] data_out;
initial begin
clkx1 = 0;
clkx2 = 0;
data_in = 'b0;
#93 ;
repeat(10)
begin
data_in = $random;
#20;
end
#10;
$stop;
end
always #10 clkx1 = ~clkx1;
always begin
#2 ;
forever
#5 clkx2 = ~clkx2;
end
wide_to_narrow u_wide_to_narrow(
.clkx1(clkx1),
.clkx2(clkx2),
.data_in(data_in),
.data_out(data_out)
);
endmodule
1.2.3 仿真波形
2.由窄到宽数据转换
2.1 工作原理
下图是数据位宽由窄到宽的示意图,工作原理和上面的相差不大。核心原理在于通过两个中间寄存器,分别获取相邻的不同两个时刻的16位比特输入值,然后再将这两个寄存器中储存的值做位拼接操作,得到新的32比特数据。
这里不采用参考书籍中给的代码,书中的rtl中使用了#2在设计语句中,实际上只能在仿真时可以体现出延时的作用,实际电路中貌似不支持这样的写法。以#开头的延时不可综合成硬件电路延时,综合工具会忽略所有延时代码,但不会报错,所以在这样写法下生成的实际电路时不存在这个#2延时的。下图是书中给出代码。
2.2 代码和仿真设计
2.2.1 设计源码
module narrow_to_wide(
input clkx1,
input clkx2,
input[15:0] data_in,
output reg [31:0] data_out
);
reg [15:0] data_in_sync1;
reg [15:0] data_in_sync2;
wire [31:0] data_out_nxt;
assign data_out_nxt = {data_in_sync1,data_in_sync2} ;
always@(posedge clkx2)
begin
data_in_sync1 <= data_in;
data_in_sync2 <= data_in_sync1;
end
always@(posedge clkx1) begin
data_out <= data_out_nxt;
end
endmodule
2.2.2 testbench编写
编写testbench的时候同样要注意上面和clk时序相关的问题。
`timescale 1ns / 1ps
module tb_narrow_to_wide( );
reg clkx1;
reg clkx2;
reg [15:0] data_in;
wire [31:0] data_out;
initial begin
clkx1 = 0;
clkx2 = 0;
data_in = 'b0;
#54;
repeat(10) begin
data_in = $random%16'hffff ;
#10;
end
#20;
$stop;
end
always #10 clkx1 = ~clkx1;
always begin
#2;
forever
#5 clkx2 = ~clkx2;
end
narrow_to_wide u_narrow_to_wide(
.clkx1(clkx1),
.clkx2(clkx2),
.data_in(data_in),
.data_out(data_out)
);
endmodule