RTL視圖
設計目標: 通過FPGA控制,輪流切換通道進行ADC讀數據,並將數據暫存到FIFO中,同時讀FIFO中的數據,通過串口打印到PC機端。FIFO采用的是16位寬的,深度用的256個字節。
1、串口設計要點:串口發送模塊,采用連續不間斷的發送兩個字節,這樣一共需要發出20個bit數據,這樣就避免FIFO讀出16位寬的數據進行分段發 。
(1)、首先需要設計第一個計數器,對位寬進行計數,也就是波特率,這里采用921600的波特率,1bit位寬的時間就是1/921600 s = 1085ns ,FPGA外接的時鍾是50MHz,一個周期是20ns,顧1085/20 = 54.25,需要計數54個時鍾周期。計數器cnt0
(2)、其次設計第二個計數器,對bit進行計數,就是准備要發送多少個bit數據出去,設計目標是連續發送兩個字節,所以計數20個bit。計數器cnt1
(3)、串口發送模塊還需要產出一個信號,得告訴上游模塊,我已經准備好接收數據了,txd_rdy
assign txd_rdy = (cnt0_vld || txd_din_vld )? 1'b0: 1'b1; //注意兩個條件不能少,尤其是txd_din_vld ,否則相對讀使能信號會延遲兩拍,導致讀使能連續會出兩個數據。詳情請看上篇文章的介紹。
(4)、串口要發送數據,得需要上游給個信號,收到有效信號后,才開始啟動計數器(位寬計數),txd_din_vld.
串口發送模塊完整代碼:

1 module uart_txd_16( 2 clk, 3 rst_n, 4 txd_din_vld, 5 data_din, 6 txd_rdy, 7 txd_dout 8 ); 9 10 parameter DATA_W = 16; 11 parameter BAUD_RATE = 54; 12 13 input clk; 14 input rst_n; 15 input txd_din_vld; 16 input [DATA_W-1:0]data_din; 17 18 output txd_rdy; 19 output txd_dout; 20 21 wire add_cnt0/* synthesis keep*/; 22 wire end_cnt0/* synthesis keep*/; 23 24 wire add_cnt1; 25 wire end_cnt1; 26 27 wire [20-1:0]data_temp; 28 29 reg cnt0_vld; 30 always @(posedge clk or negedge rst_n)begin 31 if(!rst_n)begin 32 cnt0_vld <= 0; 33 end 34 else if(txd_din_vld)begin 35 cnt0_vld <= 1; 36 end 37 else if(end_cnt1)begin 38 cnt0_vld <= 0; 39 end 40 end 41 42 reg [8:0] cnt0; 43 always @(posedge clk or negedge rst_n)begin 44 if(!rst_n)begin 45 cnt0 <= 0; 46 end 47 else if(add_cnt0)begin 48 if(end_cnt0)begin 49 cnt0 <= 0; 50 end 51 else begin 52 cnt0 <= cnt0 + 1; 53 end 54 end 55 end 56 57 assign add_cnt0 = cnt0_vld == 1; 58 assign end_cnt0 = add_cnt0 && cnt0 == BAUD_RATE - 1; 59 60 reg [5:0] cnt1; 61 always @(posedge clk or negedge rst_n)begin 62 if(!rst_n)begin 63 cnt1 <= 0; 64 end 65 else if(add_cnt1)begin 66 if(end_cnt1)begin 67 cnt1 <= 0; 68 end 69 else begin 70 cnt1 <= cnt1 + 1; 71 end 72 end 73 end 74 75 assign add_cnt1 = end_cnt0; 76 assign end_cnt1 = add_cnt1 && cnt1 == 20 - 1; //數據位寬+起始位+停止位 , 連續發送兩個字節 77 78 reg[DATA_W-1 : 0] data_buf; 79 always @(posedge clk or negedge rst_n)begin 80 if(!rst_n)begin 81 data_buf <= 0; 82 end 83 else if(txd_din_vld)begin //在檢測FIFO輸出的有效信號時,把數據進行鎖存,避免在發送過程中,data_buf 數據發生變化 84 data_buf <= data_din; 85 end 86 end 87 88 assign data_temp = {1'b1, data_buf[7:0], 1'b0, 1'b1, data_buf[15:8], 1'b0}; // 停止位 + 8bit數據 + 起始位, 低位先發 89 90 reg txd_dout; 91 always @(posedge clk or negedge rst_n)begin 92 if(!rst_n)begin 93 txd_dout <= 1; 94 end 95 else if(add_cnt0 && cnt0 == 0 && cnt1 >=0 && cnt1 < 20)begin 96 txd_dout <= data_temp[cnt1]; 97 end 98 end 99 100 assign txd_rdy = (cnt0_vld || txd_din_vld )? 1'b0: 1'b1; 101 102 endmodule
2、FIFO設計要點:
(1)、FIFO采用的是show-ahead模式,位寬是16bit,深度256,因為ADC7928采集的數據是16bit的,方便寫入FIFO,所以選擇16bit的位寬,由於串口模塊采用的連續發送兩個字節,所以讀FIFO的數據不用進行分割發。
(2)、深度是256,所以在寫入之前先判斷寫入的數量是否大於250,大於250的就暫停寫入,同時會通知上游模塊不要再進行ADC采集數據了。fifo_full_flag
(3)、上游ADC模塊,當收完一個完整數據后,告訴FIFO可以寫入了,din_vld
(4)、產生讀使能,讀使能采用的是組合邏輯(因為用的是show-ahead模式),同時判斷FIFO是否為空empty ,且下游模塊是否准備好din_rdy
assign rdreq = (empty == 0) && (din_rdy == 1);
(5)、產生讀使能后,同時拿到了FIFO的數據,需要產生一個信號告訴下游模塊,准備接收數據了fifo_dout_vld
記住關鍵點:注意數據對齊,在讀使能有效期間,數據也是在同一拍
FIFO控制模塊完整代碼:

1 module control_fifo( 2 clk, 3 rst_n, 4 din_vld, 5 fifo_data_din, 6 din_rdy,//下游模塊准備好信號 7 fifo_dout_vld, //通知下游模塊准備收數據 8 fifo_data_dout, 9 fifo_full_flag 10 ); 11 parameter DATA_WRW = 16; 12 input clk; 13 input rst_n; 14 input din_vld; 15 input [DATA_WRW-1:0] fifo_data_din; 16 input din_rdy; 17 18 output fifo_dout_vld; 19 output[DATA_WRW-1:0] fifo_data_dout; 20 output fifo_full_flag; 21 22 reg fifo_full_flag; 23 wire rdreq; 24 reg wrreq; 25 wire [DATA_WRW-1:0] q/* synthesis keep*/; 26 wire [7:0]usedw/* synthesis keep*/; 27 my_fifo my_fifo_inst ( 28 .clock ( clk ), 29 .data ( fifo_data_din ), 30 .rdreq ( rdreq ), 31 .wrreq ( wrreq ), 32 .empty ( empty ), 33 .full ( full), 34 .q ( q ), 35 .usedw ( usedw) 36 ); 37 38 //assign wrreq = full? 1'b0 : din_vld; 39 40 always @(*)begin 41 if(usedw >= 250)begin 42 wrreq = 0; 43 fifo_full_flag = 0; 44 end 45 else begin 46 wrreq = din_vld; 47 fifo_full_flag = 1; 48 end 49 50 end 51 52 assign rdreq = (empty == 0) && (din_rdy == 1); 53 54 reg [DATA_WRW-1:0] fifo_data_dout; 55 always @(posedge clk or negedge rst_n)begin 56 if(!rst_n)begin 57 fifo_data_dout <= 0; 58 end 59 else begin 60 fifo_data_dout <= q; 61 end 62 end 63 64 reg fifo_dout_vld; 65 always @(posedge clk or negedge rst_n)begin 66 if(!rst_n)begin 67 fifo_dout_vld <= 0; 68 end 69 else begin 70 fifo_dout_vld <= rdreq; 71 end 72 end 73 74 endmodule
3、ad7928設計要點:
首先查看手冊時序圖,第一次看這種時序圖,第一感覺,時鍾從哪里開始數比較好,就讓人拿不定注意,是從前頭的虛線那開始,結尾虛線結束嗎? 就會有各種疑問,網上搜資料教程也沒具體詳細將這一點。所以干脆不要想那么多,按自己的思路來
我是這樣數時鍾的,如下圖紅色線,意味着要產生17個這樣的完整時鍾,接下就設計數器了:
(1)、第一個計數器,產生sclk時鍾,也就是位寬,這里采用4分頻,sclk也就是50/4 = 12.5MHz,查看手冊,該時鍾最大可以到20MHz,計數cnt0
(2)、第二個計數器,對bit數量進行計數,發數據和收數據都需要借助這個計數器cnt1
(3)、我們可以知道在哪個階段發數據,何時收數據,所以需結合兩個計數器進行操作。
(4)、產生CS信號,可以在第一個時鍾的高電平中間拉低CS信號,在第17個時鍾的高電平中間位置拉高就行。
(5)、產生din 信號,也就是發出數據給AD7928,看時序圖,又有點蒙了,到底是在sclk的上升沿發,還是高電平期間發數據呢,因為第一個數據是在sclk高電平期間就發出了,所以后面都采用在高電平期間將數據送到din上,
也就是CS拉低的同時,發出數據。
(6)、接收dout上的數據,看時序圖還是蒙了,到底是高電平期間采數據,還是下降沿采數據,可以查看t4和 t7說明,t4是sclk下降沿到dout的訪問時間,數據保持時間是t7,說明是在下降沿時采數據(其實我有點納悶,為啥不是在高電平期間采樣,在高電平期間,我看dout時序應該是最穩的)
OK 我們還是按要求來,在sclk時鍾下降沿采數據。
采數據 、發數據、 CS拉低拉高位置就都已經確認下來了,如下圖所示,綠色虛線位置發 送數據 , 橘黃色虛線位置是 采數據 ,CS 是在第一個時鍾周期的高電平期間(綠色虛線)拉低,在第17個時鍾周期的高電平期間拉高。知道了關鍵節點,就可以寫代碼了。
ad7928的完整代碼:

1 module ad7928( 2 clk, 3 rst_n, 4 adc_dout, 5 adc_cs, 6 adc_sclk, 7 adc_din, 8 din_vld, 9 adc_dout_vld, 10 adc_data_out 11 ); 12 13 parameter WRITE = 1'b1 ; 14 parameter SEQ = 1'b0 ; 15 parameter PM1 = 1'b1 ; 16 parameter PM0 = 1'b1 ; 17 parameter SHADOW = 1'b0 ; 18 parameter RANGE = 1'b0 ; 19 parameter CODING = 1'b1 ; 20 parameter ADDRES = 3'b010; 21 22 input clk ; 23 input rst_n ; 24 input adc_dout; 25 input din_vld ; 26 27 output adc_cs ; 28 output adc_sclk; 29 output adc_din ; 30 output adc_dout_vld; 31 output [16-1:0] adc_data_out; 32 33 wire add_cnt0; 34 wire end_cnt0; 35 36 wire add_cnt1; 37 wire end_cnt1; 38 39 wire add_cnt2; 40 wire end_cnt2; 41 42 wire [15:0] data; 43 44 reg [2:0] cnt0; 45 always @(posedge clk or negedge rst_n)begin 46 if(!rst_n)begin 47 cnt0 <= 0; 48 end 49 else if(add_cnt0)begin 50 if(end_cnt0)begin 51 cnt0 <= 0; 52 end 53 else begin 54 cnt0 <= cnt0 + 1; 55 end 56 end 57 end 58 59 assign add_cnt0 = din_vld; 60 assign end_cnt0 = add_cnt0 && cnt0 == 4-1; 61 62 63 reg [4:0] cnt1; 64 always @(posedge clk or negedge rst_n)begin 65 if(!rst_n)begin 66 cnt1 <= 0; 67 end 68 else if(add_cnt1)begin 69 if(end_cnt1)begin 70 cnt1 <= 0; 71 end 72 else begin 73 cnt1 <= cnt1 + 1; 74 end 75 end 76 end 77 78 assign add_cnt1 = end_cnt0; 79 assign end_cnt1 = add_cnt1 && cnt1 == 17-1; 80 81 reg [3:0] cnt2; 82 always @(posedge clk or negedge rst_n)begin 83 if(!rst_n)begin 84 cnt2 <= 0; 85 end 86 else if(add_cnt2)begin 87 if(end_cnt2)begin 88 cnt2 <= 0; 89 end 90 else begin 91 cnt2 <= cnt2 + 1; 92 end 93 end 94 end 95 96 assign add_cnt2 = end_cnt1; 97 assign end_cnt2 = add_cnt2 && cnt2 == 8-1; 98 99 100 reg adc_sclk; 101 always @(posedge clk or negedge rst_n)begin 102 if(!rst_n)begin 103 adc_sclk <= 1; 104 end 105 else if(add_cnt0 && cnt0 >= 2 && cnt0 < 4)begin 106 adc_sclk <= 0; 107 end 108 else if(add_cnt0 && cnt0 >= 0 && cnt0 < 2)begin 109 adc_sclk <= 1; 110 end 111 end 112 113 reg adc_cs; 114 always @(posedge clk or negedge rst_n)begin 115 if(!rst_n)begin 116 adc_cs <= 1; 117 end 118 else if(add_cnt0 && cnt0 == 2-1 && cnt1 == 1-1)begin 119 adc_cs <= 0; 120 end 121 else if(add_cnt0 && cnt0 == 2-1 && cnt1 == 17-1)begin 122 adc_cs <= 1; 123 end 124 end 125 126 reg [2:0] channel_sel; 127 always @(posedge clk or negedge rst_n)begin 128 if(!rst_n)begin 129 channel_sel <= 3'b000; 130 end 131 else begin 132 case(cnt2) 133 0 : channel_sel <= 3'b000; 134 1 : channel_sel <= 3'b001; 135 2 : channel_sel <= 3'b010; 136 3 : channel_sel <= 3'b011; 137 4 : channel_sel <= 3'b100; 138 5 : channel_sel <= 3'b101; 139 6 : channel_sel <= 3'b110; 140 7 : channel_sel <= 3'b111; 141 default : channel_sel <= 3'b000; 142 endcase 143 end 144 end 145 146 reg adc_din; //給ADC送數據,進行通道切換 147 always @(posedge clk or negedge rst_n)begin 148 if(!rst_n)begin 149 adc_din <= 1; 150 end 151 else if(add_cnt0 && cnt0 == 2-1 && cnt1 >=0 && cnt1 < 16)begin 152 adc_din = data[15-cnt1]; 153 end 154 end 155 156 assign data = {WRITE, SEQ, 1'b0, channel_sel, PM1, PM0, SHADOW, 1'b0, RANGE, CODING, 4'b0000}; 157 158 reg [16-1:0] data_temp; //從ADC上讀數據 159 always @(posedge clk or negedge rst_n)begin 160 if(!rst_n)begin 161 data_temp <= 15'b000_0000_0000_0000; 162 end 163 else if(add_cnt0 && cnt0 == 3-1 && cnt1 >= 0 && cnt1 < 16)begin 164 data_temp[15-cnt1] <= adc_dout; 165 end 166 end 167 168 reg [16-1:0] adc_data_out; //將收到的完整數據進行鎖存 169 always @(posedge clk or negedge rst_n)begin 170 if(!rst_n)begin 171 adc_data_out <= 0; 172 end 173 else if(end_cnt1)begin 174 adc_data_out <= data_temp; 175 end 176 end 177 178 reg adc_dout_vld; //數據有效時,同時產生一個有效標志 179 always @(posedge clk or negedge rst_n)begin 180 if(!rst_n)begin 181 adc_dout_vld <= 0; 182 end 183 else if(end_cnt1)begin 184 adc_dout_vld <= 1; 185 end 186 else begin 187 adc_dout_vld <= 0; 188 end 189 end 190 191 endmodule
頂層設計代碼:

1 module ad7928_fifo_top( 2 clk, 3 rst_n, 4 adc_dout, 5 6 adc_cs, 7 adc_sclk, 8 adc_din, 9 txd_dout 10 ); 11 12 input clk; 13 input rst_n; 14 input adc_dout; 15 16 output adc_cs; 17 output adc_sclk; 18 output adc_din; 19 output txd_dout; 20 21 wire [16-1 : 0] fifo_data_dout; 22 wire [16-1 : 0] adc_data_out; 23 wire adc_dout_vld; 24 wire txd_rdy; 25 wire fifo_dout_vld; 26 wire fifo_full_flag; 27 28 ad7928 u1_adc( 29 .clk (clk), 30 .rst_n (rst_n), 31 .adc_dout (adc_dout), 32 .adc_cs (adc_cs), 33 .adc_sclk (adc_sclk), 34 .adc_din (adc_din), 35 .din_vld (fifo_full_flag), 36 .adc_dout_vld (adc_dout_vld), 37 .adc_data_out (adc_data_out) 38 ); 39 40 control_fifo u2_fifo( 41 .clk(clk), 42 .rst_n(rst_n), 43 .din_vld(adc_dout_vld), 44 .fifo_data_din(adc_data_out), 45 .din_rdy(txd_rdy),//下游模塊准備好信號 46 .fifo_dout_vld(fifo_dout_vld), //通知下游模塊准備收數據 47 .fifo_data_dout(fifo_data_dout), 48 .fifo_full_flag(fifo_full_flag) 49 ); 50 51 uart_txd_16 u3_uart_txd( 52 .clk(clk), 53 .rst_n(rst_n), 54 .txd_din_vld(fifo_dout_vld), 55 .data_din(fifo_data_dout), 56 .txd_rdy(txd_rdy), 57 .txd_dout(txd_dout) 58 ); 59 60 endmodule
signal TAB波形:
串口打印的數據:測試時,先按住復位按鍵,然后點開始運行仿真觸發,釋放復位按鍵,然后迅速關閉串口,拉到最開始的位置對比數據,因為串口會一直在打印數據,且速率還是滿塊的。
可以對比下,第一個數據是6392 ,0E4A ,1C7F, 2AB7, 38EC, 4727, 5564 , 6396 , 71D5, ...和仿真上的數據是完全對的,第一個數據6392 其實是第6個通道的數據。由於第一次發數據時,ADC同時也會送出數據,而此時送出的數據我們並沒有配置通道,所以ADC估計會根據上一回的配置進行送出數據。
0E4A ,1C7F, 2AB7, 38EC, 4727, 5564 , 6396 , 71D5 從這數據上來看, 可以明顯看出是哪個通道的,0E4A 是0通道,1C7F是1通道的,2AB7是2通道的,具體可以看手冊上的dout數據說明。
0通道電壓:0E4A , E4A 轉十進制 3658,
3658 * 5 /4096 = 4.46533V = 4.5V ,
從原路圖上計算:V0 = (R2+R4+R8+R9+R10+R11+R13+R14) / (R1 +R2+R4+R8+R9+R10+R11+R13+R14) * 5 = 8 / 9 * 5 = 4.444V, 這是理論計算電壓,用萬用實測的是4.5V,說明ADC測試的電壓還是蠻准的。
其他通道可以以此類推進行計算。