Verilog实现二线制I2C CMOS串行EEPROM的读写操作
1,二线制I2C CMOS串行EEPROM的简单介绍
概述:
在设计复杂的数字电路过程中,可通过仿真调试来改进源代码,使其完全符合设计要求。
介绍一个经过实际运行、验证并可综合到各种FPGA和ASIC工艺的串行EEPROM读写器件的设计过程。该器件能把并行数据和地址信号转变为串行EEPROM能识别的串行码,并把数据写入相应的地址,或根据并行的地址信号从EEPROM相应的地址读取数据并把相应的串行码转换成并行的数据放到并行地址总线上。
二进制I2C CMOS 串行EEPROM AT24C02/4/8/16是一种采用CMOS工艺制成的串行可用电擦除可编程随机读写存储器。串行EEPROM一般具有两种写入方式:
1, 字节写入方式;
2,页写入方式,其允许在一个写周期内同时对一个字节到一页的若干字节进行编程写入。 一页的大小取决于芯片内页寄存器的大小,不同公司的同一种型号存储器内的页寄存器可能是不一样的。
本项目只编写串行EEPROM的一个字节都写入和读出方式的Verilog HDL行为模型代码,串行EEPROM读写器的Verilog HDL模型也只是字节读写方式的可综合模型,对于页写入和读出方式,建议读者参考有关书籍,通过自己改写串行EEPROM的行为模型和串行EEPROM读写器的可综合模型,进而真正掌握本项目的内容。
2,I2C总线特征介绍
I2C(inter integrated circuit)双向二进制串行总线协议定义是:
只有在总线处于“非忙”状态时,数据传输才能开始。在数据传输期间,只要时钟线是高电平,数据线都必须保持稳定,否则数据线上的任何变化都被当作“启动”或“停止”信号。图16.1是被定义的总线状态。
以下介绍A、B、C、D段的工作状态。
(1)总线非忙状态(A段):
该段内的数据线(SDA)和时钟线(SCL)都保持高电平。
(2)启动数据传输(B段):
当时钟线(SCL)是高电平状态时,数据线(SDA)由高电平变为低电平的下降沿被认为是“启动”信号。只有出现“启动”信号后,其他的命令才有效。
(3)停止数据传输(C段):
当时钟线(SCL)是高电平状态时,数据线(SDA)由低电平变为高电平的上升沿被认为是“停止”信号。随着“停止”信号的出现,所有的外部操作都结束。
(4)数据有效(D段):
在出现“启动”信号以后,在时钟线(SCL)是高电平状态时,数据线是稳定的,这时数据线的状态就是要传送的数据。数据线(SDA)上数据的改变必须在时钟线是低电平期间完成,每位数据占用一个时钟脉冲。每个数据传输都是由“启动”信号开始,结束于“停止”信号。
(5)应答信号:
每个正在接收数据的EEPROM在接到一个字节的数据后,通常需要发出一个应答信号。而每个正在发送数据的EEPROM在发出一个字节的数据后,通常需要接收一个应答信号。EEPROM读写控制器必须产生一个与这个应答位相联系的额外的时钟脉冲。在EEPROM的读操作中,EEPROM读写控制器对EEPROM完成的最后一个字节不产生应答位,但是应该给EEPROM一个结束信号。
3,二线制I2C、CMOS串行EEPROM的读写操作
1,EEPROM的写操作(字节编程方式):所谓EEPROM的写操作(字节编程方式)就是通过读写控制器把一个字节数据发送到EEPROM中指定地址的存储单元。其过程如下:
(1)EEPROM读写控制器发出“启动”信号后,紧跟着送4位I2C总线器件特征编码1010和3位EEPROM芯片地址/页地址XXX,以及写状态的R/W位(=0)到总线上。
(2)这一字节表示在接收到被寻址的EEPROM产生的一个应答位后,读写控制器将跟着发送1个字节的EEPROM存储单元地址和要写入的1个字节数据。
(3)EEPROM在接收到存储单元地址后,又一次产生应答位,使读写控制器才发送数据字节,并把数据写入被寻址的存储单元。
(4)EEPROM再一次发出应答信号,读写控制器收到此应答信号后,便产生“停止”信号。AT24C02/4/8/16字节写入帧格式如图2所示。
2,二线制I2C、CMOS串行EEPROM的读操作,所谓EEPROM的读操作是通过读写控制器读取EEPROM中指定地址的存储单元中的一个字节数据。串行EEPROM的读操作分两步进行:
(1)读写器首先发送一个“启动”信号和控制字节(包括页面地址和写控制位)到 EEPROM;
(2)再通过写操作设置EEPROM存储单元地址(注意:虽然这是读操作,但需要先写入地址指针的值),在此期间EEPROM会产生必要的应答位。
接着读写器重新发送另一个“启动”信号和控制字节(包括页面地址和读控制位R/W=1),EEPROM收到后发出应答信号,然后,要寻址存储单元的数据就从SDA线上输出。读操作有3种:
(1)读当前地址存储单元的数据;
(2)读指定地址存储单元的数据;
(3)读连续存储单元的数据。
读指定地址存储单元数据的帧格式如图3所示:
4,EEPROM的Verilog HDL程序
要设计一个串行EEPROM读写器件,不仅要编写EEPROM读写器件的可综合Verilog HDL的代码,而且要编写相应的测试代码以及EEPROM的行为模型。EEPROM的读写电路及其测试电路如图4所示。
(1)EEPROM的行为模型:为了设计一个电路,首先要设计一个EEPROM的Verilog HDL模型。而设计这样一个模型需要仔细地阅读和分析EEPROM器件的说明书,因为EEPROM不是要设计的对象,而要要验证设计对象所需要的器件。所以,只需设计一个EEPROM的行为模型,而不需要可综合风格的模型,这就大大简化了设计过程。下面的Verilog HDL程序就是这个EEPROM(AT24C02/4/8/16)能完成一个字节数据读写的部分行为模型,请读者查阅AT24C02/4/8/16说明书,并对照Verilog HDL程序理解设计的要点。
这里只对在操作中用到的信号线进行模拟,对于没有用到的信号线就略去了。对EEPROM用于基本总线操作的引脚SCL和SDA说明如下,SCL为串行时钟端,这个信号用于对输入和输出数据的同步,而写入串行EEPROM的数据用其上升沿同步,输出数据用其下降沿同步,SDA是串行数据(/地址)输入/输出总线端。
4.1,EEPROM的行为模型思路如下:eeprom.v RTL设计代码
//
/* eeprom.v
用于模拟真实的EEPROM(AT24C02/4/8/16)的随机读写功能。对于符合AT24C02/4/8/16
要求的 scl 和 sda 随机读/写信号能根据I2C协议,分析其含义并进行相应的 读/写 操作
本模块是行为模块,不可综合为 门级网表。
*/
`define timeslice 100
module eeprom(scl, sda);
input scl; // 串行时钟线
inout sda; // 串行数据线
reg out_flag; // SDA 数据输出的控制信号
reg [7:0] memory [2047:0];
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;
//
parameter r7=8'b1010_1111, w7=8'b1010_1110, // main7
r6=8'b1010_1101, w6=8'b1010_1100, // main6
r5=8'b1010_1011, w5=8'b1010_1010, // main5
r4=8'b1010_1001, w4=8'b1010_1000, // main4
r3=8'b1010_0111, w3=8'b1010_0110, // main3
r2=8'b1010_0101, w2=8'b1010_0100, // main2
r1=8'b1010_0011, w1=8'b1010_0010, // main1
r0=8'b1010_0001, w0=8'b1010_0000; // main0
//
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;
// 寄存器和存储器初始化
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
// 启动信号
always@(negedge sda)
if(scl == 1)
begin
state = state + 1; // 注意:Modelsim6.1以上版本,认为从高阻态到1是负跳变沿
if(state == 2'b11)
disable write_to_eeprm; // 禁用名:write_to_eeprm 的过程或任务
end
// 主状态机
always@(posedge sda)
if(scl == 1) // 停止操作
stop_W_R;
else
begin
casex(state)
2'b01: begin
// 注意:老版书上为 2'b01,因为Modelsim 6.0 以下版本 不认为从高阻态到1是跳变沿,
// 而Modelsim 6.1以上版本,在RTL仿真时,认为从高阻态到1是负跳变沿,所以写
// EEPROM操作从 2'b10 状态开始。
// 而做布线后仿真时,Modelsim 6.1以上版本,并不认为高阻态到1是 负跳变沿,所以
// 应该将进入转态2'b10,改为与老版书一致,即2'b01.
// 不同的仿真工具在处理高阻和不定态时有所不同,必须引起设计者的注意
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
// 主状态机结束
// 操作停止
task stop_W_R;
begin
state = 2'b00; // 状态返回为初始状态
addr_byte = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask
// 读进控制字和存储单元地址
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte);
end
endtask
// EEPROM的写操作
task write_to_eeprm;
begin
shift_in(memory_buf);
address = {
ctrl_byte[3:1], addr_byte};
memory[address] = memory_buf;
// $dispaly("eeprm---memory[%0h] = %0h", address, memory[address]);
state = 2'b00; // 回到 0 状态
end
endtask
// EEPROM的读操作
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 = {
ctr