學習筆記一:I2C協議學習和Verilog實現
阿新 • • 發佈:2018-11-15
1 ////////////////////////////////////////////////// 2 //clk = 20 MHz ,一個週期50ns 3 //sck = 100 kHz (scl) ,一個週期 1000ns 4 //I2C在sck下降沿更新資料,上升沿讀取(取樣)資料 5 /////////////////////////////////////////////////// 6 module demo_I2C #(parameter F100K = 9'd200)(clk,rstn,start_sig,word_addr,wr_data,rd_data,done_sig,scl,sda,sq_i); 7 8 input clk ; 9 input rstn ; 10 11 input[1:0] start_sig ; // 12 input [7:0] word_addr ; //word address 13 input [7:0] wr_data ; //Data 14 output [7:0] rd_data ; //Data from EEPROM 15 output done_sig ; 16 17 output scl ; //sda和scl其實是用來作為模擬訊號新增在這裡的,暫存器訊號都用rscl和rsda表示了,最後用assign將rscl和rsda賦值給sda和scl,連到模組外部模擬用18 inout sda ; //sda表示當前sda的in或out的值 19 20 output [4:0] sq_i ; 21 /************************************ 22 在這裡,iic_func_module.v 的步驟i已經被引出來了。讀者要知道步驟i在無論是在設計上還是模擬上都有許多的好處。23 步驟i在模擬中可以充當“除錯跟蹤”的作用,因為只要模組的那個部分出問題,步驟i就會指向它。 24 此外,步驟i在驅動IO口的時候,我們還可以知道模擬物件的內部到底發生什麼事情了。 25 *************************************/ 26 27 reg [4:0] i ; 28 reg [9:0] cnt ; 29 reg [4:0] go ; 30 reg isout ; 31 reg isack ; //臨時存放ack訊號用於判斷 32 reg [7:0] rdata ; //存放任意8位資料的暫存器。在讀的最後一步,還會將讀到的8位sda存起來賦值給rd_data 33 reg rsda ; //用來寄存任意一位sda 34 reg rscl ; 35 reg rdone_sig ; 36 37 always@(posedge clk or negedge rstn) 38 begin 39 if(!rstn) 40 begin 41 // start_sig <= 2'b00 ; /*輸入訊號不是暫存器型別,不需要Reset*/ 42 // word_addr <= 8'd0 ; /*在處理輸入輸出訊號時,輸入訊號因為不是reg而是wire,不需要Reset*/ 43 // wr_data <= 8'd0 ; /*輸出訊號一般也不直接Reset,而是定義一個他們對應的reg,在Reset或者其他操作時對這些reg進行操作,最後用assign將輸出訊號和各自的reg相連*/ 44 rdata <= 8'd0 ; 45 rdone_sig <= 1'b0 ; 46 47 rscl <= 1'b1 ; 48 rsda <= 1'b1 ; 49 i <= 5'd0 ; 50 isout <= 1'b1 ; 51 isack <= 1'b0 ; 52 rdata <= 8'd0 ; 53 go <= 3'd0 ; 54 end 55 56 else if(start_sig[0]) //write option 57 case(i) 58 0: //start 59 begin 60 if(cnt == 10'd0) 61 begin 62 rscl <= 1'b1 ; 63 rsda <= 1'b1 ; 64 end 65 else cnt <= cnt + 1'b1; 66 67 if(cnt == 10'd100) 68 begin 69 rsda <= 1'b0 ; 70 71 end 72 else cnt <= cnt + 1'b1; 73 74 if(cnt == F100K - 1'b1) 75 begin 76 i <= i + 5'd1 ; 77 cnt <= 0 ; 78 end 79 else cnt <= cnt + 1'b1; 80 end 81 82 1: //write device address 83 begin 84 isout = 1'b1; 85 rdata <= {4'b1010,3'b000,1'b0}; //1010是EEPROM型號,000是這顆EEPROM地址(三個引腳全部接地),0表示/W(寫) 86 i <= 5'd7; 87 go <= i + 1'b1; 88 end 89 90 2: //write word address 91 begin 92 isout = 1'b1; 93 i <= 5'd7; 94 rdata <= word_addr; 95 go <= i + 1'b1; 96 end 97 98 3: //write data 99 begin 100 isout = 1'b1; 101 i <= 5'd7; 102 rdata <= wr_data; 103 go <= i + 1'b1; 104 end 105 106 4: //stop 107 begin 108 if(cnt == 10'd0) 109 rscl <= 1'b0 ; 110 else if(cnt == 10'd50) 111 rscl <= 1'b1 ; 112 else 113 cnt <= cnt + 1'b1 ; 114 115 if(cnt == 10'd0) 116 rsda <= 1'b0 ; 117 else if(cnt == 10'd150) 118 rsda <= 1'b1 ; 119 else 120 cnt <= cnt + 1'b1 ; 121 122 if(cnt == 10'd50 + F100K - 1'b1) 123 begin 124 i <= i + 1'b1 ; 125 cnt <= 10'd0 ; 126 end 127 else 128 cnt <= cnt + 1'b1 ; 129 end 130 131 5: //return done_sig 132 begin 133 rdone_sig <= 1'b1; 134 i <= i + 1'b1; 135 end 136 6: //return IDLE 137 begin 138 rdone_sig <= 1'b0; 139 i <= 5'd0; 140 end 141 7,8,9,10,11,12,13,14: 142 begin 143 isout = 1'b1; 144 rsda <= rdata[14 - i]; 145 if(cnt == 10'd0) 146 rscl <= 1'b0 ; 147 else if(cnt == 10'd100) 148 rscl <= 1'b1 ; 149 else 150 cnt <= cnt + 1'b1 ; 151 152 if(cnt == F100K - 1) 153 begin 154 i <= i + 1'b1 ; 155 cnt <= 10'b0 ; 156 end 157 else 158 cnt <= cnt + 1'b1 ; 159 160 end 161 15: //waiting for acknowledge 162 begin 163 isout = 1'b0; //等待應答時是Read,因此是輸入模式,=表示即時響應 164 if(cnt == 10'b0) 165 rscl <= 1'b0; 166 else if(cnt == 10'b100) 167 rscl <= 1'b1; 168 else 169 cnt <= cnt + 1'b1; 170 171 if(cnt == F100K - 1) 172 begin 173 i <= i + 1'b1; 174 cnt <= 0; 175 end 176 else 177 cnt <= cnt + 1'b1; 178 179 if(cnt == 10'd150) 180 isack <= sda; //保險起見,在150個clk後才進行ack讀取 181 else 182 cnt <= cnt + 1'b1; 183 end 184 16: //判斷是否應答,返回go 185 begin 186 if(!isack) 187 i <= go; 188 else 189 i <= 0; 190 end 191 192 default: i <= 0; 193 endcase 194 /***************************************************************************************************************************************/ 195 else if(start_sig[1]) //read option 196 case(i) //讀寫操作不衝突,i 不衝突 197 0: //start 198 begin 199 if(cnt == 0) 200 begin 201 rscl <= 1'b1 ; 202 rsda <= 1'b1 ; 203 end 204 else cnt <= cnt + 1'b1; 205 206 if(cnt == 100) 207 begin 208 rsda <= 1'b0 ; 209 210 end 211 else cnt <= cnt + 1'b1; 212 213 if(cnt == F100K - 1) 214 begin 215 i <= i + 5'd1 ; 216 cnt <= 0 ; 217 end 218 else cnt <= cnt + 1'b1; 219 end 220 1: //write device address (read前先要write獲得從機應答) 221 begin 222 isout = 1'b1; 223 rdata <= {4'b1010,3'b000,1'b0}; //1010是EEPROM型號,000是這顆EEPROM地址(三個引腳全部接地),0表示/W(寫) 224 i <= 5'd10; 225 go <= i + 1'b1; 226 end 227 2: //write word address 228 begin 229 isout = 1'b1; 230 rdata <= word_addr; 231 i <= 5'd10; 232 go <= i + 1'b1; 233 end 234 3: //start again ,需要再控制sda和scl共同作用產生start訊號 235 begin 236 isout = 1'b1; 237 if(cnt == 0) 238 begin 239 rscl <= 1'b0 ; 240 rsda <= 1'b0 ; 241 end 242 else cnt <= cnt + 1'b1; 243 244 if(cnt == 50) 245 begin 246 rsda <= 1'b1 ; 247 rscl <= 1'b1 ; 248 end 249 else cnt <= cnt + 1'b1; 250 251 if(cnt == 150) 252 begin 253 rsda <= 1'b0 ; 254 255 end 256 else cnt <= cnt + 1'b1; 257 if(cnt == 250) 258 begin 259 rscl <= 1'b0 ; //這時EEPROM已經start了 260 261 end 262 else cnt <= cnt + 1'b1; 263 if(cnt == 300 - 1) //保險起見,等到start穩定再進入下一狀態 264 begin 265 i <= i + 5'd1 ; 266 cnt <= 0 ; 267 end 268 else cnt <= cnt + 1'b1; 269 end 270 271 272 4: // 再寫一次device address,告訴從裝置變成read了 273 begin 274 isout = 1'b1; //切換到輸入(讀取)模式 275 rdata <= {4'b1010,3'b000,1'b1}; //1010是EEPROM型號,000是這顆EEPROM地址(三個引腳全部接地),1表示R(讀) 276 i <= 5'd10; 277 go <= i + 1'b1; 278 end 279 5: //read data 280 begin 281 isout = 1'b0; 282 rdata <= 8'd0; ///* 注意這裡,在讀取8位sda寄存在rdata之前,要先將rdata清零*/// 283 i <= 5'd20; 284 go <= i + 1'b1; 285 end 286 287 6: //stop 288 begin 289 isout = 1'b1; 290 291 if(cnt == 0) 292 rscl <= 1'b0 ; 293 else if(cnt == 50) 294 rscl <= 1'b1 ; 295 else 296 cnt <= cnt + 1'b1 ; 297 298 if(cnt == 0) 299 rsda <= 1'b0 ; 300 else if(cnt == 150) 301 rsda <= 1'b1 ; 302 else 303 cnt <= cnt + 1'b1 ; 304 305 if(cnt == 50 + F100K - 1) 306 begin 307 i <= i + 1'b1 ; 308 cnt <= 0 ; 309 end 310 else 311 cnt <= cnt + 1'b1 ; 312 end 313 314 7: //return isdone 315 begin 316 rdone_sig <= 1'b1; 317 i <= i + 1'b1; 318 end 319 8: //return IDLE 320 begin 321 rdone_sig <= 1'b0; 322 i <= 0; 323 end 324 10,11,12,13,14,15,16,17: 325 begin 326 isout = 1'b1; 327 rsda <= rdata[17 - i]; 328 if(cnt == 0) 329 rscl <= 1'b0 ; 330 else if(cnt == 100) 331 rscl <= 1'b1 ; 332 else 333 cnt <= cnt + 1'b1 ; 334 335 if(cnt == F100K - 1) 336 begin 337 i <= i + 1'b1; 338 cnt <= 0 ; 339 end 340 else 341 cnt <= cnt + 1'b1 ; 342 343 end 344 18: //waiting for acknowledge 345 begin 346 isout = 1'b0; //等待應答時是Read,因此是輸入模式,=表示即時響應 347 if(cnt == 0) 348 rscl <= 1'b0; 349 else if(cnt == 100) 350 rscl <= 1'b1; 351 else 352 cnt <= cnt + 1'b1; 353 354 if(cnt == F100K - 1) 355 begin 356 i <= i + 1'b1; 357 cnt <= 0; 358 end 359 else 360 cnt <= cnt + 1'b1; 361 362 if(cnt == 150) 363 isack <= sda; //保險起見,在150個clk後才進行sda讀取 364 else 365 cnt <= cnt + 1'b1; 366 end 367 19: //判斷是否應答,返回go 368 begin 369 if(!isack) 370 i <= go; 371 else 372 i <= 0; 373 end 374 375 20,21,22,23,24,25,26,27: 376 begin 377 isout = 1'b0; 378 379 if(cnt == 0) 380 rscl <= 1'b0 ; 381 else if(cnt == 100) 382 rscl <= 1'b1 ; 383 else 384 cnt <= cnt + 1'b1 ; 385 386 if(cnt == F100K - 1) 387 begin 388 i <= i + 1'b1 ; 389 cnt <= 0 ; 390 end 391 else 392 cnt <= cnt + 1'b1 ; 393 394 if(cnt == 150) //保險起見,在150個clk後才進行sda讀取 395 rdata[27- i] <= sda; //讀寫都是先對MSB操作 396 else 397 cnt <= cnt + 1'b1; 398 399 end 400 401 28: //no ack(由於是單Data讀,不是連續Data讀,所以不需要應答),但是scl還是要繼續跳轉的 402 begin 403 isout = 1'b1; 404 405 if(cnt == 0) 406 rscl <= 1'b0 ; 407 else if(cnt == 100) 408 rscl <= 1'b1 ; 409 else 410 cnt <= cnt + 1'b1 ; 411 412 if(cnt == F100K - 1) 413 begin 414 i <= go ; 415 cnt <= 0 ; 416 end 417 else 418 cnt <= cnt + 1'b1 ; 419 end 420 421 default: i <= 0; 422 endcase 423 else 424 begin 425 // start_sig <= 2'b00 ; 426 // addr_sig <= 8'd0 ; 427 // wr_data <= 8'd0 ; 428 rdata <= 8'd0 ; 429 rdone_sig <= 1'b0 ; 430 431 rscl <= 1'b1 ; 432 rsda <= 1'b1 ; 433 i <= 5'd0 ; 434 isout <= 1'b1 ; 435 isack <= 1'b0 ; 436 rdata <= 8'd0 ; 437 go <= 3'd0 ; 438 end 439 end 440 441 assign sda = isout?rsda:1'bz ; //sda是一個帶三態門的inout埠,三態門在out線上,所以在輸出情況下,要將isout開啟,rsda傳到sda上。在輸入情況下,isout關閉,輸出方 442 assign scl = rscl ; //向線是高阻態,但輸入方向沒有三態門,是導通狀態,可以read或者ackownledge資料。 443 assign done_sig = rdone_sig ; 444 assign rd_data = rdata ; //rdata在讀的最後一步,將8位sda都存了下來,所以要將rdata給rd_data 445 446 447 assign sq_i = i ; 448 endmodule
--------------------------------------------------------------------------------------------------------------------------分割線----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 `timescale 1 ns/ 1 ns 2 module demo_I2C_vlg_tst(); 3 4 /*與其說這是一個模擬檔案,倒不如說這是一個top檔案,因為裡面不僅給出了激勵,還模擬出了EEPROM的應答訊號, 5 與介面模組通過sq_i,done_sig,start_sig,inout口sda實現互相控制*/ 6 /*這也就說明了為什麼設計和驗證不分家的原因,當設計模組很龐大的時候,只能通過FPGA上板進行原型驗證,大到一定規模之後, 7 FPGA也無法滿足,只能通過搭建一個驗證平臺模擬使用環境(可能是硬體類似這種EEPROMinout介面)來驗證功能,testbench並不簡單,更像top*/ 8 9 reg clk; 10 reg rstn; 11 12 reg [1:0] start_sig; 13 reg [7:0] word_addr; 14 reg [7:0] wr_data; 15 16 // wires 17 wire done_sig; 18 wire [7:0] rd_data; 19 wire scl; 20 21 //IO inout埠在寫testbench時,輸入reg和輸出wire都要寫 22 reg treg_sda; //輸入 23 wire sda; //輸出 24 assign sda = treg_sda; //由於是inout埠,要將輸入輸出連起來 25 26 27 wire [4:0] sq_i; 28 29 demo_I2C i1 ( 30 // port map - connection between master ports and signals/registers 31 .clk(clk), 32 .done_sig(done_sig), 33 .rd_data(rd_data), 34 .rstn(rstn), 35 .scl(scl), 36 .sda(sda), 37 .sq_i(sq_i), 38 .start_sig(start_sig), 39 .word_addr(word_addr), 40 .wr_data(wr_data) 41 ); 42 initial 43 begin 44 //學習這裡的Reset和clk寫法 45 rstn = 0; 46 #10 rstn = 1; 47 clk = 1; 48 forever #25 clk = ~clk; 49 50 $display("Running testbench"); 51 end 52 53 reg [3:0] i; 54 55 always@(posedge clk or negedge rstn) /*這裡是對輸入進行激勵*/ 56 if(!rstn) 57 begin 58 i <= 4'd0; 59 start_sig <= 2'd0; 60 word_addr <= 8'd0; 61 wr_data <= 8'd0; 62 end 63 else 64 case(i) 65 0: 66 begin 67 if(done_sig) //第二步:寫完成後,done_sig有一拍變1的動作,在這一拍跳到第三步 68 begin 69 start_sig <= 2'd0; 70 i <= i + 1'b1; 71 end 72 else //第一步:done_sig=0,先寫 73 begin 74 start_sig <= 2'b01; 75 word_addr <= 8'b10101010; 76 wr_data <= 9'b11110000; 77 end 78 end 79 1: 80 begin 81 if(done_sig) //第四步:讀操作完成後,done_sig會有一拍短暫的變1動作,在這一拍跳到第五步 82 begin 83 start_sig <= 2'd0; 84 i <= i + 1'b1; 85 end 86 else //第三步:在上一拍結束後,done_sig立刻變0,進行這一步讀操作 87 begin 88 start_sig <= 2'b10; 89 word_addr <= 8'b10101010; 90 end 91 end 92 2: //停止動作 //第五步:在第四步短暫的變1後立馬回到0,並停留在這一步,表示這個寫+讀操作結束 93 i <= i; 94 default: i <= i; 95 endcase 96 97 98 99 100 /////////////////////////////////////////////// 101 /*這一部分是對IO口的激勵,模擬EEPROM的acknowledge操作,因為介面程式碼並不是上板和EEPROM通訊,為了模擬介面程式碼的正確性 102 這裡寫出了在介面程式碼進行一段操作後EEPROM的應答訊號,還有讀取操作時EEPROM輸入給介面的rd_data*/ 103 104 always@(posedge clk or negedge rstn) 105 if(!rstn) 106 treg_sda = 1'b1; //reset並不是輸入狀態(在根本上,如果模擬物件不是將IO設定為輸入狀態,無論怎樣驅動和刺激都沒有用) 107 else if(start_sig[0]) 108 case(sq_i) 109 15: 110 treg_sda = 1'b0; //在15狀態時,isout拉低輸出關斷,輸入導通,sda輸入為0,即ack 111 112 default: treg_sda = 1'b1; //其他狀態下,並不是輸入狀態(在根本上,如果模擬物件不是將IO設定為輸入狀態,無論怎樣驅動和刺激都沒有用) 113 endcase 114 else if(start_sig[1]) 115 case(sq_i) 116 18: 117 treg_sda = 1'b0; 118 119 20,21,22,23,24,25,26,27: 120 treg_sda = wr_data[27 - sq_i]; //這些狀態是read,isout拉低,是輸入狀態,進行8位資料傳入 121 122 default: treg_sda = 1'b1; //其他狀態下,並不是輸入狀態(在根本上,如果模擬物件不是將IO設定為輸入狀態,無論怎樣驅動和刺激都沒有用) 123 endcase 124 else 125 treg_sda = 1'b1; 126 127 endmodule