1. 程式人生 > >學習筆記一:I2C協議學習和Verilog實現

學習筆記一:I2C協議學習和Verilog實現

  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