状态机练习- 基于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
View Code

测试代码:

 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
View Code

仿真波形:

 

 Quartus 的逻辑分析仪抓取的数据,可以看到在ack时,sda是保持为低的

 

 为了进一步确认ack的有效性,可以将寄存器地址和寄存数据低四位设置为F,看sad在ACK时, sda能否拉低,拉低说明ack是有效

 

 

经过实际验证,数据能写进eeprom中,读出来的数据是对的。

I2C的读实例请看下一个练习实验。

posted @ 2022-04-13 15:05  MyBooks  阅读(201)  评论(0编辑  收藏  举报