参考自夏宇闻老师《verilog系统设计原理》
系统设计基础原理:
IIC串行总线工作状态定义如下:
(1)总线非忙状态(A段):SDA和SCL均保持高电平;
(2)启动数据传输(B段):SCL为高电平,SDA的下降沿被认为是启动信号,只有出现启动信号之后,其他的命令才有效。
(3)停止数据传输(C段):SCL为高电平,SDA的上升沿被认为是停止信号。
(4)数据有效(D段):出现启动信号后,在SCL高电平时,数据线稳定,表示要传输的数据;SDA必须在SCL低电平时改变。每个数据占用一个时钟脉冲。
EEPROM写操作帧格式:
写操作过程即通过EEPROM读写控制器将一个字节数据发送到EEPROM中指定地址的存储单元。
过程如下:EEPROM读写控制器发送启动信号,紧跟着一个控制字节(包含特征编码1010+芯片地址/页地址XXX+写状态(0))至总线上;收到EEPROM产生的一个应答位后,紧跟着发送一字节地址和一字节数据,EEPROM分别产生一次应答位,最后读写控制器产生停止信号。
EEPROM读操作帧格式:
读操作即通过读写控制器读取EEPROM中指定地址的存储单元中的一个字节数据。
过程如下:EEPROM读写控制器发送启动信号和一个控制字节(此时控制状态为写)至总线上,通过写操作设置EEPROM存储单元地址,此期间EEPROM会产生必要的应答位;读写控制器紧跟着重新发送启动信号和一个控制字节(此时控制状态为读),EEPROM发出应答信号后,寻址存储单元的数据从SDA线上输出。EEPROM产生一次非应答(高电平),最后读写控制器产生停止信号。
Verilog程序由三个模块构成:
Signal:信号源模块,产生用于EEPROM读写器件的仿真测试信号,能产生相应的复位信号(RESET),时钟信号(CLK),读信号(WR),写信号(RD),并行地址信号(ADDR)和并行数据信号(DATA),并能接收串行EEPROM读写期间的应答信号(ACK)及,以此来调节发送或接收数据的速度。
其中ACK为输入信号,DATA为双向信号,可输出EEPROM写操作的数据,也可接收EEPROM读操作的数据。该模块为行为仿真,采用阻塞赋值,不可综合为门级网表。
代码如下所示:
module Signal(
input ACK ,//应答信号,EEPROM读写器用于控制信号源读写速度
inout[7:0] DATA ,//写操作数据
output reg RESET ,//复位信号
output reg CLK ,//时钟周期200ns
output reg RD ,//读标志 持续一个时钟
output reg WR ,//写标志 持续一个时钟
output reg[10:0] ADDR //读/写地址
);
reg W_R ;
reg[7:0] data_to_eeprom ;//DATA输出写EEPROM操作的数据
reg[10:0] addr_mem[0:255] ;//地址存储器
reg[7:0] data_mem[0:255] ;//数据(写)存储器
reg[7:0] ROM[0:2047] ;//表示EEPROM存储器
integer i,j ;//循环参数
integer OUTFILE ;//文件句柄,文件内容为写操作地址,数据
parameter test_number =50; //测试次数
assign DATA =(W_R)? 8'bz:data_to_eeprom;//W_R=0,DATA输出写EEPROM操作的数据
//-----------------时钟输入-----------------
always #(`timeslice2/2) CLK = ~CLK ;
//-----------------读写信号输入--------------
initial begin
RESET=1 ;
i=0 ;
j=0 ;
W_R=0 ;
CLK=0 ;
RD=0 ;
WR=0 ;
#1000 ;
RESET=0 ;
//WR拉高一个时钟,收到ACK后再次循环,循环次数test_number
repeat(test_number)begin
#(5*`timeslice2);
WR=1;
#(`timeslice2)
WR=0;
@(posedge ACK);
end
//写操作完成后,W_R拉高,进入读操作
#(10*`timeslice2);
W_R=1;
//RD拉高一个时钟,收到ACK后再次循环,循环次数test_number
repeat(test_number)begin
#(5*`timeslice2)
RD=1;
#(`timeslice2)
RD=0;
@(posedge ACK);
end
end
//读地址存储器,数据存储器,test_number个时钟;并将地址与对应数据写入eeprom.dat文件
initial begin
$display("writing----writing----writing----writing");
#(2*`timeslice2);
for(i=0;i<test_number;i=i+1)begin
ADDR=addr_mem[i];
data_to_eeprom=data_mem[i];
// $fdisplay(OUTFILE,"@%0h %0h",ADDR,data_to_eeprom);
$fdisplay(OUTFILE,"%0h",data_to_eeprom);
@(posedge ACK);
end
end
//将eeprom.dat文件数据存于ROM;比较读eeprom.dat文件数据与写入数据是否一致
initial@(posedge W_R)begin
ADDR=addr_mem[0];
$fclose(OUTFILE);
$readmemh("C:/users/lenovo/Desktop/example/eeprom.dat",ROM);
$display("Begin READING----READING----READING----READING");
for(j=0;j<=test_number;j=j+1)begin
ADDR=addr_mem[j];
@(posedge ACK);
if(data_mem[ADDR]==ROM[ADDR])
$display("data_mem%0h==ROM_[%0h]%0h---READ RIGHT",data_mem[ADDR],ADDR,ROM[ADDR]);
else
$display("data_mem%0h!==ROM[%0h]%0h---READ WRONG",data_mem[ADDR],ADDR,ROM[ADDR]);
end
end
//将dat文件中地址,数据分别储存在addr_mem,data_mem存储器;打开eeprom.dat文件
initial begin
OUTFILE=$fopen("C:/users/lenovo/Desktop/example/eeprom.dat");
$readmemh("C:/users/lenovo/Desktop/example/addr.dat",addr_mem);
$readmemh("C:/users/lenovo/Desktop/example/data.dat",data_mem);
end
endmodule
其中,addr.dat,data.dat分别为存放EEPROM读/写操作地址,写操作数据的文件;eeprom.dat用于输出EEPROM读操作的数据,与写入数据对比是否一致(文件中数据为十六进制表示)。
00
01
02
03
04
05
06
07
08
09
0a
0b
0c
0d
0e
0f
10
11
12
13
14
15
16
17
18
19
1a
1b
1c
1d
1e
1f
20
21
22
23
24
25
26
27
28
29
2a
2b
2c
2d
2e
2f
30
31
32
33
34
35
36
37
38
39
3a
3b
3b
3a
39
38
37
36
35
34
33
32
31
30
2f
2e
2d
2c
2b
2a
29
28
27
26
25
24
23
22
21
20
1f
1e
1d
1c
1b
1a
19
18
17
16
15
14
13
12
11
10
0f
0e
0d
0c
0b
0a
09
08
07
06
05
04
03
02
01
00
仿真结果如下图所示:
写操作:
可观察到DATA此时从Signal模块输出至EEPROM读写器,该数据为EEPROM写操作的数据;WR为写信号,EEPROM读写器检测到该信号后,从SDA数据线给EEPROM器件发出写入帧信号;ADDR为写入地址。
读操作:
可观察到Signal发出读信号RD一段时间后,EEPROM读写器会发出应答信号ACK,同时返回读EEPROM数据DATA;将读数据与写入数据进行对比,可验证程序正确性:
EEPROM:用于模拟真实的EEPROM(AT 24C02/4/8/16)的随机读写功能,该模块的存在是为了验证EEPROM读写器的功能,是行为模型,采取阻塞赋值,不具备可综合风格。该模块与EEPROM读写器之间通过双向总线SCL和SDA进行信息交互。其中,SCL为串行时钟线,SDA为串行数据线。
当进行EEPROM的读操作时,SDA数据帧格式为:{启动,控制字节,应答,地址字节,应答,数据字节,应答,停止},无时间空隙。
当进行EEPROM的写操作时,SDA数据帧格式为:{启动,控制字节,应答,地址字节,应答,启动,控制字节,数据字节,非应答,停止},无时间空隙。
SDA数据线上数据,每比特占据一个时钟周期,与上升沿下降沿中心线对齐。
代码如下所示:
module EEPROM(
input scl ,//串行时钟线 时钟周期400ns
inout sda //串行数据线
);
reg out_flag ;//SDA数据线的控制信号,1:SDA为输出端口
reg[7:0] memory[2047:0] ;//模拟EEPROM存储器
reg[10:0] address ;
reg[7:0] memory_buf ;//写操作数据寄存器
reg[7:0] sda_buf ;//SDA数据输出寄存器
// reg[7:0] shift ;//SDA数据输入寄存器
reg[7:0] addr_byte ;//EEPROM地址寄存器
reg[7:0] ctrl_byte ;//控制字节寄存器
reg[1:0] State ;//状态寄存器
integer i ;
//控制信息:1010+XXX(芯片地址)+0/1(W/R)
parameter r7=8'b10101111,w7=8'b10101110,
r6=8'b10101101,w6=8'b10101100,
r5=8'b10101011,w5=8'b10101010,
r4=8'b10101001,w4=8'b10101000,
r3=8'b10100111,w3=8'b10100110,
r2=8'b10100101,w2=8'b10100100,
r1=8'b10100011,w1=8'b10100010,
r0=8'b10100001,w0=8'b10100000;
assign sda=(out_flag==1)? sda_buf[7]:1'bz ;//out_flag=1,将sda_buf在SDA总线输出
//寄存器和存储器初始化
initial begin
addr_byte = 0 ;
ctrl_byte = 0 ;
out_flag = 0 ;
sda_buf = 0 ;
State = 2'b00 ;
memory_buf = 0 ;
address = 0 ;
// shift = 0 ;
for(i=0;i<=2047;i=i+1)
memory[i]=0;
end
//scl为高电平,sda下降沿即表示启动信号
/*若State='b00,检测到启动信号,State跳变为'b01 ;
若State='b10,检测到启动信号,State跳变为'b11,
此时为读操作的第二个启动信号,需跳出write_to_eeprm任务;
*/
always@(negedge sda)begin
if(scl==1)begin
State = State+1;
if(State==2'b11)
disable write_to_eeprm;//disable用于跳出任何循环,可从外部关闭task
end
end
/*
scl为高电平且sda上升沿,表示停止信号,运行stop_W_R任务;
scl为低电平且sda上升沿(控制字节第一个比特为高电平),表示控制字节来临,从sda数据线读入:
若State='b01,控制字节表示W,则状态跳转到'b10,同时进行写操作;若控制字节不表示W,状态跳转至'b00;
若State='b11,进行读操作。
*/
always@(posedge sda)begin
if(scl==1)
stop_W_R;
else begin
casex(State)
2'b01:begin
read_in;
if(ctrl_byte==w7||ctrl_byte==w6||ctrl_byte==w5
||ctrl_byte==w4||ctrl_byte==w3||ctrl_byte==w2
||ctrl_byte==w1||ctrl_byte==w0)
begin
State = 2'b10;
write_to_eeprm;
end
else
State = 2'b00;
end
2'b11:
read_from_eeprm;
default:
State = 2'b00;
endcase
end
end
//操作停止,返回初始状态
task stop_W_R;
begin
State = 2'b00;
addr_byte =0;
ctrl_byte =0;
out_flag =0;
sda_buf =0;
end
endtask
//从SDA总线读进控制字节和地址字节,
//SDA数据线内容为:{8比特控制字节,1比特应答,8比特地址,1比特应答}无时间空隙
//SDA数据线上数据,每比特占据一个时钟周期,与上升沿下降沿中心位置对齐
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte);
end
endtask
//从SDA总线读进写操作数据,将数据存入EEPROM指定地址空间,返回初始状态
task write_to_eeprm;
begin
shift_in(memory_buf);
address = {ctrl_byte[3:1],addr_byte};
memory[address] = memory_buf;
$display("eeprm------memory[%0h]=%0h",address,memory[address]);
State=2'b00;
end
endtask
//当SDA总线读进控制字节表示R时,从EEPROM读出对应地址数据,存储于sda_buf
//将数据通过SDA数据线输出,并返回初始状态
task read_from_eeprm;
begin
shift_in(ctrl_byte);
if(ctrl_byte==r7||ctrl_byte==r6||ctrl_byte==r5
||ctrl_byte==r4||ctrl_byte==r3||ctrl_byte==r2
||ctrl_byte==r1||ctrl_byte==r0)
begin
address = {ctrl_byte[3:1],addr_byte};
sda_buf = memory[address];
shift_out ;
State = 2'b00;
end
end
endtask
//在scl上升沿,从sda数据线读进数据(7次),读取完成后,输出应答信号
//sda_buf=0,out_flag拉高一个时钟,即代表输出一个时钟低电平。
task shift_in;
output[7:0] shift ;
begin
@(posedge scl) shift[7] = sda ;
@(posedge scl) shift[6] = sda ;
@(posedge scl) shift[5] = sda ;
@(posedge scl) shift[4] = sda ;
@(posedge scl) shift[3] = sda ;
@(posedge scl) shift[2] = sda ;
@(posedge scl) shift[1] = sda ;
@(posedge scl) shift[0] = sda ;
@(negedge scl)
begin
#`timeslice;//1/4个周期
out_flag = 1;
sda_buf = 0;
end
@(negedge scl)
#`timeslice out_flag = 0;
end
endtask
//将控制信号out_flag设置为1,当scl下降沿时,左移sda_buf。
task shift_out;
begin
out_flag = 1;
for(i=7;i>0;i=i-1)begin
@(negedge scl);
#`timeslice;
sda_buf = sda_buf<<1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1;//非应答
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule
仿真结果如下图所示:
写操作:其中A段为启动信号,B段为控制字节,D段为地址字节,F段为数据字节,C,E,G段为应答位,H为停止信号。
读操作:其中A段为启动信号,B段为控制字节,D段为地址字节,F段为另一个启动信号,G为数据字节,C,E,H段为应答位,I为数据字节,J为非应答信号,K为停止信号。
EEPROM_WR:EEPROM读写控制器模块,在接收到信号源产生的写信号时,将控制字节,并行地址信号,并行数据信号转换为相应的串行信号发送到EEPROM行为模型中去。在接收到信号源产生的读信号时,将控制字节,并行地址信号,转换为相应的串行信号发送到EEPROM模型,并读取从EEPROM输出的寻址存储单元数据。并发送ACK给信号源模块,用于调节发送或接收数据的速度。
该模块为使用非阻塞赋值,可综合为门级网表。
代码如下所示:
module EEPROM_WR(
input RESET ,//复位信号
input CLK ,//时钟信号
input WR ,//写信号
input RD ,//读信号
input[10:0] ADDR ,//地址(读/写)
inout[7:0] DATA ,//in:写EEPROM数据
inout SDA ,//串行数据线
output reg SCL ,//串行时钟线
output reg ACK //读写应答信号,用于调节信号源收发数据速度
);
reg WF ;//写操作标志
reg RF ;//读操作标志
reg FF ;//标志寄存器
reg link_sda ;//从SDA数据线输出开关
reg link_read ;//从SDA数据线读进开关
reg link_head ;//启动信号开关
reg link_write ;//写EEPROM操作开关
reg link_stop ;//停止信号开关
reg[1:0] head_buf ;//启动信号寄存器
reg[1:0] stop_buf ;//停止信号寄存器
reg[7:0] sh8out_buf ;//EEPROM写寄存器,存储写入EEPROM的控制字,地址,数据
reg[7:0] data_from_rm ;//EEPROM读寄存器,从EEPROM读出的数据
reg[8:0] sh8out_state ;//EEPROM写状态寄存器
reg[9:0] sh8in_state ;//EEPROM读状态寄存器
reg[2:0] head_state ;//启动状态寄存器
reg[2:0] stop_state ;//停止状态寄存器
reg[10:0] main_state ;//主状态寄存器
wire sda1 ;
wire sda2 ;
wire sda3 ;
wire sda4 ;
assign sda1 = (link_head )? head_buf[1] : 1'b0 ;//link_head为1时,将启动信号输出至sda1
assign sda2 = (link_write)? sh8out_buf[7] : 1'b0 ;//link_write为1时,将写寄存器输出至sda2
assign sda3 = (link_stop )? stop_buf[1] : 1'b0 ;//link_stop为1时,将停止信号输出至sda3
assign sda4 = (sda1|sda2|sda3);
assign SDA = (link_sda)? sda4 : 1'bz ;//link_sda为1时,EEPROM_WR从SDA线输出至EEPROM
assign DATA = (link_read)? data_from_rm : 8'hzz;//link_read为1时,DATA为从EEPROM读出的数据
parameter
Idle = 11'b00000000001,
Ready = 11'b00000000010,
Write_start = 11'b00000000100,
Ctrl_write = 11'b00000001000,
Addr_write = 11'b00000010000,
Data_write = 11'b00000100000,
Read_start = 11'b00001000000,
Ctrl_read = 11'b00010000000,
Data_read = 11'b00100000000,
Stop = 11'b01000000000,
Ackn = 11'b10000000000,
sh8out_bit7 = 9'b000000001,
sh8out_bit6 = 9'b000000010,
sh8out_bit5 = 9'b000000100,
sh8out_bit4 = 9'b000001000,
sh8out_bit3 = 9'b000010000,
sh8out_bit2 = 9'b000100000,
sh8out_bit1 = 9'b001000000,
sh8out_bit0 = 9'b010000000,
sh8out_end = 9'b100000000;
parameter
sh8in_begin = 10'b0000000001,
sh8in_bit7 = 10'b0000000010,
sh8in_bit6 = 10'b0000000100,
sh8in_bit5 = 10'b0000001000,
sh8in_bit4 = 10'b0000010000,
sh8in_bit3 = 10'b0000100000,
sh8in_bit2 = 10'b0001000000,
sh8in_bit1 = 10'b0010000000,
sh8in_bit0 = 10'b0100000000,
sh8in_end = 10'b1000000000,
head_begin = 3'b001,
head_bit = 3'b010,
head_end = 3'b100,
stop_begin = 3'b001,
stop_bit = 3'b010,
stop_end = 3'b100;
parameter Yes = 1,
No = 0;
//产生串行时钟SCL,为输入时钟CLK的二分频,时钟周期400ns
always@(negedge CLK)begin
if(RESET)
SCL <= 0;
else
SCL <= ~SCL;
end
always@(posedge CLK)begin
if(RESET)begin//初始化寄存器
link_read <= No;
link_write <= No;
link_head <= No;
link_stop <= No;
link_sda <= No;
ACK <= 0 ;
RF <= 0 ;
WF <= 0 ;
FF <= 0 ;
main_state <= Idle ;
end
else begin
casex(main_state)
Idle:begin //检测到读/写信号跳转到Ready状态
link_read <= No;
link_write <= No;
link_head <= No;
link_stop <= No;
link_sda <= No;
if(WR)begin
WF <= 1 ;
main_state <= Ready ;
end
else if(RD)begin
RF <= 1 ;
main_state <= Ready ;
end
else begin
WF <= 0 ;
RF <= 0 ;
main_state <= Idle ;
end
end
/*打开启动信号开关,sda数据线开关,设置启动信号停止信号初始值
启动状态设置为head_begin,跳转至Write_start
*/
Ready:begin
link_read <= No;
link_write <= No;
link_head <= Yes;//启动信号开关打开,从SDA输出启动信号
link_stop <= No;
link_sda <= Yes;//将本模块产生的SDA数据输出给EEPROM
head_buf[1:0] <= 2'b10;//移位可产生下降沿
stop_buf[1:0] <= 2'b01;//移位可产生上升沿
head_state <= head_begin;//启动状态
FF <= 0;
ACK <= 0;
main_state <= Write_start ;
end
/*调用shift_head,直至FF不为0,可在SCL为高电平时,生成一个下降沿启动信号sda1
FF仅拉高一个时钟,将启动信号开关关闭,打开EEPROM写操作开关,EEPROM写寄存器设置为控制字
EEPROM写状态设置为sh8out_bit6,mainState跳转为Ctrl_write
*/
Write_start:begin
if(FF==0)
shift_head;//输出启动信号
else begin
sh8out_buf[7:0] <= {1'b1,1'b0,1'b1,1'b0,ADDR[10:8],1'b0};//EEPROM写寄存器存储地址
link_head <= No;
link_write <= Yes; //SDA数据线输出地址
FF <= 0;
sh8out_state <= sh8out_bit6;
main_state <= Ctrl_write;
end
end
/*调用shift8_out,直至FF不为0,即可将sh8out_buf中控制字转换为串行数据sda2
数据在SCL为低电平时变化,在SCL为高电平时保持稳定,紧接着关闭sda开关,
关闭写操作开关,FF仅拉高一个时钟;
将EEPROM写状态设置为sh8out_bit7,EEPROM写寄存器设置为地址,mainState跳转为Addr_write
*/
Ctrl_write:begin
if(FF==0)
shift8_out;
else begin//FF=1,即控制字节输出完毕
sh8out_state <= sh8out_bit7;//多一个状态用于提前开启sda,write开关
sh8out_buf[7:0] <= ADDR[7:0];
FF <= 0;
main_state <= Addr_write;
end
end
/*调用shift8_out,直至FF不为0,即可将sh8out_buf中地址字转换为串行数据sda2
数据在SCL为低电平时变化,在SCL为高电平时保持稳定,紧接着关闭sda开关,
关闭写操作开关,FF仅拉高一个时钟;
若写操作,将EEPROM写状态设置为sh8out_bit7,EEPROM写寄存器设置为数据,mainState跳转为Data_write
若读操作,重置启动信号,启动状态,mainState跳转为Read_start
*/
Addr_write:begin
if(FF==0)//输出地址
shift8_out;
else begin//地址输出完成
FF <= 0;
if(WF)begin//若写操作,继续输出数据
sh8out_state <= sh8out_bit7;
sh8out_buf[7:0] <= DATA;
main_state <= Data_write;
end
if(RF)begin//若读操作,再次输出启动信号
head_buf <= 2'b10;
head_state <= head_begin;
main_state <= Read_start;
end
end
end
/*调用shift8_out,直至FF不为0,即可将sh8out_buf中数据字转换为串行数据sda2
数据在SCL为低电平时变化,在SCL为高电平时保持稳定,紧接着关闭sda开关,
关闭写操作开关,FF仅拉高一个时钟;
将EEPROM写操作开关关闭,设置停止状态为stop_begin,mainState跳转为Stop
*/
Data_write:begin
if(FF==0)//输出数据字节
shift8_out;
else begin//输出停止信号
stop_state <= stop_begin;
main_state <= Stop;
link_write <= No;
FF <= 0;
end
end
/*调用shift_head,直至FF不为0,可在SCL为高电平时,生成一个下降沿启动信号sda1
FF仅拉高一个时钟,将启动信号开关关闭,打开EEPROM写操作开关,EEPROM写寄存器设置为控制字
EEPROM写状态设置为sh8out_bit6,mainState跳转为Ctrl_read
*/
Read_start:begin
if(FF==0)//输出启动信号
shift_head;
else begin//输出读控制字
sh8out_buf <= {1'b1,1'b0,1'b1,1'b0,ADDR[10:8],1'b1};
link_head <= No;
link_sda <= Yes;
link_write <= Yes;
FF <= 0;
sh8out_state <= sh8out_bit6;
main_state <= Ctrl_read;
end
end
/*调用shift8_out,直至FF不为0,即可将sh8out_buf中控制字转换为串行数据sda2
数据在SCL为低电平时变化,在SCL为高电平时保持稳定,紧接着关闭sda开关,
关闭写操作开关,FF仅拉高一个时钟;
将EEPROM写操作开关,sda输入EEPROM开关关闭,设置EEPROM读状态为sh8in_begin,
mainState跳转为Data_read
*/
Ctrl_read:begin
if(FF==0)
shift8_out;
else begin
link_sda <= No;
link_write <= No;
FF <= 0;
sh8in_state <= sh8in_begin;
main_state <= Data_read;
end
end
/*调用shift8in,直至FF不为0,即可读进SDA数据,并转换为并行data_from_rm;
数据在SCL为高电平时读取;紧接着打开读EEPROM开关,拉高FF,sh8in_state跳转为sh8in_bit7
FF仅拉高一个时钟;
打开sda数据线开关,停止信号开关,设置停止状态为stop_bit,mainState跳转为Stop
*/
Data_read:begin
if(FF==0)
shift8in;//读数据
else begin//输出停止信号
link_stop <= Yes;
link_sda <= Yes;
stop_state <= stop_bit;
FF <= 0;
main_state <= Stop;
end
end
/*调用shift_stop,直至FF不为0,可在SCL为高电平时,生成一个上升沿停止信号sda3
FF仅拉高一个时钟,将停止信号开关关闭。
应答信号ACK拉高,mainState跳转为Ackn*/
Stop:begin
if(FF==0)
shift_stop;
else begin
ACK <= 1;
FF <= 0;
main_state <= Ackn;
end
end
/*ACK仅拉高一个时钟就拉低,读写操作标志WF,RF,状态返回初始状态*/
Ackn:begin
ACK <= 0;
WF <= 0;
RF <= 0;
main_state <= Idle;
end
default: main_state <= Idle;
endcase
end
end
/*将串行数据转换为并行数据
在SCL为高时,在CLK上升沿,将串行数据线SDA8个时钟的数据读进,转换成位宽8的信号data_from_rm
读完一个字节后,打开EEPROM读操作开关link_read,标志寄存器FF拉高,状态跳转为sh8in_bit7
*/
task shift8in;
begin
casex(sh8in_state)//EEPROM读状态寄存器
sh8in_begin:
sh8in_state <= sh8in_bit7;
sh8in_bit7:
if(SCL)begin
data_from_rm[7] <= SDA;
sh8in_state <= sh8in_bit6;
end
else
sh8in_state <= sh8in_bit7;
sh8in_bit6:
if(SCL)begin
data_from_rm[6] <= SDA;
sh8in_state <= sh8in_bit5;
end
else
sh8in_state <= sh8in_bit6;
sh8in_bit5:
if(SCL)begin
data_from_rm[5] <= SDA;
sh8in_state <= sh8in_bit4;
end
else
sh8in_state <= sh8in_bit5;
sh8in_bit4:
if(SCL)begin
data_from_rm[4] <= SDA;
sh8in_state <= sh8in_bit3;
end
else
sh8in_state <= sh8in_bit4;
sh8in_bit3:
if(SCL)begin
data_from_rm[3] <= SDA;
sh8in_state <= sh8in_bit2;
end
else
sh8in_state <= sh8in_bit3;
sh8in_bit2:
if(SCL)begin
data_from_rm[2] <= SDA;
sh8in_state <= sh8in_bit1;
end
else
sh8in_state <= sh8in_bit2;
sh8in_bit1:
if(SCL)begin
data_from_rm[1] <= SDA;
sh8in_state <= sh8in_bit0;
end
else
sh8in_state <= sh8in_bit1;
sh8in_bit0:
if(SCL)begin
data_from_rm[0] <= SDA;
sh8in_state <= sh8in_end;
end
else
sh8in_state <= sh8in_bit0;
sh8in_end:
if(SCL)begin
link_read <= Yes;//不用立刻拉低,不影响SDA线输出
FF <= 1 ;
sh8in_state <= sh8in_bit7;
end
else
sh8in_state <= sh8in_end;
default:begin
link_read <= No;
sh8in_state <= sh8in_bit7;
end
endcase
end
endtask
/*将并行数据转化为串行数据
打开SDA输出开关,写EEPROM开关,将EEPROM写寄存器sh8out_buf赋值给sda2
SCL 为低电平时改变SDA上数据,SCL为高时,数据稳定。
link_sda&&link_write为输出valid信号
*/
task shift8_out;
begin
casex(sh8out_state)//EEPROM写状态寄存器
sh8out_bit7:
if(!SCL)begin //为了在SCL低电平时,改变SDA值
link_sda <= Yes ;//将EEPROM_WR数据从SDA输出开关
link_write <= Yes ;//写EEPROM操作开关
sh8out_state <= sh8out_bit6 ;
end
else
sh8out_state <= sh8out_bit7 ;
sh8out_bit6:
if(!SCL)begin
link_sda <= Yes ;
link_write <= Yes ;
sh8out_state <= sh8out_bit5 ;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit6 ;
sh8out_bit5:
if(!SCL)begin
sh8out_state <= sh8out_bit4 ;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit5 ;
sh8out_bit4:
if(!SCL)begin
sh8out_state <= sh8out_bit3 ;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit4 ;
sh8out_bit3:
if(!SCL)begin
sh8out_state <= sh8out_bit2 ;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit3 ;
sh8out_bit2:
if(!SCL)begin
sh8out_state <= sh8out_bit1 ;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit2 ;
sh8out_bit1:
if(!SCL)begin
sh8out_state <= sh8out_bit0 ;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit1 ;
sh8out_bit0:
if(!SCL)begin
sh8out_state <= sh8out_end ;
sh8out_buf <= sh8out_buf<<1;
end
else
sh8out_state <= sh8out_bit0 ;
sh8out_end:
if(!SCL)begin
link_sda <= No ;
link_write <= No;
FF <= 1 ;
end
else
sh8out_state <= sh8out_end ;
endcase
end
endtask
//输出启动信号 下降沿 head_buf:2'b10;
//link_sda&&link_head为启动valid信号
task shift_head;
begin
casex(head_state)//启动状态寄存器
head_begin:
if(!SCL)begin
link_write <= No;//写EEPROM操作开关
link_sda <= Yes;//将EEPROM_WR数据从SDA输出开关
link_head <= Yes;//启动信号开关打开
head_state <= head_bit;
end
else
head_state <= head_begin;
head_bit:
if(SCL)begin //在SCL为高时,形成下降沿
FF <= 1;
head_buf <= head_buf<<1;//启动信号寄存器移位
head_state <= head_end;
end
else
head_state <= head_bit;
head_end:
if(!SCL)begin
link_head <= No;
link_write <= Yes;
end
else
head_state <= head_end;
endcase
end
endtask
//输出停止信号 上升沿stop_buf[1:0] : 2'b01;
//link_sda&&link_stop为停止valid信号
task shift_stop;
begin
casex(stop_state)
stop_begin:
if(!SCL)begin
link_sda <= Yes;
link_write <= No;
link_stop <= Yes;
stop_state <= stop_bit;
end
else
stop_state <= stop_begin;
stop_bit:
if(SCL)begin
stop_buf <= stop_buf<<1;
stop_state <= stop_end;
end
else
stop_state <= stop_bit;
stop_end:
if(!SCL)begin
link_head <= No;
link_stop <= No;
link_sda <= No;
FF <= 1;
end
else
stop_state <= stop_end;
endcase
end
endtask
endmodule
仿真结果如下图所示:
写操作:
可观察到,当检测到写信号时,EEPROM读写器将并行地址信号ADDR,并行数据信号DATA转换为相应的串行信号通过SDA数据线发送到EEPROM行为模型中。
读操作:
可观察到,当检测到读信号时,EEPROM读写器将并行地址信号ADDR,转换为相应的串行信号通过SDA数据线发送到EEPROM行为模型中。并读取EEPROM从SDA数据线上输出的寻址存储单元数据,并转换为并行数据DATA。
Top模块,用于把上述模块连接起来,以便用于测试。
module top_EEPROM;
wire RESET,CLK,RD,WR,ACK,SCL,SDA;
wire[10:0] ADDR ;
wire[7:0] DATA ;
parameter test_numbers=50;
initial #(`timeslice*180*test_numbers) $stop ;
Signal
#(test_numbers) signal(
.RESET (RESET ),
.CLK (CLK ),
.RD (RD ),
.WR (WR ),
.ADDR (ADDR ),
.ACK (ACK ),
.DATA (DATA )
);
EEPROM_WR
eeprom_wr(
.RESET (RESET ),
.SDA (SDA ),
.SCL (SCL ),
.ACK (ACK ),
.CLK (CLK ),
.WR (WR ),
.RD (RD ),
.ADDR (ADDR ),
.DATA (DATA )
);
EEPROM
eeprom(
.sda (SDA ),
.scl (SCL )
);
endmodule