简化串行EEPROM系统设计

参考自夏宇闻老师《verilog系统设计原理》
EEPROM系统模型
系统设计基础原理:
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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值