此篇文章基於德州儀器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.