//`timescale 1ns/1ns
//夏宇闻数字逻辑设计
/*在夏宇闻老师的代码中加入了自己理解的详细注解,因为自己也是初学者,如果有不当之处请指出。谢谢*/
/*
① 总线非忙状态(A 段)
数据线SDA 和 时钟线 SCL 都保持高电平。
② 启动数据传输(B 段)
当时钟线(SCL)为高电平状态时,数据线(SDA)由高电平变为低电平的下降沿被认为是
“启动”信号。只有出现“启动”信号后,其它的命令才有效。
③ 停止数据传输(C 段)
当时钟线(SCL)为高电平状态时,数据线(SDA)由低电平变为高电平的上升沿被认为是
“停止”信号。随着“停在”信号出现,所有的外部操作都结束。
④ 数据有效(D 段)
在出现“启动”信号以后,在时钟线(SCL)为高电平状态时数据线是稳定的,这时数据线
的状态就要传送的数据。数据线(SDA)上的数据的改变必须在时钟线为低电平期间完成,
每位数据占用一个时钟脉冲。每个数传输都是由“启动”信号开始,结束于“停止”信号。
⑤ 应答信号
每个正在接收数据的EEPROM 在接到一个字节的数据后,通常需要发出一个应答信号。而每
个正在发送数据的EEPROM 在发出一个字节的数据后,通常需要接收一个应答信号。EEPROM
读写控制器必须产生一个与这个应答位相联系的额外的时钟脉冲。在EEPROM 的读操作中,
EEPROM 读写控制器对EEPROM 完成的最后一个字节不产生应答位,但是应该给EEPROM 一个
结束信号。
*/
module EEPROM(SDA,SCL,ACK,RESET,CLK,WR,RD,ADDR,DATA);
output SCL; //串行时钟线
output ACK; //读写一个周期的应答信号
input RESET; //复位信号
input CLK; //时钟信号输入
input WR,RD; //读写信号
input[10:0] ADDR; //地址线
inout SDA; //串行数据线
inout[7:0] DATA; //并行数据线
reg ACK;
reg SCL;
reg WF,RF; //读写操作标志
reg FF; //标志寄存器
reg [1:0] head_buf; //启动信号寄存器
reg [1:0] stop_buf; //停止信号寄存器
reg [7:0] sh8out_buf; //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; //主状态寄存器
reg [7:0] data_from_rm; //EEPROM读寄存器
reg link_sda; //SDA 数据输入EEPROM开关
reg link_read; //EEPROM读操作开关
reg link_head; //启动信号开关
reg link_write; //EEPROM写操作开关
reg link_stop; //停止信号开关
wire sda1,sda2,sda3,sda4;
//--------------串行数据在开关的控制下有次序的输出或输入-------------------
assign sda1 = (link_head) ? head_buf[1] : 1'b0; //结合head_state任务产生启动信号。link_head、link_stop、link_write同时只能打开一个,所以在打开link_sda开关的情况下sd1相当于与SDA连接在一起。
assign sda2 = (link_write) ? sh8out_buf[7] : 1'b0; //结合sh8out_state任务产生并转串信号输出到sda2信号线。link_head、link_stop、link_write同时只能带开一个,所以在打开link_sda开关的情况下sd2相当于与SDA连接在一起。
assign sda3 = (link_stop) ? stop_buf[1] : 1'b0; //结合stop_state任务产生停止信号。link_head、link_stop、link_write同时只能打开一个,所以在打开link_sda开关的情况下sd3相当于与SDA连接在一起。
assign sda4 = (sda1 | sda2 | sda3); //因为link_head、link_stop、link_write同时只能打开一个,所以sda4相当于与sda1、sda2、sda3中的其中一个连接在一起。
assign SDA = (link_sda) ? sda4 : 1'bz; //在link_sda打开的情况下,若有其它开关打开则SDA与那一路线路相连接在一起。在SCL高电平期间SDA必须为高阻态,否则会被认为是ACK或NO ACK信号
assign DATA = (link_read) ? data_from_rm : 8'hzz;//读开关打开时,用读寄存器中的值驱动DATA线路。
//--------------------------------主状态机状态-----------------------parameter
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, //ACK信号(应答信号)
//-------------------------并行数据串行输出状态-----------------------------
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'b1,
NO = 1'b0;
//-------------产生串行时钟,为输入时钟的二分频-------------------
always @(negedge CLK)
if(RESET)
SCL <= 0;
else
SCL <= ~SCL;
//-----------------------------主状态程序----------------------------------
always @ (posedge CLK)
if(RESET) //同步高电平复位
begin
link_read <= NO;
link_write <= NO;
link_head <= NO; //启动信号开关
link_stop <= NO;
link_sda <= NO; //SDA 数据输入EEPROM开关
ACK <= 0;
RF <= 0;
WF <= 0;
FF <= 0; //用于判断执行的任务是否完成,执行完成后FF置1
main_state <= Idle;
end
else
begin
case(防河蟹)x(main_state) //case(防河蟹)x与casez在仿真时不同,但综合出的电路相同,对X、Z状态表现为don't care
Idle: begin
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 //Idle状态下没有按键按下则在Idle状态下循环
WF <= 0;
RF <= 0;
main_state <= Idle;
end
end
Ready: begin
link_read <= NO;
link_write <= NO;
link_stop <= NO;
link_head <= YES; //打开写信号开关
link_sda <= YES; //打开SDA 数据输入EEPROM开关
head_buf[1:0] <= 2'b10; //启动信号寄存器,用于产生启动信号
stop_buf[1:0] <= 2'b01; //停止信号寄存器,用于产生停止信号
head_state <= head_begin; //启动状态寄存器赋值为启动状态(主状态机Ready状态下的子状态机)
FF <= 0;
ACK <= 0;
main_state <= Write_start;
end
Write_start:
if(FF == 0)
shift_head; //调用输出启动信号任务。输出启动信号
else //待启动信号输出完成时
begin
sh8out_buf[7:0] <= {1'b1,1'b0,1'b1,1'b0,ADDR[10:8],1'b0}; //装载控制字,最后一位为0表示写eeprom
link_head <= NO; //关闭启动信号开关
link_write <= YES; //打开eeprom写操作开关
FF <= 0;
sh8out_state <= sh8out_bit6; //EEPROM 写状态寄存器赋值为主状态机Write_start下的子状态
main_state <= Ctrl_write;
end
Ctrl_write:
if(FF ==0)
shift8_out; //调用并行数据转换为串行数据任务。输出控制字
else //调用的任务(控制值并转串,并通过SDA传输到eeprom)已执行完成时,则执行下面语句块
begin
sh8out_state <= sh8out_bit7;
sh8out_buf[7:0] <= ADDR[7:0]; //装载地址值
FF <= 0; //标志位清零
main_state <= Addr_write;
end
Addr_write:
if(FF == 0)
shift8_out; //调用并行数据转换为串行数据任务。输出地址,选通eeprom地址
else //调用的任务(地址值并转串,并通过SDA传输到eeprom)已执行完成时,则执行下面语句块
begin
FF <= 0;
if(WF) //如果是写信号时
begin
sh8out_state <= sh8out_bit7; //选择并转串任务中状态机的状态。(先传输高位,再传输低位)
sh8out_buf[7:0] <= DATA; //装载用于写eeprom的数据
main_state <= Data_write; //主状态跳转到写外部数据到eeprom的状态
end
if(RF) //如果是读信号时
begin
head_buf <= 2'b10; //装载用于产生启动信号的值
head_state <= head_begin; //选择产生启动信号任务中状态机的状态
main_state <= Read_start; //主状态跳转到读开始状态
end
end
Data_write:
if(FF == 0)
shift8_out; //调用并行数据转换为串行数据任务。输出数据串行数据到SDA
else //调用的任务(外部数据并转串,并通过SDA传输到eeprom)已执行完成时,则执行下面语句块
begin
stop_state <= stop_begin; //置位停止任务中状态机的状态
main_state <= Stop; //主状态跳转到停止状态
link_write <= NO; //关闭写开关
FF <= 0; //标志位清零
end
Read_start:
if(FF == 0)
shift_head; //调用输出启动信号任务
else //调用的启动信号任务完成时,则执行下面语句块
begin
sh8out_buf <= {1'b1,1'b0,1'b1,1'b0,ADDR[10:8],1'b1}; //装载控制字,最后一位为1,表示读eeprom
link_head <= NO;
link_sda <= YES;
link_write <= YES;
FF <= 0; //标志位清零
sh8out_state <= sh8out_bit6; //置位并转串任务中状态机的状态
main_state <= Ctrl_read; //主状态跳转到读控制字状态
end
Ctrl_read:
if(FF == 0)
shift8_out; //调用并行数据转换为串行数据任务,通过SDA输出读控制字
else //输出控制字任务完成时(此时FF已置为1),则执行下面语句块
begin
link_sda <= NO;
link_write <= NO;
FF <= 0;
sh8in_state <= sh8in_begin; //置位串转并任务中状态机的状态
main_state <= Data_read; //主状态跳转到读数据状态
end
Data_read:
if(FF == 0)
shift8in; //串行数据转换为并行数据任务,读8bit eeprom数据转为并行数据,存入data_from_rm寄存器组中
else //数据已读会之后,则执行下面语句块
begin
link_stop <= YES; //打开停止信号开关
link_sda <= YES; //打开控制SDA通断的开关
stop_state <= stop_bit; //置位停止信号任务中状态机的状态
FF <= 0; //标志位清零
main_state <= Stop; //主状态跳转到操作停止状态
end
Stop:
if(FF == 0)
shift_stop; //输出停止信号任务。输出停止信号到SDA线上
else //停止信号已产生并传输完成后,则执行下面语句块
begin
ACK <= 1; //产生应答信号
FF <= 0; //标志位清零
main_state <= Ackn; //主状态跳转到应答状态
end
Ackn: //输出停止信号后输出ACK信号?
begin
ACK <= 0; //关闭应答信号
WF <= 0; //写标志位清零
RF <= 0; //读标志位清零
main_state <= Idle; //主状态跳回到空闲状态
end
default: main_state <= Idle;
endcase
end
//------------------------串行数据转换为并行数据任务----------------------------------
task shift8in; //SDA数据只能在SCL低电平期间传送到data_from_rm中,因为在SCL高电平期间要用于检测启动、停止、ACK、NO ACK信号。
begin
case(防河蟹)x(sh8in_state) //判断eeprom写寄存器状态
sh8in_begin:
sh8in_state <= sh8in_bit7;
sh8in_bit7:
if(SCL) //SCL高电平期间存入数据,因为在高电平期间数据是稳定的
begin
data_from_rm[7] <= SDA; //eeprom读寄存器第7位赋值为SDA上传输的电平(因为此处是二分频,所以当下一个CLK下降沿到来时,SCL一定会反转变为低电平)
sh8in_state <= sh8in_bit6;
end
else
sh8in_state <= sh8in_bit7; //SCL低电平期间,则状态一直停留在当前状态,直到SCL为高电平时将SDA上的数据读回
sh8in_bit6:
if(SCL) //SCL高电平期间
begin
data_from_rm[6] <= SDA; //eeprom读寄存器第6位赋值为SDA上传输的电平
sh8in_state <= sh8in_bit5;
end
else
sh8in_state <= sh8in_bit6; //SCL低电平期间,则状态一直停留在当前状态,直到SCL为高电平时将SDA上的数据读回
sh8in_bit5:
if(SCL)
begin
data_from_rm[5] <= SDA; //eeprom读寄存器第5位赋值为SDA上传输的电平
sh8in_state <= sh8in_bit4;
end
else
sh8in_state <= sh8in_bit5; //SCL低电平期间,则状态一直停留在当前状态,直到SCL为高电平时将SDA上的数据读回
sh8in_bit4:
if(SCL)
begin
data_from_rm[4] <= SDA; //eeprom读寄存器第4位赋值为SDA上传输的电平
sh8in_state <= sh8in_bit3;
end
else
sh8in_state <= sh8in_bit4; //SCL低电平期间,则状态一直停留在当前状态,直到SCL为高电平时将SDA上的数据读回
sh8in_bit3:
if(SCL)
begin
data_from_rm[3] <= SDA; //eeprom读寄存器第3位赋值为SDA上传输的电平
sh8in_state <= sh8in_bit2;
end
else
sh8in_state <= sh8in_bit3; //SCL低电平期间,则状态一直停留在当前状态,直到SCL为高电平时将SDA上的数据读回
sh8in_bit2:
if(SCL)
begin
data_from_rm[2] <= SDA; //eeprom读寄存器第2位赋值为SDA上传输的电平
sh8in_state <= sh8in_bit1;
end
else
sh8in_state <= sh8in_bit2; //SCL低电平期间,则状态一直停留在当前状态,直到SCL为高电平时将SDA上的数据读回
sh8in_bit1:
if(SCL)
begin
data_from_rm[1] <= SDA; //eeprom读寄存器第1位赋值为SDA上传输的电平
sh8in_state <= sh8in_bit0;
end
else
sh8in_state <= sh8in_bit1; //SCL低电平期间,则状态一直停留在当前状态,直到SCL为高电平时将SDA上的数据读回
sh8in_bit0:
if(SCL)
begin
data_from_rm[0] <= SDA; //eeprom读寄存器第0位赋值为SDA上传输的电平
sh8in_state <= sh8in_end;
end
else
sh8in_state <= sh8in_bit0; //SCL低电平期间,则状态一直停留在当前状态,直到SCL为高电平时将SDA上的数据读回
sh8in_end:
if(SCL)
begin
link_read <= YES; //打开读开关
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
//------------------------------ 并行数据转换为串行数据任务---------------------------
task shift8_out; //sh8out_buf中的数据只能在SCL低电平期间传送,因为在SCL高电平期间要用于检测启动、停止、ACK、NO ACK信号。
begin
case(防河蟹)x(sh8out_state) //判断eeprom写寄存器状态
sh8out_bit7:
if(!SCL) //SCL低电平期间
begin
link_sda <= YES;
link_write <= YES; //eeprom写寄存器赋值中的值的第7位通过link_write、link_sda开关控制输出到SDA线上
sh8out_state <= sh8out_bit6;
end
else
sh8out_state <= sh8out_bit7; //SCL高电平期间,则状态一直停留在当前状态,直到SCL为低电平时将写寄存器中的对应位数据输出到SDA线上为止
sh8out_bit6:
if(!SCL) //SCL低电平期间
begin
link_sda <= YES;
link_write <= YES;
sh8out_state <= sh8out_bit5;
sh8out_buf <= sh8out_buf<<1; //eeprom写寄存器赋值中的值的第6位通过开关控制输出到SDA线上
end
else
sh8out_state <= sh8out_bit6; //SCL高电平期间,则状态一直停留在当前状态,直到SCL为低电平时将写寄存器中的对应位数据输出到SDA线上为止
sh8out_bit5:
if(!SCL)
begin
sh8out_state <= sh8out_bit4;
sh8out_buf <= sh8out_buf<<1; //eeprom写寄存器赋值中的值的第5位通过开关控制输出到SDA线上
end
else
sh8out_state <= sh8out_bit5; //SCL高电平期间,则状态一直停留在当前状态,直到SCL为低电平时将写寄存器中的对应位数据输出到SDA线上为止
sh8out_bit4:
if(!SCL)
begin
sh8out_state <= sh8out_bit3;
sh8out_buf <= sh8out_buf<<1; //eeprom写寄存器赋值中的值的第4位通过开关控制输出到SDA线上
end
else
sh8out_state <= sh8out_bit4; //SCL高电平期间,则状态一直停留在当前状态,直到SCL为低电平时将写寄存器中的对应位数据输出到SDA线上为止
sh8out_bit3:
if(!SCL)
begin
sh8out_state <= sh8out_bit2;
sh8out_buf <= sh8out_buf<<1; //eeprom写寄存器赋值中的值的第3位通过开关控制输出到SDA线上
end
else
sh8out_state <= sh8out_bit3; //SCL高电平期间,则状态一直停留在当前状态,直到SCL为低电平时将写寄存器中的对应位数据输出到SDA线上为止
sh8out_bit2:
if(!SCL)
begin
sh8out_state <= sh8out_bit1;
sh8out_buf <= sh8out_buf<<1; //eeprom写寄存器赋值中的值的第2位通过开关控制输出到SDA线上
end
else
sh8out_state <= sh8out_bit2; //SCL高电平期间,则状态一直停留在当前状态,直到SCL为低电平时将写寄存器中的对应位数据输出到SDA线上为止
sh8out_bit1:
if(!SCL)
begin
sh8out_state <= sh8out_bit0;
sh8out_buf <= sh8out_buf<<1; //eeprom写寄存器赋值中的值的第1位通过开关控制输出到SDA线上
end
else
sh8out_state <= sh8out_bit1; //SCL高电平期间,则状态一直停留在当前状态,直到SCL为低电平时将写寄存器中的对应位数据输出到SDA线上为止
sh8out_bit0:
if(!SCL)
begin
sh8out_state <= sh8out_end;
sh8out_buf <= sh8out_buf<<1; //eeprom写寄存器赋值中的值的第0位通过开关控制输出到SDA线上
end
else
sh8out_state <= sh8out_bit0; //SCL高电平期间,则状态一直停留在当前状态,直到SCL为低电平时将写寄存器中的对应位数据输出到SDA线上为止
sh8out_end:
if(!SCL)
begin
link_sda <= NO;
link_write <= NO;
FF <= 1;
end
else
sh8out_state <= sh8out_end;
endcase
end
endtask
//--------------------------- 输出启动信号任务 ---------------------------------
task shift_head;
begin
case(防河蟹)x(head_state) //判断启动状态寄存器状态
head_begin:
if(!SCL)
begin
link_write <= NO;
link_sda <= YES;
link_head <= YES;
head_state <= head_bit;
end
else
head_state <= head_begin;
head_bit:
if(SCL)
begin
FF <= 1;
head_buf <= head_buf<<1; //在SCL高电平期间,电平从1到0,因此产生启动信号,在link_head打开的情况下启动信号输送到SDA线路。
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
//--------------------------- 输出停止信号任务 --------------------------------------
task shift_stop;
begin
case(防河蟹)x(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; //在SCL高电平期间,电平从0到1,因此产生停止信号,在link_head打开的情况下停止信号输送到SDA线路。
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
文章评论(0条评论)
登录后参与讨论