此篇文章基于德州仪器TLV5618A型号的DAC。根据文章关于小梅哥ADC128S022驱动设计的思考的排布形式,作如下文章。
需要说明的是,DAC驱动设计的各种分析也适用于ADC驱动,毕竟两者驱动本质上都是线性序列机。
文章目录
1. 开篇,各模块连接关系
这些连接关系比ADC驱动简单的多,从左到右直来直去,没有给User_Ctrl反馈的部分,也就是说testbench的编写将会简单很多。
其中start
为外部使能信号,dac_data[15:0]
为16位数据,由于该DAC是双通道输出,更有速度的切换等控制,故需要额外的4位作为控制位,剩下的12位依然为数据位,可与小梅哥的AC620开发板上的ADC相联通。
关于外部使能信号的使用技巧,详见“必须为脉冲形式的start信号”
2. DAC时序分析
注意,DAC的时序中有一条时序限制,即tsu,通过查阅手册可知,该值只需大于等于10ns即可。而该型号DAC工作频率sclk
最高为20MHz,此为后话。
根据时序图的状态划分,再有ADC序列机的编写基础,很容易列出如下表格并编写相应的case语句。
分析:
- 由于复位或DAC系统未使能时可将信号置为:
csn =1;
sclk=0;
din =0;
而时刻0正好与复位情况重合,故可去掉时刻0;- 时刻1和时刻2之间仅仅是将sclk拉高,且由于SCLK的最大频率为20MHz,即50ns,即使半个周期的25ns超过tsu的最小值10ns,故可将时刻1和时刻2重合,即拉低csn的同时拉高sclk。
- 同理第34,35时刻,出于低功耗的考虑,可将其去掉,直接将时刻36拿来用,也就是说,在sclk的第16个下降沿之后,sclk保持不变,同时csn直接拉高。
综上,可得新的csn时钟信号,如图红线所示。
该时序图对应时序表格如下:
可见,相比于第一种时序,后者的时序少了4个状态,当然这对低功耗的设计并没有起到多大作用 -_-!,只是一个思路而已。
dac_driver.v代码如下:
module dac_driver(
//I
clk_50M ,
rstn ,
start ,
dac_data,
//O
conv_done,
csn ,
din ,
sclk ,
);
input clk_50M ;
input rstn ;
input start ;
input [15:0] dac_data;
output conv_done;
output csn ;
output din ;
output sclk ;
parameter CNT_NUM = 4 ;
reg [2:0] div_cnt ;
reg en ;
reg [5:0] sclk_edge_cnt ;
reg conv_done ;
reg csn ;
reg din ;
reg sclk ;
//start to en
always @ (posedge clk_50M or negedge rstn) begin
if (!rstn)
en <= 1'b0;
else if (start)
en <= 1'b1;
else if (conv_done)
en <= 1'b0;
else
en <= en;
end
always @ (posedge clk_50M or negedge rstn) begin
if (!rstn)
div_cnt <= 3'b0;
else if (en) begin
if (div_cnt == CNT_NUM/2 - 1)
div_cnt <= 3'b0;
else
div_cnt <= div_cnt + 1'b1;
end
else
div_cnt <= 3'b0;
end
wire sclk2x = (div_cnt == CNT_NUM/2 - 1);
//sclk_edge_cnt
always @ (posedge clk_50M or negedge rstn) begin
if (!rstn)
sclk_edge_cnt <= 6'd0;
else if (en) begin
if (sclk2x) begin
if (sclk_edge_cnt == 6'd32)
sclk_edge_cnt <= 6'd0;
else
sclk_edge_cnt <= sclk_edge_cnt + 1'b1;
end
else
sclk_edge_cnt <= sclk_edge_cnt;
end
else
sclk_edge_cnt <= 6'd0;
end
//conv_done
always @ (posedge clk_50M or negedge rstn) begin
if (!rstn)
conv_done <= 1'b0;
else if (sclk2x) begin
if (sclk_edge_cnt == 6'd32)
conv_done <= 1'b1;
else
conv_done <= 1'b0;
end
else
conv_done <= 1'b0;
end
reg [15:0] dac_data_r;
always @ (posedge clk_50M or negedge rstn) begin
if (!rstn) begin
dac_data_r <= 16'b0;
else
dac_data_r <= dac_data;
end
always @ (posedge clk_50M or negedge rstn) begin
if (!rstn) begin
csn <= 1'b1;
sclk <= 1'b0;
din <= 1'b0;
end
else if (en) begin
if (sclk2x) begin
case (sclk_edge_cnt)
6'd0 : begin csn<= 1'b0; sclk <= 1'b1; din <= dac_data_r[15]; end
6'd1 : begin sclk <= 1'b0; end
6'd2 : begin sclk <= 1'b1; din <= dac_data_r[14]; end
6'd3 : begin sclk <= 1'b0; end
6'd4 : begin sclk <= 1'b1; din <= dac_data_r[13]; end
6'd5 : begin sclk <= 1'b0; end
6'd6 : begin sclk <= 1'b1; din <= dac_data_r[12]; end
6'd7 : begin sclk <= 1'b0; end
6'd8 : begin sclk <= 1'b1; din <= dac_data_r[11]; end
6'd9 : begin sclk <= 1'b0; end
6'd10 : begin sclk <= 1'b1; din <= dac_data_r[10]; end
6'd11 : begin sclk <= 1'b0; end
6'd12 : begin sclk <= 1'b1; din <= dac_data_r[ 9]; end
6'd13 : begin sclk <= 1'b0; end
6'd14 : begin sclk <= 1'b1; din <= dac_data_r[ 8]; end
6'd15 : begin sclk <= 1'b0; end
6'd16 : begin sclk <= 1'b1; din <= dac_data_r[ 7]; end
6'd17 : begin sclk <= 1'b0; end
6'd18 : begin sclk <= 1'b1; din <= dac_data_r[ 6]; end
6'd19 : begin sclk <= 1'b0; end
6'd20 : begin sclk <= 1'b1; din <= dac_data_r[ 5]; end
6'd21 : begin sclk <= 1'b0; end
6'd22 : begin sclk <= 1'b1; din <= dac_data_r[ 4]; end
6'd23 : begin sclk <= 1'b0; end
6'd24 : begin sclk <= 1'b1; din <= dac_data_r[ 3]; end
6'd25 : begin sclk <= 1'b0; end
6'd26 : begin sclk <= 1'b1; din <= dac_data_r[ 2]; end
6'd27 : begin sclk <= 1'b0; end
6'd28 : begin sclk <= 1'b1; din <= dac_data_r[ 1]; end
6'd29 : begin sclk <= 1'b0; end
6'd30 : begin sclk <= 1'b1; din <= dac_data_r[ 0]; end
6'd31 : begin sclk <= 1'b0; end
6'd32 : begin csn <= 1'b1; end
default : begin csn <= 1'b1; sclk <= 1'b0; din <= 1'b0; end
endcase
end
end
else begin
csn <= 1'b1;
sclk <= 1'b0;
din <= 1'b0;
end
end
endmodule
以小梅哥的testbench加持:
`timescale 1ns/1ns
`define p 20
module dac_driver_tb;
reg clk_50M ;
reg rstn ;
reg start ;
reg [15:0] dac_data ;
wire conv_done ;
wire csn ;
wire din ;
wire sclk ;
//instance
dac_driver #(
.CNT_NUM(8)
)
U_dac_driver(
.clk_50M (clk_50M ),
.rstn (rstn ),
.start (start ),
.dac_data (dac_data ),
.conv_done (conv_done ),
.csn (csn ),
.din (din ),
.sclk (sclk )
);
//tb
initial clk_50M = 1'b0 ;
always #(`p/2) clk_50M = ~clk_51M ;
initial begin
rstn = 1'b0 ;
start_dac = 1'b0 ;
dac_data = 16'b0 ;
#(`p * 5)
rstn = 1'b1;
#(`p * 10);
rstn = 1;
#(`p * 10);
//=============================
dac_data = 16'hC_AAA;
start = 1;
#(`p * 1);
start = 0;
#(`p * 10);
wait(conv_done);
#(`p * 1000);
//=============================
dac_data = 16'h4_555;
start = 1;
#(`p * 1);
start = 0;
#(`p * 10);
wait(conv_done);
#(`p * 1000);
//=============================
dac_data = 16'h1_555;
start = 1;
#(`p * 1);
start = 0;
#(`p * 10);
wait(conv_done);
#(`p * 1000);
//=============================
dac_data = 16'hf_555;
start = 1;
#(`p * 1);
start = 0;
#(`p * 10);
wait(conv_done);
#(`p * 1000);
$stop;
end
endmodule
可得如下仿真结果
还有剩下两个dac_data
的数值仿真,在此不一一列举。
3. 必须为脉冲形式的start信号
实际中,我们需要DAC连续工作,比如极端情况下,只要板子一开机就工作,那么将start
信号一直拉高,即在testbench中在复位之后将其置为start = 1'b1
,直到仿真结束,仿真结果如下:
可见在一次数据转换完之后,立马进行下一次的数据转换,信号en
一直为高,同时conv_done
信号虽然产生了脉冲,但是并没有让en
信号拉低。也就是说start
信号一直为高已经在电路逻辑上(详见以下语句)出现了错误,虽然仿真并没有报错。
//start to en
always @ (posedge clk_50M or negedge rstn) begin
if (!rstn)
en <= 1'b0;
else if (start)
en <= 1'b1;
else if (conv_done)
en <= 1'b0;
else
en <= en;
end
另外,当把仿真时刻拉倒dac_data = 16'hCAAA
和dac_data = 16'h4555
两数据交替时刻(如下图所示)可发现:
- 当数据
16'hCAAA
没有转换完,下一个数据16'h4555
就来了; 16'h4555
到来后的下一个sclk上升沿,本应该拉高的din
信号此时拉低,说明数据切换的时候两数据发生了串扰,导致DAC输入不稳定。- 直到下一次转换数据的周期开始时,DAC才输入正常的
din
。
以上仿真现象说明不可将start信号一直置为高电平,否则DAC的输入din
不稳定,影响DAC输出。
实际调试中,本人将start
信号接到轻触按键S0上,即start
一直为高电平,并且调用ISSP软件对DAC进行调试,发现
- 当在
source
中先输入dac_data = 16'hC000
(对DAC的输入进行配置,配置C
表示DAC的输出只在通道OUTA上进行变化) - 再输入
dac_data = 16'h47FF
(对DAC的输入进行配置,配置4
表示DAC的输出只在通道OUTB上进行变化) - 发现本应保持不变的输出通道OUTA却发生了变化,用电压表测量之后,本该保持为0V的通道OUTA,电压却上升了一些。
并且更有意思的是,通过反复输入dac_data = 16'hC000
和dac_data = 16'h47FF
,发现每次OUTA的输出电压并不一样。通过反复检查dac_driver.v代码,最终找出了问题所在,即信号start
并不应该总为高电平,而应是一个脉冲信号。
4. 驱动的改写
分析:
start信号为脉冲信号的条件(或前提)是,当输入的dac_data
数据发生变化,就应该启动dac_driver对DAC进行驱动。由于dac_data_r
比dac_data
正好晚一拍,故可用这两个寄存器来产生start
脉冲信号;由于要对start
进行赋值,故start
信号不再为input类型,而应为reg型或wire型。
assign start = (! (dac_data_r == dac_data) ) ;
又,虽然脉冲产生了,但在dac_driver.v中,这仅仅是个内部信号,若想从外部拉一个信号对整个dac_driver以及DAC进行控制,则应该再添加一个外部控制信号,命名为start_dac
,于是,原先接到轻触按键S0上的start
信号如今便接到了input start_dac
信号上。
dac_driver.v中第50行的代码改为如下方式,同时更改相应信号的类型:
assign start = ( !(dac_data_r == dac_data) );
always @ (posedge clk_50M or negedge rstn) begin
if (!rstn)
en <= 1'b0;
else if (start_dac) begin
if (start)
en <= 1'b1;
else if (conv_done)
en <= 1'b0;
else
en <= en;
end
else
en <= 1'b0;
end
在testbench中,添加reg start_dac
信号,将第51行到63行改为:
initial begin
rstn = 1'b0 ;
start_dac = 1'b0 ;
dac_data = 16'b0 ;
#(`p * 5)
rstn = 1'b1;
#(`p * 10);
rstn = 1;
#(`p * 10);
start_dac = 1'b1;
并将第66行到117行中的start
信号去掉(也去掉最开始声明的reg start
信号),同时,testbench中tb与dac_driver的端口连接稍作修改,将对应的start
替换成start_dac
;为了留有一定的裕度,将dac_driver.v稍作改动:sclk_edge_cnt = 6'd33
时,conv_done
信号拉高(DAC时序分析中已经删去了第33个时刻,但是为了稳妥起见,还是加上第33个时刻,对应case
语句中,该时刻直接跳转到default
中即可)。仿真结果如下:
第一个数据16'hCAAA
第二个数据16'h4555
之后两个数据不一一列举。
5. 同时变化的dac_data与dac_data_r
放大start
脉冲信号产生的时刻
发现
dac_data_r
竟然跟dac_data
同时变化,并没有晚一拍。
testbench中的第50、60、70、80行可知以及对dac_data_r
的赋值语句如下:
//tb中的赋值
dac_data = 16'hC_AAA;
dac_data = 16'h4_555;
dac_data = 16'h1_555;
dac_data = 16'hf_555;
//dac_driver.v中的赋值
dac_data_r <= dac_data;
可知它俩综合出来的电路如下:
dac_data
的赋值是阻塞逻辑,即让它变化它就立马变化,而在clk上升沿dac_data
被赋值时,相当于在触发器的D端已经采样到了变成16'h4555
的dac_data
信号,忽略D触发器的延时,dac_data_r
变成16'h4555
也出现在该clk的上升沿。这也就是为什么两者同时变化的原因。
而电路的延时不可忽略,start
信号总会产生脉冲,这也就是为什么即使上述两信号同时发生变化,modelsim里的仿真依然能够正常进行的原因了。
改进
方法一:
将仿真拉倒16'h0000
和16'hCAAA
变化的时刻可发现,16'hCAAA
的变化出现在clk的下降沿,之后产生了正常的start
的脉冲信号,这是因为rstn
信号的拉高正好处于clk的下降沿。同理,若将后续几个数值的变化都置于clk下降沿进行改变,start
脉冲信号也能够正常产生。
)
方法二:
若在testbench中将赋值语句改为以下:
//tb中的赋值
dac_data <= 16'hC_AAA;
dac_data <= 16'h4_555;
dac_data <= 16'h1_555;
dac_data <= 16'hf_555;
//dac_driver.v中的赋值
dac_data_r <= dac_data;
即把tb中的阻塞赋值全部改为非阻塞赋值,这时综合出来的电路相当于:
即相当于数据16'h4555
的变化并不能立马影响到dac_data_r
,脉冲信号start
能够正常产生。如图所示:
6. DAC & ADC联合调试
两个驱动和板子上两个芯片连接方式如图所示:
将各个输入/输出端口接到开发板的对应位置,即可完成DAC & ADC联合调试。调试内容请参阅梅哥《FPGA自学笔记》第256页。
The End.