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