状态机练习- 基于EEPROM的I2C 随机写
eeprom的I2C 随机写完整时序如下:
状态机如下:
设计思路:
(1)、产生scl时钟需要一个计数器 cnt0,本实验scl 是100K速率
1 //计数产生scl时钟 2 always @(posedge clk or negedge rst_n)begin 3 if(!rst_n)begin 4 cnt0 <= 0; 5 end 6 else if(add_cnt0)begin 7 if(end_cnt0)begin 8 cnt0 <= 0; 9 end 10 else begin 11 cnt0 <= cnt0 + 1; 12 end 13 end 14 end 15 16 assign add_cnt0 = wr_vld; 17 assign end_cnt0 = add_cnt0 && cnt0 == SCL_100K - 1;
(2)、每个状态下,scl周期个数,cnt1,在start 、ack、stop这个三种状态下,scl时钟周期个数都是为1,其他状态下都是8
1 //计数scl周期数量 2 always @(posedge clk or negedge rst_n)begin 3 if(!rst_n)begin 4 cnt1 <= 0; 5 end 6 else if(add_cnt1)begin 7 if(end_cnt1)begin 8 cnt1 <= 0; 9 end 10 else begin 11 cnt1 <= cnt1 + 1; 12 end 13 end 14 end 15 16 assign add_cnt1 = end_cnt0; 17 assign end_cnt1 = add_cnt1 && cnt1 == x - 1; 18 19 always @(*)begin 20 if(state_c == START || state_c == ACK || state_c == STOP)begin 21 x = 1; 22 end 23 else begin 24 x = 8; 25 end 26 end
(3)、利用计数器cnt0,产生scl时钟,由于sda都是在scl的低电平中间发送数据,高电平中间采集数据;在scl为高时,sda拉低产生start信号;在scl为高时,sda 低 到 高 产生stop信号;
所以需要知道几个点,高电平中间, scl的上升沿,scl的下降沿,需要对这几个点进行标记,方便后面使用,注意,因为是在scl低电平中间发送数据,再利用cnt0产生scl时,
应该打破常规设计思路,计数器寄到1/4时,拉高scl,在计数到3/4时拉低scl ,如下图,在stop时,不希望scl被拉低,所以附加一个 state_c != STOP 条件。
这样做的好处就是,每个状态机都是在计数器cnt1 计数完后进行跳转。在设计跳转条件时,就比较方便
//scl时钟周期 _--_ always @(posedge clk or negedge rst_n)begin if(!rst_n)begin scl <= 1; end else if(add_cnt0 && cnt0 == ((SCL_100K>>1)+ (SCL_100K>>2)) -1 && state_c != STOP)begin scl <= 0; end else if(add_cnt0 && cnt0 == (SCL_100K>>2) -1)begin scl <= 1; end end assign SCL_H2L = add_cnt0 && cnt0 == ((SCL_100K>>1)+ (SCL_100K>>2)) -1; //下降沿 assign SCL_L2H = add_cnt0 && cnt0 == (SCL_100K>>2) -1; //上升沿 assign SCL_H_MIDDLE = add_cnt0 && cnt0 == (SCL_100K>>1) -1; //高电平中间
(4)、在ACK时,需要将sda释放,还有释放结束点,注意,释放结束点是scl的下降沿时刻 ,需要控制sda的方向,引入link信号:
reg link; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin link <= 1; end else if(state_c == ACK && add_cnt0 && cnt0 >= 0 && cnt0 <((SCL_100K>>1)+ (SCL_100K>>2)))begin link <= 0; //释放sda,准备接收数据 end else begin link <= 1; end end
(5)、在ACK状态时,可能会跳转到三种状态,ACK - > REG_ADD,ACK - > REG_DAT, ACK - > STOP, 需要引入一个标志进行标记,否则无法区分ACK到底是调到哪种状态
//标记ack位置,如果到了发送数据阶段,可以知道是第几个ack了 always @(posedge clk or negedge rst_n)begin if(!rst_n)begin ack_flag <= 0; end else if(state_c == REG_ADD)begin ack_flag <= 1; //代表是发送完寄存器地址后面的ACK end else if(state_c == REG_DAT)begin ack_flag <= 2; //代表是发送完寄存器数据后面的ACK end else if(state_c == STOP && end_cnt1)begin ack_flag <= 0; end end
(6)、三段式状态机设计,及sda的送出数据的产生,及ack的检验
完整代码:
1 module eeprom_wr( 2 clk, 3 rst_n, 4 wr_en, 5 slave_address, 6 reg_address, 7 reg_data, 8 scl, 9 sda 10 ); 11 parameter D_W = 8 ; 12 parameter SCL_100K = 500; 13 parameter IDLE = 0 ; 14 parameter START = 1 ; 15 parameter SLAVE_ADD = 2 ; 16 parameter ACK = 3 ; 17 parameter REG_ADD = 4 ; 18 parameter REG_DAT = 5 ; 19 parameter STOP = 6 ; 20 21 22 input clk ; 23 input rst_n ; 24 input wr_en ; 25 input[D_W-1:0] slave_address; 26 input[D_W-1:0] reg_address ; 27 input[D_W-1:0] reg_data ; 28 29 output scl; 30 inout sda; 31 32 wire SCL_H2L ; 33 wire SCL_L2H ; 34 wire SCL_H_MIDDLE ; 35 wire add_cnt0 ; 36 wire end_cnt0 ; 37 wire add_cnt1 ; 38 wire end_cnt1 ; 39 40 wire IDLE2START ; 41 wire START2SLAVE_ADD ; 42 wire SLAVE_ADD2ACK ; 43 wire ACK2REG_ADD ; 44 wire ACK2REG_DAT ; 45 wire ACK2STOP ; 46 wire REG_ADD2ACK ; 47 wire REG_DAT2ACK ; 48 wire STOP2IDLE ; 49 50 51 reg[9 -1 : 0] cnt0; 52 reg[4 -1 : 0] cnt1; 53 54 55 reg scl ; 56 reg ack_err ; 57 reg[4-1:0] state_c ; 58 reg[4-1:0] state_n ; 59 reg[4-1:0] x ; 60 reg wr_vld ; 61 reg[2-1:0] ack_flag; 62 63 always @(posedge clk or negedge rst_n)begin 64 if(!rst_n)begin 65 wr_vld <= 0; 66 end 67 else if(wr_en)begin 68 wr_vld <= 1; 69 end 70 else if(state_c == STOP && end_cnt1)begin 71 wr_vld <= 0; 72 end 73 end 74 75 //计数产生scl时钟 76 always @(posedge clk or negedge rst_n)begin 77 if(!rst_n)begin 78 cnt0 <= 0; 79 end 80 else if(add_cnt0)begin 81 if(end_cnt0)begin 82 cnt0 <= 0; 83 end 84 else begin 85 cnt0 <= cnt0 + 1; 86 end 87 end 88 end 89 90 assign add_cnt0 = wr_vld; 91 assign end_cnt0 = add_cnt0 && cnt0 == SCL_100K - 1; 92 93 //计数bit数量 94 always @(posedge clk or negedge rst_n)begin 95 if(!rst_n)begin 96 cnt1 <= 0; 97 end 98 else if(add_cnt1)begin 99 if(end_cnt1)begin 100 cnt1 <= 0; 101 end 102 else begin 103 cnt1 <= cnt1 + 1; 104 end 105 end 106 end 107 108 assign add_cnt1 = end_cnt0; 109 assign end_cnt1 = add_cnt1 && cnt1 == x - 1; 110 111 always @(*)begin 112 if(state_c == START || state_c == ACK || state_c == STOP)begin 113 x = 1; 114 end 115 else begin 116 x = 8; 117 end 118 end 119 120 //scl时钟周期 _--_ 121 always @(posedge clk or negedge rst_n)begin 122 if(!rst_n)begin 123 scl <= 1; 124 end 125 else if(add_cnt0 && cnt0 == ((SCL_100K>>1)+ (SCL_100K>>2)) -1 && state_c != STOP)begin 126 scl <= 0; 127 end 128 else if(add_cnt0 && cnt0 == (SCL_100K>>2) -1)begin 129 scl <= 1; 130 end 131 end 132 133 assign SCL_H2L = add_cnt0 && cnt0 == ((SCL_100K>>1)+ (SCL_100K>>2)) -1; 134 assign SCL_L2H = add_cnt0 && cnt0 == (SCL_100K>>2) -1; 135 assign SCL_H_MIDDLE = add_cnt0 && cnt0 == (SCL_100K>>1) -1; 136 137 //第一段 138 always @(posedge clk or negedge rst_n)begin 139 if(!rst_n)begin 140 state_c <= IDLE; 141 end 142 else begin 143 state_c <= state_n; 144 end 145 end 146 147 //第二段 148 always @(*)begin 149 case(state_c) 150 IDLE:begin 151 if(IDLE2START)begin 152 state_n = START; 153 end 154 else begin 155 state_n = state_c; 156 end 157 end 158 159 START:begin 160 if(START2SLAVE_ADD)begin 161 state_n = SLAVE_ADD; 162 end 163 else begin 164 state_n = state_c; 165 end 166 end 167 168 SLAVE_ADD:begin 169 if(SLAVE_ADD2ACK)begin 170 state_n = ACK; 171 end 172 else begin 173 state_n = state_c; 174 end 175 end 176 177 ACK:begin 178 if(ACK2REG_ADD)begin 179 state_n = REG_ADD; 180 end 181 else if(ACK2REG_DAT)begin 182 state_n = REG_DAT; 183 end 184 else if(ACK2STOP)begin 185 state_n = STOP; 186 end 187 else begin 188 state_n = state_c; 189 end 190 end 191 192 REG_ADD:begin 193 if(REG_ADD2ACK)begin 194 state_n = ACK; 195 end 196 else begin 197 state_n = state_c; 198 end 199 end 200 201 REG_DAT:begin 202 if(REG_DAT2ACK)begin 203 state_n = ACK; 204 end 205 else begin 206 state_n = state_c; 207 end 208 end 209 210 STOP:begin 211 if(STOP2IDLE)begin 212 state_n = IDLE; 213 end 214 else begin 215 state_n = state_c; 216 end 217 end 218 219 default:begin 220 state_n = IDLE; 221 end 222 223 endcase 224 end 225 226 //第三段 227 assign IDLE2START = state_c == IDLE && wr_vld ; 228 assign START2SLAVE_ADD = state_c == START && end_cnt1; 229 assign SLAVE_ADD2ACK = state_c == SLAVE_ADD && end_cnt1; 230 assign ACK2REG_ADD = state_c == ACK && end_cnt1 && ack_flag == 0; 231 assign ACK2REG_DAT = state_c == ACK && end_cnt1 && ack_flag == 1; 232 assign ACK2STOP = state_c == ACK && end_cnt1 && ack_flag == 2; 233 assign REG_ADD2ACK = state_c == REG_ADD && end_cnt1; 234 assign REG_DAT2ACK = state_c == REG_DAT && end_cnt1; 235 assign STOP2IDLE = state_c == STOP && end_cnt1; 236 237 //第四段 238 //标记ack位置,如果到了发送数据阶段,可以知道是第几个ack了 239 always @(posedge clk or negedge rst_n)begin 240 if(!rst_n)begin 241 ack_flag <= 0; 242 end 243 else if(state_c == REG_ADD)begin 244 ack_flag <= 1; 245 end 246 else if(state_c == REG_DAT)begin 247 ack_flag <= 2; 248 end 249 else if(state_c == STOP && end_cnt1)begin 250 ack_flag <= 0; 251 end 252 end 253 254 255 reg link; 256 always @(posedge clk or negedge rst_n)begin 257 if(!rst_n)begin 258 link <= 1; 259 end 260 else if(state_c == ACK && add_cnt0 && cnt0 >= 0 && cnt0 <((SCL_100K>>1)+ (SCL_100K>>2)))begin 261 link <= 0; //释放sda,准备接收数据 262 end 263 else begin 264 link <= 1; 265 end 266 end 267 268 //sda 发输出数据 269 reg sda_temp; 270 always @(posedge clk or negedge rst_n)begin 271 if(!rst_n)begin 272 sda_temp <= 1; 273 end 274 else if(state_c == START && SCL_L2H)begin 275 sda_temp <= 0; //产生start信号 276 end 277 else if(state_c == SLAVE_ADD)begin 278 sda_temp <= slave_address[7-cnt1]; 279 end 280 else if(state_c == REG_ADD)begin 281 sda_temp <= reg_address[7-cnt1]; 282 end 283 else if(state_c == REG_DAT)begin 284 sda_temp <= reg_data[7-cnt1]; 285 end 286 else if(state_c == ACK && end_cnt0 && ack_flag == 2)begin 287 sda_temp <= 0; //在产生stop之前,先将sda信号拉低 288 end 289 else if(state_c == STOP && SCL_H_MIDDLE)begin 290 sda_temp <= 1; 291 end 292 end 293 294 //sda接收数据 ,主要是判断是否接收到有效的ack 295 always @(posedge clk or negedge rst_n)begin 296 if(!rst_n)begin 297 ack_err <= 0; 298 end 299 else if(state_c == ACK && SCL_H_MIDDLE)begin 300 ack_err <= sda; 301 end 302 end 303 304 assign sda = link? sda_temp : 1'bz; 305 306 endmodule
测试代码:
1 `timescale 1ns/1ns 2 3 module eeprom_sim(); 4 5 reg clk; 6 reg rst_n; 7 reg wr_en; 8 reg[8-1:0] slave_address; 9 reg[8-1:0] reg_address; 10 reg[8-1:0] reg_data; 11 12 wire scl; 13 wire sda; 14 15 parameter CYCLE = 20; 16 17 initial begin 18 clk = 0; 19 forever begin 20 #(CYCLE/2); 21 clk = ~clk; 22 end 23 end 24 25 initial begin 26 rst_n = 1; 27 #1; 28 rst_n = 0; 29 #(CYCLE*2); 30 rst_n = 1; 31 end 32 33 initial begin 34 wr_en = 0; 35 slave_address = 8'b1010_0000; 36 reg_address = 8'ha2; 37 reg_data = 8'haa; 38 39 #1; 40 #(CYCLE*5); 41 wr_en = 1; 42 #(CYCLE); 43 wr_en = 0; 44 end 45 46 47 eeprom_wr u1_inist( 48 .clk (clk), 49 .rst_n (rst_n), 50 .wr_en (wr_en), 51 .slave_address (slave_address), 52 .reg_address (reg_address), 53 .reg_data (reg_data), 54 .scl (scl), 55 .sda (sda) 56 ); 57 58 59 endmodule
仿真波形:
Quartus 的逻辑分析仪抓取的数据,可以看到在ack时,sda是保持为低的
为了进一步确认ack的有效性,可以将寄存器地址和寄存数据低四位设置为F,看sad在ACK时, sda能否拉低,拉低说明ack是有效
经过实际验证,数据能写进eeprom中,读出来的数据是对的。
I2C的读实例请看下一个练习实验。