8.03


基于Modelsim的I2C CMOS串行的EEPROM读写操作
一.Modelsim软件
这个软件最大的好处就是速度快,与传统的EDA设计工具相比,最显著的特点就是速度快,为什么这么快呢,因为它只能作为仿真工具,无法综合,对于初级阶段的人来说,先来仿真也未尝不可;当然这个软件可以与许多大型的EDA设计工具配合使用,比较常用的就是Quartus设计软件。Modelsim软件的版本很多,如10.1C,10.2以及10.4等等,版本之间大同小异,具体的差异可以去了解一下,这个软件需要破解,破解过程并不复杂,但是我遇到一个非常坑的问题就是破解完成后,我编译文件并仿真文件,出现一个奇怪的问题:Verilog中的$display以及$monitor语句无法正常显示在Transcript窗口,我怀疑是不是破解不完全,于是重新装,重新破解,结果还是不行;我又怀疑是不是软件版本的问题,网上也有人说是版本导致的,于是我换了以上三个版本,结果仍然一样。后来我怀疑是不是电脑系统版本的问题,于是借用别人的Win7电脑,安装完一次,重新测试,所有问题都没有,顺便补充一下我的电脑系统是Win10版本的。


二.I2C协议
这个协议很简单,是个串口执行的,因此比较慢,需要两个线,一个时钟线SDL,一个数据线SDA,有启动,停止,数据有效段以及应答阶段,具体定义参考夏宇闻的第三版Verilog教程的P227页。明白了原理之后就可以写代码了,这里说明一点:控制字节以及EEPROM的存储地址发送成功之后都需要应答的,一定注意写和读两个示意图。


三.Verilog代码

当然我的代码也参考了别人的成果,这里感谢网上那些大神的开源,毕竟我比较菜。

源代码

    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305

module I2C
(input clk,rst,RW_sig, //RW_sig =1 stands for reading
input [7:0]Addr_data,Write_data,
output SCL,
inout SDA,
input SDA_data,
output [7:0]Read_data,
output Isover // Isover=1 Stands for over
);

parameter clk_div=249;
reg [7:0]count;
reg [7:0]rRead_data;
reg [4:0]state1;
reg [4:0]state2;
reg rSDA;
reg rIsover;
reg rIOselect;
reg rSCL;
reg IsACK;// IsASK=0 Return=0 state

assign SDA=(rIOselect==1)? rSDA:1'bz;
assign Read_data=rRead_data;
assign SCL=rSCL;
assign Isover=rIsover;
//assign IOselect<=rIOselcet;
always @ (posedge clk or negedge rst)
if(!rst)
begin
count<=8'b0;
state1<=5'b0;
state2<=5'b0;
rRead_data<=8'b0;
rSCL<=1'b1;
rSDA<=1'b1;
rIsover<=1'b0;
rIOselect<=1'b1;
end
else if(!RW_sig)
begin
case(state1)
0: //Write start
begin
rIOselect<=1'b1;
if(count==0)
begin
rSCL<=1'b1;
rSDA<=1'b1;
end
if(count==100) rSDA<=0;
if(count==200) rSCL<=0;
if(count==clk_div)
begin
count<=8'b0;
state1<=state1+1'b1;
end
else
count<=count+1'b1;
end
1: // Device Address
begin
rRead_data<={4'b1010, 3'b000, 1'b0};
state1<=5'b00111;
state2<=state1+1'b1;
end
2: // EEPROM Address
begin
rRead_data<=Addr_data;
state1<=5'b00111;
state2<=state1+1'b1;
end
3: // Data
begin
rRead_data<=Write_data;
state1<=5'b00111;
state2<=state1+1'b1;
end
4: // Stop Start
begin
rIOselect<=1;
if(count==0)
begin
rSCL<=1'b1;
rSDA<=1'b0;
end
if(count==50)
begin
rSCL<=1'b1; //which can be ignored
rSDA<=1'b1;
end

if(count==clk_div)
begin
count<=8'b0000_0000;
state1<=state1+1'b1;
end
else
count<=count+1'b1;
end
5: //Stop
begin
rIsover<=1'b1;
state1<=state1+1'b1;
end
6:
begin
rIsover<=1'b0;
state1<=5'b00000;
end
7,8,9,10,11,12,13,14:
begin
if(count==0)
rSDA<=rRead_data[14-state1];
if(count==50)
rSCL<=1'b1;
if(count==150)
rSCL<=1'b0;
if(count==clk_div)
begin
count<=8'b0;
state1<=state1+1'b1;
end
else
count<=count+1'b1;
end

15:
begin
rIOselect<=0;
if(count==0)
begin
IsACK<=SDA_data;
state1<=state1+1'b1;
end
end
16:
begin
rIOselect<=1'b1;
if(!IsACK)
state1<=5'b00000;
else
state1<=state2;
end
endcase
end
else if(RW_sig) //READ DATA
begin
case(state1) //READ Start Firstly
0:
begin
rIOselect<=1'b1;
if(count==0)
begin
rSCL<=1;
rSDA<=1;
end
if(count==100) rSDA<=0;
if(count==200) rSCL<=0;
if(count==clk_div)
begin
count<=8'b0;
state1<=state1+1'b1;
end
else
count<=count+1'b1;
end
1: //Address write data
begin
rRead_data<={4'b1010, 3'b000, 1'b0};
state1<=5'b01000;
state2<=state1+1'b1;
end
2:
begin
rRead_data<=Addr_data;
state1<=5'b01000;
state2<=state1+1'b1;
end
3:
// READ Start Secondly
begin

rIOselect<=1'b1;
if(count==0)
begin
rSCL<=1;
rSDA<=1;
end
if(count==100) rSDA<=0;
if(count==200) rSCL<=0;
if(count==clk_div)
begin
count<=8'b0;
state1<=state1+1'b1;
state2<=state1+1'b1;
end
else
count<=count+1'b1;
end
4:
begin
rRead_data<={4'b1010, 3'b000, 1'b1};
state1<=5'b01000;
state2<=state1+1'b1;
end
5:
begin
rRead_data<=8'b0;
state1<=5'b10100;
state2<=state1+1'b1;
end
6: //Stop
begin
rIsover<=1'b1;
state1<=state1+1'b1;
end
7:
begin
rIsover<=1'b0;
state1<=5'b00000;
end

8,9,10,11,12,13,14,15:

begin
rIOselect<=1'b1;
if(count==0)
rSDA<=rRead_data[15-state1];
if(count==50)
rSCL<=1'b1;
if(count==150)
rSCL<=1'b0;
if(count==clk_div)
begin
count<=8'b0;
state1<=state1+1'b1;
end
else
count<=count+1'b1;
end


16:
begin
rIOselect<=0;
if(count==0)

IsACK<=rSDA;
end
17:
begin
if(!IsACK)
state1<=5'b00000;
else
state1<=state2;
end
20,21,22,23,24,25,26,27:
begin
rIOselect<=1'b0;
if(count==0)
rRead_data[27-state1] <= SDA;
if(count==50)
rSCL<=1'b1;
if(count==150)
rSCL<=1'b0;
if(count==clk_div)
begin
count<=8'b0;
state1<=state1+1'b1;
end
else
count<=count+1'b1;
end
28:
begin
rIOselect<=1'b1;
if(count==0)
begin
rSCL<=1;
rSDA<=1;
end
if(count==100) rSDA<=0;
if(count==200) rSCL<=0;

if(count==clk_div)
begin
count<=8'b0;
state1<=state2;
end

else
count<=count+1;

end
endcase
end
endmodule

//PS:注释写的太差了

```


### 测试代码
`timescale 1ns / 1ps module tbI2C; reg clk; reg rst; reg RW_sig; reg SDA_data; reg [7:0]Addr_data; reg [7:0]Write_data; wire SCL; wire SDA; wire Isover; wire [7:0]Read_data; I2C DT ( .clk(clk), .rst(rst), .RW_sig(RW_sig), .Addr_data(Addr_data), .Write_data(Write_data), .SCL(SCL), .SDA(SDA), .SDA_data(SDA_data), .Read_data(Read_data), .Isover(Isover) ); initial begin rst=1; clk=0; RW_sig=1'b0; Addr_data=8'b10101010; Write_data=8'b0000111; #2.5 rst=0; #5 rst=1; #4550 SDA_data=1'b1; end always #1 clk=~clk; endmodule ```
关于在testbench的阻塞与非阻塞语句的差别,具体看一下以下链接的文章
https://www.cnblogs.com/fkl523/p/4029467.html

下图就是EEPROM的写代码的测试结果



这个代码看似很长,其实读写重复部分很多,可以优化一下,还要说一下,仿真检验的testbench部分,我只验证了写的部分(RW_sig==0)的代码,读的代码没验证,我觉得出错的可能性非常大,有兴趣的可以继续完成验证。在验证写的过程中,因为定义的SDA为inout类型,双向端口,在testbench中只能声明为wire类型,可是在应答中,需要将SDA传输给IsACK,于是我增加了一个SDA_data作为传输向量,只是为了看完整的仿真结果。