16-Verilog实现二线制I2C CMOS串行EEPROM的读写操作

Verilog实现二线制I2C CMOS串行EEPROM的读写操作

  • 1,二线制I2C CMOS串行EEPROM的简单介绍
  • 2,I2C总线特征介绍
  • 3,二线制I2C、CMOS串行EEPROM的读写操作
  • 4,EEPROM的Verilog HDL程序
    • 4.1,EEPROM的行为模型思路如下:eeprom.v RTL设计代码
    • 4.2,EEPROM_WR的行为模型,RTL设计代码:eeprom_wr.v
    • 4.3,下面的程序就是把这个信号源的Verilog HDL模型和顶层模块。RTL代码设计模型是:signal.v
    • 4.4,顶层模块的RTL代码设计模型:tb_top.v
  • 5,SIM输出波
  • 6,总结

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 100module 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,       // main7r6=8'b1010_1101, w6=8'b1010_1100,        // main6r5=8'b1010_1011, w5=8'b1010_1010,        // main5r4=8'b1010_1001, w4=8'b1010_1000,        // main4r3=8'b1010_0111, w3=8'b1010_0110,        // main3r2=8'b1010_0101, w2=8'b1010_0100,        // main2r1=8'b1010_0011, w1=8'b1010_0010,        // main1r0=8'b1010_0001, w0=8'b1010_0000;        // main0
//
assign     sda = (out_flag == 1) ? sda_buf[7] : 1'bz;//   寄存器和存储器初始化
initial  beginaddr_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)beginstate = 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 begincasex(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)beginstate = 2'b10;write_to_eeprm;   // 写操作endelsestate = 2'b00;end2'b11:read_from_eeprm;   // 读操作default: state = 2'b00;endcase
end// 主状态机结束//      操作停止
task stop_W_R;beginstate = 2'b00;    // 状态返回为初始状态addr_byte = 0;ctrl_byte = 0;out_flag  = 0;sda_buf   = 0;       end
endtask//      读进控制字和存储单元地址
task read_in;beginshift_in(ctrl_byte);shift_in(addr_byte);         end    
endtask//  EEPROM的写操作
task write_to_eeprm;beginshift_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;beginshift_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)beginaddress = {ctrl_byte[3:1],addr_byte};sda_buf = memory[address];shift_out;state = 2'b00;endend
endtask//  SDA数据线上的数据存入寄存器,数据在 SCL 的高电平有效
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;out_flag = 1;   // 应答信号输出sda_buf  = 0;end@(negedge scl)#`timeslice     out_flag = 0;end
endtask//     EEPROM存储器中的数据通过 SDA 数据线输出,数据在 SCL 低电平时变化
task shift_out;beginout_flag = 1;for(i=6; 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
endtaskendmodule

(2)EEPROM读写器的可综合的Verilog HDL模型,下面的Verilog程序是一个可综合的EEPROM读写器模型,它接收来自信号源模型产生的读信号、写信号、并行地址信号和并行数据信号,并把它们转换为相应的串行信号发送到串行EEPROM(AT24C02/4/8/16)的行为模型中去。它海发送应答信号(ACK)到信号源模型,以便让信号源来调节发送或接收数据的速度,以配合EEPROM读写器模型从EEPROM行为模型发送(写)和接收(读)数据。因为它是我们的设计对象,所以它的仿真不但要正确无误,还需要能综合成门级网表。
这个程序基本上由两部分组成,开关组合电路和控制时序电路,如下图5所示。开关电路在控制时序电路的控制下,按照设计的要求有节奏地打开或闭合,这样SDA可以按I2C数据总线的格式输出或输入,使SDA和SCL一起完成EEPROM的读写操作。

在这里插入图片描述

电路最终用同步有限状态机(FSM)的设计方法实现。程序实则上是一个嵌套的状态机,由主状态机和从状态机通过由控制线启动的总线在不同的输入信号情况下构成不同功能的较复杂的有限状态机,这个有限状态机只有唯一的驱动时钟CLK。根据串行EEPROM的读写操作时序可知,用5个状态时钟可以完成写操作,用7个状态时钟可以完成读操作。由于读写操作的状态中有几个状态是一致的,用一个嵌套的状态机即可。状态转移如下图6所示。程序由一个读写大任务和若干个较小的任务所组成,其状态机采用独热编码,若需改变状态编码,只需改变程序中的parameter定义即可。
读者可通过模仿这一程序来编写较复杂的可综合Verilog HDL模块程序。这个设计已通过后仿真,并可在FPGA上实现布局布线。

在这里插入图片描述

4.2,EEPROM_WR的行为模型,RTL设计代码:eeprom_wr.v

//	模块名称:EEPROM_WR
//	模块功能:EEPROM读写器根据 MCU 的并行数据、地址线 和 读/写控制线对 EEPROM(AT24C02/4/8/16)
//	的行为模块进行随机读写操作,而且本模块为教学要求做了许多简化,
//	但本模块只能做随机的读写操作,功能不完整。//	本模块为可综合模块,可综合为 门级网表module	eeprom_wr(
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;
assign	sda2	= (link_write) ? sh8out_buf[7] 	: 1'b0;
assign	sda3	= (link_stop)  ? stop_buf[1] 	: 1'b0;
assign	sda4	= (sda1 | sda2 | sda3);
assign	SDA		= (link_sda)   ? sda4 			: 1'bz;
assign	DATA	= (link_read)  ? data_from_rm 	: 8'hzz;//	主状态机状态定义
parameter
Idle			= 11'b000_0000_0001,
Ready			= 11'b000_0000_0010,
Write_start		= 11'b000_0000_0100,
Ctrl_write		= 11'b000_0000_1000,Addr_write		= 11'b000_0001_0000,
Data_write		= 11'b000_0010_0000,
Read_start		= 11'b000_0100_0000,
Ctrl_read		= 11'b000_1000_0000,Data_read		= 11'b001_0000_0000,
Stop			= 11'b010_0000_0000,
Ackn			= 11'b100_0000_0000;//	并行数据串行输出状态
parameter
sh8out_bit7		= 9'b0_0000_0001,
sh8out_bit6		= 9'b0_0000_0010,
sh8out_bit5		= 9'b0_0000_0100,
sh8out_bit4		= 9'b0_0000_1000,sh8out_bit3		= 9'b0_0001_0000,
sh8out_bit2		= 9'b0_0010_0000,
sh8out_bit1		= 9'b0_0100_0000,
sh8out_bit0		= 9'b0_1000_0000,sh8out_end		= 9'b1_0000_0000;//	串行数据并行输出状态
parameter
sh8in_begin		= 10'b00_0000_0001,
sh8in_bit7		= 10'b00_0000_0010,
sh8in_bit6		= 10'b00_0000_0100,
sh8in_bit5		= 10'b00_0000_1000,sh8in_bit4		= 10'b00_0001_0000,
sh8in_bit3		= 10'b00_0010_0000,
sh8in_bit2		= 10'b00_0100_0000,
sh8in_bit1		= 10'b00_1000_0000,sh8in_bit0		= 10'b01_0000_0000,
sh8in_end		= 10'b10_0000_0000;//	启动状态
parameter
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,为输入时钟的2分频
always@(negedge CLK)if(RESET)SCL		<= 0;elseSCL		<= ~SCL;//	主状态机程序
always@(posedge CLK)if(RESET)	beginlink_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;endelse	begincasex(main_state)Idle:	beginlink_read	<= NO;link_write	<= NO;link_head	<= NO;link_stop	<= NO;link_sda	<= NO;if(WR)	beginWF		<= 1;main_state	<= Ready;endelse if(RD)	beginRF			<= 1;main_state	<= Ready;endelse	beginWF	<=	0;RF	<= 	0;main_state	<= Idle;endendReady:		beginlink_read		<= NO;link_write		<= NO;link_stop		<= NO;link_head		<= YES;link_sda		<= YES;head_buf[1:0]	<= 2'b10;stop_buf[1:0]	<= 2'b01;head_state		<= head_begin;FF				<= 0;ACK				<= 0;main_state		<= Write_start;endWrite_start:if(FF == 0)shift_head;else	beginsh8out_buf[7:0]		<= {1'b1, 1'b0, 1'b1, 1'b0, ADDR[10:8], 1'b0};link_head			<= NO;link_write			<= YES;FF					<= 0;sh8out_state		<= sh8out_bit6;main_state			<= Ctrl_write;endCtrl_write:	if(FF == 0)shift8_out;else	beginsh8out_state		<= sh8out_bit7;sh8out_buf[7:0]		<= ADDR[7:0];FF					<= 0;main_state			<= Addr_write;endAddr_write:if(FF == 0)shift8_out;else	beginFF					<= 0;if(WF)		beginsh8out_state	<= sh8out_bit7;sh8out_buf[7:0]	<= DATA;main_state		<= Data_write;endelse if(RF)		beginhead_buf		<= 2'b10;head_state		<= head_begin;main_state		<= Read_start;endendData_write:if(FF == 0)shift8_out;else	beginstop_state		<= stop_begin;main_state		<= Stop;link_write		<= NO;FF				<= 0;endRead_start:if(FF == 0)shift_head;else	beginsh8out_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;endCtrl_read:if(FF == 0)shift8_out;else	beginlink_sda		<= NO;link_write		<= NO;FF				<= 0;sh8in_state		<= sh8in_begin;main_state		<= Data_read;endData_read:if(FF == 0)shift8in;else	beginlink_stop		<= YES;link_sda		<= YES;stop_state		<= stop_bit;FF				<= 0;main_state		<= Stop;endStop:if(FF == 0)shift_stop;else	beginACK				<= 1;FF				<= 0;main_state		<= Ackn;endAckn:beginACK				<= 0;WF				<= 0;RF				<= 0;main_state		<= Idle;enddefault:main_state		<= Idle;endcase		end//	串行数据转换为并行数据任务
task	shift8in;			//	inbegincasex(sh8in_state)sh8in_begin:sh8in_state			<= sh8in_bit7;sh8in_bit7:		if(SCL)		begindata_from_rm[7]		<= SDA;sh8in_state			<= sh8in_bit6;endelsesh8in_state			<= sh8in_bit7;sh8in_bit6:		if(SCL)		begindata_from_rm[6]		<= SDA;sh8in_state			<= sh8in_bit5;endelsesh8in_state			<= sh8in_bit6;sh8in_bit5:		if(SCL)		begindata_from_rm[5]		<= SDA;sh8in_state			<= sh8in_bit4;endelsesh8in_state			<= sh8in_bit5;sh8in_bit4:		if(SCL)		begindata_from_rm[4]		<= SDA;sh8in_state			<= sh8in_bit3;endelsesh8in_state			<= sh8in_bit4;sh8in_bit3:		if(SCL)		begindata_from_rm[3]		<= SDA;sh8in_state			<= sh8in_bit2;endelsesh8in_state			<= sh8in_bit3;sh8in_bit2:		if(SCL)		begindata_from_rm[2]		<= SDA;sh8in_state			<= sh8in_bit1;endelsesh8in_state			<= sh8in_bit2;			sh8in_bit1:		if(SCL)		begindata_from_rm[1]		<= SDA;sh8in_state			<= sh8in_bit0;endelsesh8in_state			<= sh8in_bit1;sh8in_bit0:		if(SCL)		begindata_from_rm[0]		<= SDA;sh8in_state			<= sh8in_end;endelsesh8in_state			<= sh8in_bit0;						sh8in_end:		if(SCL)		beginlink_read			<= YES;FF					<= 1;sh8in_state			<= sh8in_bit7;endelsesh8in_state			<= sh8in_end;default:		beginlink_read			<= NO;sh8in_state			<= sh8in_bit7;endendcaseend
endtask//	并行数据转换为串行数据任务
task	shift8_out;begincasex(sh8out_state)sh8out_bit7:	//	7if(!SCL)beginlink_sda		<= YES;link_write		<= YES;sh8out_state	<= sh8out_bit6;endelsesh8out_state	<= sh8out_bit7;sh8out_bit6:	//	6if(!SCL)		beginlink_sda		<= YES;link_write		<= YES;sh8out_state	<= sh8out_bit5;sh8out_buf		<= sh8out_buf << 1;endelsesh8out_state	<= sh8out_bit6;sh8out_bit5:	//	5if(!SCL)	beginsh8out_state	<= sh8out_bit4;sh8out_buf		<= sh8out_buf << 1;endelsesh8out_state	<= sh8out_bit5;sh8out_bit4:	//	4if(!SCL)	beginsh8out_state	<= sh8out_bit3;sh8out_buf		<= sh8out_buf << 1;endelsesh8out_state	<= sh8out_bit4;sh8out_bit3:	//	3if(!SCL)	beginsh8out_state	<= sh8out_bit2;sh8out_buf		<= sh8out_buf << 1;endelsesh8out_state	<= sh8out_bit3;sh8out_bit2:	//	2if(!SCL)	beginsh8out_state	<= sh8out_bit1;sh8out_buf		<= sh8out_buf << 1;endelsesh8out_state	<= sh8out_bit2;sh8out_bit1:	//	1if(!SCL)	beginsh8out_state	<= sh8out_bit0;sh8out_buf		<= sh8out_buf << 1;endelsesh8out_state	<= sh8out_bit1;sh8out_bit0:	//	0if(!SCL)	beginsh8out_state	<= sh8out_end;sh8out_buf		<= sh8out_buf << 1;endelsesh8out_state	<= sh8out_bit0;sh8out_end:	// endif(!SCL)	beginlink_sda		<= NO;link_write		<= NO;FF				<= 1;endelsesh8out_state	<= sh8out_end;endcaseend
endtask//						输出启动任务
task 	shift_head;begincasex(head_state)head_begin:if(!SCL)		beginlink_write		<= NO;link_sda		<= YES;link_head		<= YES;head_state		<= head_bit;endelsehead_state		<= head_begin;head_bit:if(SCL)		beginFF				<= 1;head_buf		<= head_buf << 1;head_state		<= head_end;endelsehead_state		<= head_bit;head_end:if(!SCL)	beginlink_head		<= NO;link_write		<= YES;endelsehead_state		<= head_end;endcaseendendtask//		输出停止信号任务
task	shift_stop;begincasex(stop_state)stop_begin:		if(!SCL)	beginlink_sda	<= YES;link_write	<= NO;link_stop	<= YES;stop_state	<= stop_bit;endelsestop_state	<= stop_begin;stop_bit:if(SCL)		beginstop_buf	<= stop_buf << 1;stop_state	<= stop_end;endelsestop_state	<= stop_bit;stop_end:	if(!SCL)	beginlink_head	<= NO;link_stop	<= NO;link_sda	<= NO;FF			<= 1;endelsestop_state	<= stop_end;endcaseendendtaskendmodule

说明:
eeprom_wr.v,程序模块最终通过各种EDA软件的综合,并在多种系列的FPGA上实现布局布线,最终布线后仿真验证。

(3)EEPROM的信号源模块和顶层模块:完成串行EEPROM读写器件的设计后,我们还需要做EEPROM读写器件的仿真。仿真可以分为前仿真和后仿真,前仿真是Verilog HDL的功能仿真,后仿真是Verilog HDL代码经过综合、布局布线后的时序仿真。为此,我们还要编写用于EEPROM读写器件的仿真测试的信号源程序。这个信号源能产生相应的读信号、写信号、并行地址信号和并行数据信号,并能接收串行EEPROM读写器件的应答信号(ACK),为此来调节发送或接收数据的速度。在这个程序中,为了保证串行EEPROM读写器件的正确性,可以进行完整的测试。写操作时输入的地址信号和数据信号的数据通过系统命令readmemh从addr.dat和data.dat文件中取得,而在addr.dat和data.dat文件中可以存放任意数据。读操作时从EEPROM读出的数据存入文件eeprom.dat。对比3个文件的数据就可以验证程序的正确性。readmemhfopen等系统命令读者可以参考Verilog HDL的语法部分。最后我们把信号源、EEPROM和EEPROM读写器用顶层模块连接在一起。

4.3,下面的程序就是把这个信号源的Verilog HDL模型和顶层模块。RTL代码设计模型是:signal.v

注意:
系统命令:
在这里插入图片描述

//	
/*	模块功能:
用于产生测试信号,对所设计的 EEPROM_WR 模块进行测试。signal模块能对被测试模块产生的 ack 信号
产生响应,发出模仿 MCU 的数据、地址信号和 读/写信号;被测试的模块在接收到信号后会发出
写/读 EEPROM虚拟模块的信号。模块说明:
本模块是行为模块,不可综合为门级网表。而且本模块为教学目的做了许多简化,功能不完整。
*///	信号源的Verilog HDL模型: signal.v
`define		timeslice 200
module	signal(RESET, CLK, RD, WR, ADDR, ACK, DATA);
output				RESET;			//	复位信号
output				CLK;			//	时钟信号
output				RD, WR;			//	读写信号
output	[10:0]		ADDR;			//	11 位地址信号input				ACK;			//	读写周期的应答信号
inout	[7:0]		DATA;			//	数据线reg					RESET;
reg					CLK;
reg					RD, WR;
reg					W_R;			//	低位,写操作;	高位,读操作
reg		[10:0]		ADDR;
reg		[7:0]		data_to_eeprom;
reg		[10:0]		addr_mem	[0:255];
reg		[7:0]		data_mem	[0:255];
reg		[7:0]		ROM			[1:2047];integer		i, j;
integer		OUTFILE;
parameter			test_number		= 50;assign				DATA	= (W_R) ? 8'bz : data_to_eeprom;//	时钟信号输入
always #(`timeslice/2)	CLK = ~CLK;//	读写信号输入
initial		beginRESET	= 1;i		= 0;j		= 0;W_R		= 0;CLK		= 0;RD		= 0;WR		= 0;#1000;RESET	= 0;		repeat(test_number)			//	连续写 test_number 次数据,测试成功后可以增加到全部地址并覆盖测试begin#(5 * `timeslice);WR	= 1;#(`timeslice);WR	= 0;@(posedge ACK);		//	EEPROM_WR 转换模块请求写数据end#(10 * `timeslice);W_R	= 1;			// 开始读操作repeat(test_number)		//	开始读 test_number 次数据begin#(5 * `timeslice);RD	= 1;#(`timeslice)RD	= 0;@(posedge ACK);		// EEPROM_WR 转换模块请求读数据end
end//	写操作
initial		begin$display("writing-----writing-----writing-----writing");#(2 * `timeslice);for(i = 0; i <= test_number; i = i+1)	beginADDR			= addr_mem[i];		//	输出写操作的地址data_to_eeprom	= data_mem[i];		//	输出需要转换的平行数据$fdisplay(OUTFILE, "@%0h  %0h", ADDR, data_to_eeprom);//	把输出的地址和数据记录在已经打开的 eeprom.dat 文件中@(posedge ACK);		//	 EEPROM_WR 转换模块请求写数据end
end//	读操作
initial		
@(posedge W_R)beginADDR		= addr_mem[0];$fclose(OUTFILE);					//	关闭已经打开的 eeprom.dat 文件$readmemh("./eeprom.dat", ROM);		//	把数据文件的数据读到 ROM 中去$display("Begin READING --- READING --- READING --- READING");for(j = 0; j <= test_number; j = j+1)	beginADDR = addr_mem[j];@(posedge ACK)if(DATA == ROM[ADDR])	//	比较并显示发送的数据和接收到的数据是否一致$display("DATA %0h == ROM[%0h] --- READ RIGHT", DATA, ADDR);else $display("DATA %0h != ROM[%0h] --- READ WRONG", DATA, ADDR);endendinitial	beginOUTFILE		 = $fopen("./eeprom.dat");		//	打开一个名是 eeprom.dat 的文件备用$readmemh("E:/vivado_xia_verilog/16_eeprom/addr.dat", addr_mem);			//	把地址数据存入地址存储器$readmemh("E:/vivado_xia_verilog/16_eeprom/data.dat", data_mem);			//	把准备写入 EEPROM 的数据存入数据存储器
endendmodule

4.4,顶层模块的RTL代码设计模型:tb_top.v

/*
顶层模块: top.v
模块功能:用于把产生测试信号的模块(signal)与设计的具体模块(EEPROM_WR)以及EEPROM虚拟模块连接
起来的模块,用于全面测试。
模块说明:本模块是行为模块,不可综合为门级网表。但其中 EEPROM_WR 可以综合为 门级网表,所以可以对所设计的 EEPROM 读写器进行门级仿真。
*/`define			timeslice 200module	tb_top;
wire				RESET;
wire				CLK;
wire				RD, WR;
wire				ACK;wire	[10:0]		ADDR;
wire	[7:0]		DATA;		
wire				SCL;
wire				SDA;parameter			test_numbers	= 7'd123;//  initial		#(`timeslice * 180 * test_numbers)		$stop;//	例化
signal	#(test_numbers)	u1_signal(
.RESET				(RESET			),		
.CLK				(CLK			),
.RD					(RD				),
.WR					(WR				),
.ADDR				(ADDR			),
.ACK				(ACK			),
.DATA				(DATA			)
);eeprom_wr	u1_eeprom_wr(
.RESET				(RESET			),
.SDA				(SDA			),
.SCL				(SCL			),
.ACK				(ACK			),
.CLK				(CLK			),
.WR					(WR				),
.RD					(RD				),
.ADDR				(ADDR			),
.DATA				(DATA			)
);eeprom		u1_eeprom(
.sda				(SDA			),
.scl				(SCL			)
);endmodule

5,SIM输出波

通过前后仿真可以验证程序的正确性。这里给出的是EEPROM读写时序的前仿真波形,如图7和图8所示。后仿真波形除SCL和SDA与CLK有些延迟外,信号的逻辑关系与前仿真一致。

在这里插入图片描述
图7. EEPROM的写程序

在这里插入图片描述
图8. EEPROM的读时序

在这里插入图片描述
图9. EEPROM.dat的数据位置

说明:以上代码已通过RTL仿真、综合、布局布线、和布线后仿真验证。

6,总结

用Verilog设计可实现的数字逻辑电路时,必须对它的电路结构的总体有一个明确的想法。
例如本项目I2C CMOS EEPROM模块的设计,必须要对如何控制sda串行总线有深入细致的认识。Sda总线既用于输出,又用于输入,它必须通过开关网络(组合逻辑电路)与寄存器发生联系。Sda有时与某个寄存器的输出连接,有时又与另一个寄存器的输入连接。而这些连接过程与MCU发过来的命令和数据有关。发出的或接收的信号流又与串行通信协议有关,这些关系都体现在一个或几个状态机中。状态机是这些开关逻辑正确协调操作的指挥者。为了设计好这些状态机必须要搞清楚信号数据的流动和协议。对接口时序的要求必须明确在具体电路模块的设计中体现,也必须在与其连接通信的虚拟模块中明确地用表示行为的Verilog语句体现。只有这样,才能在仿真中帮助我们及时发现复杂状态机编写的漏洞,从而设计出正确无误的电路系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/670259.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Kotlin】Kotlin环境搭建

1 前言 Kotlin 是一种现代但已经成熟的编程语言&#xff0c;由 JetBrains 公司于 2011 年设计和开发&#xff0c;并在 2012 年开源&#xff0c;在 2016 年发布 v1.0 版本。在 2017 年&#xff0c;Google 宣布 Kotlin 正式成为 Android 开发语言&#xff0c;这进一步推动了 Kotl…

【Go】一、Go语言基本语法与常用方法容器

GO基础 Go语言是由Google于2006年开源的静态语言 1972&#xff1a;&#xff08;C语言&#xff09; — 1983&#xff08;C&#xff09;—1991&#xff08;python&#xff09;—1995&#xff08;java、PHP、js&#xff09;—2005&#xff08;amd双核技术 web端新技术飞速发展&…

【Python程序开发系列】聊一聊github的pull request几种合并方式

一、什么是PR 在正常的工作流程中&#xff0c;PR 用于将一个分支的更改合并到另一个分支&#xff0c;而这些更改通常以提交的形式存在。每个提交都有一个唯一的提交 ID&#xff0c;用于标识和跟踪更改的历史。因此一般情况下PR包含源分支的多个commit提交记录&#xff08;pr_co…

数据结构_找环,破环题-2.5

一. 判断单链表有无环 a. 错误的思路&#xff1a;遍历陷入死循环 1&#xff09;和相交的遍历思路一样&#xff0c;找指向相同。 错误点 一直在死循环。 思考点&#xff1a;如何破环 b. 个人思路&#xff1a;反转链表回首结点 1&#xff09;目前的经验&#xff0c;无非就…

无人机集群协同导航构型自适应选择算法

无人机集群协同导航构型自适应选择算法 Evandworld E-mail&#xff1a;evandworldfoxmail.com 摘要 针对卫星定位系统用于无人机集群时成本高、精度低等问题&#xff0c;本文提出一种基于卡尔曼滤波和概率的无人机集群构型自适应选择算法。在自适应扩展卡尔曼滤波的基础上&a…

kubernetes部署nacos2.3.0

一、nacos简介 nacos官网地址 简单易用 动态配置管理、服务发现和动态的一站式解决方案&#xff0c;20多种开箱即用的以服务为中心的架构特性&#xff0c;基本符合生产要求的轻量级易用控制台。 更适应云架构 无缝支持 Kubernetes 和 Spring Cloud&#xff0c;在主流公共云上…

Qt:QFileDialog

目录 一、介绍 二、功能 三、具体事例 1、将某个界面保存为图片&#xff0c;后缀名可选PNG、JPEG、SVG等 一、介绍 QFileDialog提供了一个对话框&#xff0c;允许用户选择文件或者目录&#xff0c;也允许用户遍历文件系统&#xff0c;用以选择一个或多个文件或者目录。 QF…

Debezium发布历史111

原文地址&#xff1a; https://debezium.io/blog/2021/10/07/incremental-snapshots/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. Incremental Snapshots in Debezium October 7, 2021 by Jiri Pechanec mys…

linux虚拟机升级g++编译器版本

原先的 更新你的软件包列表&#xff1a; sudo apt update 添加Ubuntu Toolchain PPA&#xff08;Personal Package Archive&#xff09;&#xff0c;这是一个提供较新编译器版本的第三方软件源&#xff1a; sudo add-apt-repository ppa:ubuntu-toolchain-r/test 再次更新软件包…

Tokitsukaze and Short Path (plus minus)

一、Tokitsukaze and Short Path (plus) 解析&#xff1a; 容易知道 对于 u 和 v 两点之间的距离是 2*max(a[u],a[v]),同时这也是这两个点的最短距离&#xff0c;因为如果想要绕路的话&#xff0c;就必须再至少经过一个点&#xff0c;这样的话&#xff0c;就会使得两者的距离变…

RTthread线程间通信(邮箱,消息队列,信号/软件中断)---02代码分析邮箱和消息队列

RT-Thread代码分析 这是源码分析, 实际使用看这个 信号看这个 看这一篇之前最好看一下我的RT-Thread对象管理以及线程管理, 时钟管理 邮箱 实际是实现是一个对环形缓存区的使用 struct rt_mailbox {struct rt_ipc_object parent; /**< inherit from…

python进行批量搜索匹配替换文本文字的matlab操作实例

在进行一些数据处理时&#xff0c;可能需要抓取原文中的一些内容&#xff0c;批量替换原文另外的一些内容&#xff0c;而且事先还需要一步搜索匹配的步骤。 举个例子&#xff0c;如下matlab输出的txt文件&#xff0c;原文件有几万行数据&#xff0c;这里只摘取3行对应的 文件文…

R语言:箱线图绘制(添加平均值趋势线)

箱线图绘制 1. 写在前面2.箱线图绘制2.1 相关R包导入2.2 数据导入及格式转换2.3 ggplot绘图 1. 写在前面 今天有时间把之前使用过的一些代码和大家分享&#xff0c;其中箱线图绘制我认为是非常有用的一个部分。之前我是比较喜欢使用origin进行绘图&#xff0c;但是绘制的图不太…

vite+vue3发布自己的npm组件+工具函数

记录一下个人最近一次发布npm组件的过程&#xff1a; 一、创建组件和工具函数 执行命令创建一个空项目&#xff1a; npm create vite 创建过程稍微有些慢&#xff0c;不知何故&#xff1f;其中选择vue , 个人暂时使用的JS 。在 src 目录下面创建一个文件 package 存放组件和公…

【Java八股面试系列】JVM-垃圾回收

目录 垃圾回收 堆空间的基本结构 内存分配和回收原则 分代收集机制 Minor GC 流程 空间分配担保 老年代 大对象直接进入老年代 长期存活的对象将进入老年代 GC的区域 对象存活判定算法 引用计数法 可达性分析算法 finalize() 字符串常量判活 类判活 垃圾回收算…

1、将 ChatGPT 集成到数据科学工作流程中:提示和最佳实践

将 ChatGPT 集成到数据科学工作流程中:提示和最佳实践 希望将 ChatGPT 集成到您的数据科学工作流程中吗?这是一个利用 ChatGPT 进行数据科学的提示的实践。 ChatGPT、其继任者 GPT-4 及其开源替代品非常成功。开发人员和数据科学家都希望提高工作效率,并使用 ChatGPT 来简…

提示工程实战案例

如果你在 2023 年听说过生成式人工智能&#xff08;Generative AI&#xff09;&#xff0c;你一定也听说过提示工程&#xff08;Prompt Engineering&#xff09;。 通过快速的互联网搜索或质疑 GenAI 本身&#xff0c;人们可以轻松找到相关的定义、提示技术&#xff0c;例如 零…

机器学习---半监督学习简单示例(标签传播算法)

1. 使用半监督学习方法 Label Spreading 在一个生成的二维数据集上进行标签传播 import numpy as np import matplotlib.pyplot as plt from sklearn.semi_supervised import label_propagation from sklearn.datasets import make_circles# generate ring with inner box n_s…

Apache Zeppelin 整合 Spark 和 Hudi

一 环境信息 1.1 组件版本 组件版本Spark3.2.3Hudi0.14.0Zeppelin0.11.0-SNAPSHOT 1.2 环境准备 Zeppelin 整合 Spark 参考&#xff1a;Apache Zeppelin 一文打尽Hudi0.14.0编译参考&#xff1a;Hudi0.14.0 最新编译 二 整合 Spark 和 Hudi 2.1 配置 %spark.confSPARK_H…

tee漏洞学习-翻译-2:探索 Qualcomm TrustZone的实现

原文&#xff1a;http://bits-please.blogspot.com/2015/08/exploring-qualcomms-trustzone.html 获取 TrustZone image 从两个不同的位置提取image 从手机设备本身从google factory image 已经root的Nexus 5设备&#xff0c;image存储在eMMC芯片上&#xff0c;并且eMMC芯片…