关于DAC_TLV5618A驱动设计的思考

此篇文章基于德州仪器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语句。

在这里插入图片描述在这里插入图片描述

分析:

  1. 由于复位或DAC系统未使能时可将信号置为:
    csn =1;
    sclk=0;
    din =0;
    而时刻0正好与复位情况重合,故可去掉时刻0;
  2. 时刻1和时刻2之间仅仅是将sclk拉高,且由于SCLK的最大频率为20MHz,即50ns,即使半个周期的25ns超过tsu的最小值10ns,故可将时刻1和时刻2重合,即拉低csn的同时拉高sclk。
  3. 同理第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'hCAAAdac_data = 16'h4555两数据交替时刻(如下图所示)可发现:

  1. 当数据16'hCAAA没有转换完,下一个数据16'h4555就来了;
  2. 16'h4555到来后的下一个sclk上升沿,本应该拉高的din信号此时拉低,说明数据切换的时候两数据发生了串扰,导致DAC输入不稳定。
  3. 直到下一次转换数据的周期开始时,DAC才输入正常的din
    在这里插入图片描述

以上仿真现象说明不可将start信号一直置为高电平,否则DAC的输入din不稳定,影响DAC输出。

实际调试中,本人将start信号接到轻触按键S0上,即start一直为高电平,并且调用ISSP软件对DAC进行调试,发现

  1. 当在source中先输入dac_data = 16'hC000(对DAC的输入进行配置,配置C表示DAC的输出只在通道OUTA上进行变化)
  2. 再输入dac_data = 16'h47FF(对DAC的输入进行配置,配置4表示DAC的输出只在通道OUTB上进行变化)
  3. 发现本应保持不变的输出通道OUTA却发生了变化,用电压表测量之后,本该保持为0V的通道OUTA,电压却上升了一些。

并且更有意思的是,通过反复输入dac_data = 16'hC000dac_data = 16'h47FF,发现每次OUTA的输出电压并不一样。通过反复检查dac_driver.v代码,最终找出了问题所在,即信号start并不应该总为高电平,而应是一个脉冲信号。

4. 驱动的改写

分析:
start信号为脉冲信号的条件(或前提)是,当输入的dac_data数据发生变化,就应该启动dac_driver对DAC进行驱动。由于dac_data_rdac_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'h4555dac_data信号,忽略D触发器的延时,dac_data_r变成16'h4555也出现在该clk的上升沿。这也就是为什么两者同时变化的原因。

而电路的延时不可忽略,start信号总会产生脉冲,这也就是为什么即使上述两信号同时发生变化,modelsim里的仿真依然能够正常进行的原因了。

改进

方法一:
将仿真拉倒16'h000016'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.


  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值