關於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.


發佈了31 篇原創文章 · 獲贊 4 · 訪問量 4562
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章