碎碎思 · 2021年07月27日

Verilog复杂时序逻辑电路设计实践

[例1] 一个简单的状态机设计--序列检测器

序列检测器是时序数字电路设计中经典的教学范例,下面我们将用Verilog HDL语言来描述、仿真、并实现它。

序列检测器的逻辑功能描述:

序列检测指的就是将一个指定的序列从数字码流中识别出来。本例中,我们将设计一个“10010”序列的检测器。设X为数字码流输入,Z为检出标记输出,高电平表示“发现指定序列”,低电平表示“没有发现指定序列”。考虑码流为“ 110010010000100101…” 则有下表:

image.png

在时钟2-6,码流X中出现指定序列“10010”,对应输出Z在第6个时钟变为高电平――“1”,表示“发现指定序列”。同样地,在时钟13-17码流,X中再次出现指定序列“10010”,Z输出“1”。注意,在时钟5-9还有一次检出,但它是与第一次检出的序列重叠的,即前者的前面两位同时也是后者的最后两位。

根据以上逻辑功能描述,我们可以分析得出状态转换图如下:

image.png

其中状态A-E表示5比特序列“10010”按顺序正确地出现在码流中。考虑到序列重叠的可能,转换图中还有状态F、G。另外、电路的初始状态设为IDLE。

进一步,我们得出Verilog HDL代码。

//文件:sequence.v
module seqdet( x, z, clk, rst);
input x,clk, rst;
output z;

reg [2:0] state;//状态寄存器
wire z;

parameter  IDLE= 'd0, A='d1,  B='d2,
     C='d3,  D='d4,
     E='d5,  F='d6,
      G='d7;

assign z=(state==D && x==0) ? 1 :0;
always @(posedge clk or negedge rst)
 if(!rst)
  begin
  state<=IDLE;
  
  end
 else
  casex( state)
   IDLE: if(x==1)
      begin
       state<=A;
      end
   A:    if (x==0)
      begin
       state<=B;
      end
   B: if (x==0)
      begin 
       state<=C;
      end
     else
      begin
       state<=F;
      end
   C: if(x==1)
      begin
       state<=D;
      end
         else
      begin
       state<=G;
      end
   D: if(x==0)
      begin
       state<=E;
      end
         else
      begin
       state<=A;
      end
   E: if(x==0)
      begin
       state<=C;
      end
         else
      begin
       state<=A;
      end
   F:  if(x==1)
      begin
       state<=A;
      end
         else
      begin
       state<=B;
      end
   G:      if(x==1)
      begin
       state<=F;
      end
   default:  state<=IDLE;
   endcase
endmodule

为了验证其正确性,我们接着编写测试用代码。

//文件:sequence.tf

`timescale 1ns/1ns

module t;
reg clk, rst;
reg [23:0] data;
wire z,x;
assign x=data[23];

initial
 begin
  clk<=0;
  rst<=1;
  #2 rst<=0;
  #30 rst<=1;  //复位信号
  data='b1100_1001_0000_1001_0100; //码流数据
 end

always #10 clk=~clk; //时钟信号
always @ (posedge clk) // 移位输出码流
  data={data[22:0],data[23]};
    
seqdet m ( .x(x), .z(z), .clk(clk), .rst(rst)); //调用序列检测器模块

// Enter fixture code here

endmodule // t

其中、X码流的产生,我们采用了移位寄存器的方式,以方便更改测试数据。仿真结果如下图所示:

image.png

从波形中,我们可以看到程序代码正确地完成了所要设计的逻辑功能。另外,sequence.v的编写,采用了可综合的Verilog HDL 风格,它可以通过综合器的综合最终实现到FPGA中。

说明:以上编程、仿真、综合和后仿真在PC WINDOWS NT 4.0操作系统及QuickLogic SPDE环境下通过。

[例2]EEPROM读写器件的设计

下面我们将介绍一个经过实际运行验证并可综合到各种FPGA和ASIC工艺的串行EEPROM读写器件的设计过程。列出了所有有关的Verilog HDL程序。这个器件能把并行数据和地址信号转变为串行EEPROM能识别的串行码并把数据写入相应的地址,或根据并行的地址信号从EEPROM相应的地址读取数据并把相应的串行码转换成并行的数据放到并行地址总线上。当然还需要有相应的读信号或写信号和应答信号配合才能完成以上的操作。

1. 二线制I2C CMOS 串行EEPROM的简单介绍

二线制I2C CMOS 串行EEPROM AT24C02/4/8/16 是一种采用CMOS 工艺制成的串行可用电擦除可编程只读存储器。串行EEPROM 一般具有两种写入方式,一种是字节写入方式,还有另一种页写入方式,允许在一个写周期内同时对一个字节到一页的若干字节进行编程写入,一页的大小取决于芯片内页寄存器的大小,不同公司的同一种型号存储器的内页寄存器可能是不一样的。为了程序的简单起见,在这里只编写串行 EEPROM 的一个字节的写入和读出方式的Verilog HDL 的行为模型代码,串行EEPROM读写器的Verilog HDL模型也只是字节读写方式的可综合模型,对于页写入和读出方式,读者可以参考有关书籍,改写串行EEPROM 的行为模型和串行EEPROM读写器的可综合模型。

2. I2C (Inter Integrated Circuit)总线特征介绍

I2C 双向二线制串行总线协议定义如下:

只有在总线处于“非忙”状态时,数据传输才能被初始化。在数据传输期间,只要时钟线为高电平,数据线都必须保持稳定,否则数据线上的任何变化都被当作“启动”或“停止”信号。图 1 是被定义的总线状态。

image.png

① 总线非忙状态(A 段)

数据线SDA 和 时钟线 SCL 都保持高电平。

② 启动数据传输(B 段)

当时钟线(SCL)为高电平状态时,数据线(SDA)由高电平变为低电平的下降沿被认为是“启动”信号。只有出现“启动”信号后,其它的命令才有效。

③ 停止数据传输(C 段)

当时钟线(SCL)为高电平状态时,数据线(SDA)由低电平变为高电平的上升沿被认为是“停止”信号。随着“停在”信号出现,所有的外部操作都结束。

④ 数据有效(D 段)

在出现“启动”信号以后,在时钟线(SCL)为高电平状态时数据线是稳定的,这时数据线的状态就要传送的数据。数据线(SDA)上的数据的改变必须在时钟线为低电平期间完成,每位数据占用一个时钟脉冲。每个数传输都是由“启动”信号开始,结束于“停止”信号。

⑤ 应答信号

每个正在接收数据的EEPROM 在接到一个字节的数据后,通常需要发出一个应答信号。而每个正在发送数据的EEPROM 在发出一个字节的数据后,通常需要接收一个应答信号。EEPROM 读写控制器必须产生一个与这个应答位相联系的额外的时钟脉冲。在EEPROM 的读操作中,EEPROM 读写控制器对EEPROM 完成的最后一个字节不产生应答位,但是应该给EEPROM 一个结束信号。

3. 二线制I2C CMOS 串行EEPROM读写操作

1) EEPROM 的写操作(字节编程方式)

所谓EEPROM的写操作(字节编程方式)就是通过读写控制器把一个字节数据发送到EEPROM 中指定地址的存储单元。其过程如下:EEPROM 读写控制器发出“启动”信号后,紧跟着送4位I2C总线器件特征编码1010 和3 位EEPROM 芯片地址/页地址XXX 以及写状态的R/W 位(=0),到总线上。这一字节表示在接收到被寻址的EEPROM 产生的一个应答位后,读写控制器将跟着发送1个字节的EEPROM 存储单元地址和要写入的1个字节数据。EEPROM 在接收到存储单元地址后又一次产生应答位以后,读写控制器才发送数据字节,并把数据写入被寻址的存储单元。EEPROM 再一次发出应答信号,读写控制器收到此应答信号后,便产生“停止”信号。字节写入帧格式如图2所示:

image.png

2) 二线制I2C CMOS 串行EEPROM 的读操作

所谓EEPROM的读操作即通过读写控制器读取 EEPROM 中指定地址的存储单元中的一个字节数据。串行EEPROM 的读操作分两步进行:读写器首先发送一个“启动”信号和控制字节(包括页面地址和写控制位)到EEPROM,再通过写操作设置EEPROM 存储单元地址(注意:虽然这是读操作,但需要先写入地址指针的值),在此期间EEPROM 会产生必要的应答位。接着读写器重新发送另一个“启动”信号和控制字节(包括页面地址和读控制位R/W = 1),EEPROM收到后发出应答信号,然后,要寻址存储单元的数据就从SDA 线上输出。读操作有三种:读当前地址存储单元的数据、读指定地址存储单元的数据、读连续存储单元的数据。在这里只介绍读指定地址存储单元数据的操作。读指定地址存储单元数据的帧格式如图3:

image.png

4. EEPROM的Verilog HDL 程序

要设计一个串行EEPROM读写器件,不仅要编写EEPROM读写器件的可综合Verilog HDl的代码,而且要编写相应的测试代码以及EERPOM的行为模型。EEPROM的读写电路及其测试电路如图4。

image.png

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,串行数据(/地址)输入/输出端。

`timescale 1ns/1ns
`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'b10101111,w7= 8'b10101110,         //main7
          r6= 8'b10101101,w6= 8'b10101100,         //main6
           r5= 8'b10101011,w5= 8'b10101010,         //main5
          r4= 8'b10101001,w4= 8'b10101000,         //main4
           r3= 8'b10100111,w3= 8'b10100110,         //main3
           r2= 8'b10100101,w2= 8'b10100100,         //main2
           r1= 8'b10100011,w1= 8'b10100010,         //main1
          r0= 8'b10100001,w0= 8'b10100000;         //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;
      if(State == 2'b11)
         disable write_to_eeprm;
    end
//------------ 主状态机 --------------------------
always @(posedge sda)            
  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
//------------- 操作停止------------------------------
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; 
    $display("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 = {ctrl_byte[3:1],addr_byte};  
      sda_buf = memory[address];
      shift_out;
      State= 2'b00;
      end
  end    
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;
  begin
    out_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
endtask     
  endmodule

 2 ) EEPROM读写器的可综合的Verilog HDL模型

下面的程序是一个串行EEPROM读写器的可综合的Verilog HDL模型,它接收来自信号源模型产生的读信号、写信号、并行地址信号、并行数据信号,并把它们转换为相应的串行信号发送到串行EEPROM(AT24C02/4/8/16) 的行为模型中去;它还发送应答信号 (ACK)到信号源模型,以便让信号源来调节发送或接收数据的速度以配合EEPROM模型的接收(写)和发送(读)数据。因为它是我们的设计对象,所以它不但要仿真正确无误,还需要可综合。

这个程序基本上由两部分组成:开关组合电路和控制时序电路,见图5。开关电路在控制时序电路的控制下按照设计的要求有节奏的打开或闭合,这样SDA可以按I2C 数据总线的格式输出或输入,SDA和SCL一起完成EEPROM的读写操作。

image.png

EEPROM读写器的结构

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

image.png

读写操作状态转移

`timescale 1ns/1ns 
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'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;                
//-------------产生串行时钟,为输入时钟的二分频-------------------
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;
    ACK        <= 0;
    RF         <= 0;
    WF         <= 0;
    FF         <= 0;
    main_state <= Idle; 
   end
 else
  begin   
    casex(main_state)
           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
                   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;    
                 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
 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};
                  link_head        <= NO;
                  link_write       <= YES;
                  FF               <= 0;   
                  sh8out_state     <= sh8out_bit6;
                  main_state       <= Ctrl_write;
                end
 Ctrl_write: 
               if(FF ==0)
                  shift8_out;
            else
              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;
              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     
 Data_write:  
                if(FF == 0)
                 shift8_out;
            else  
                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};
                  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; 
             else  
                begin 
                  link_sda         <= NO;
                  link_write       <= NO;
                  FF               <= 0;
                  sh8in_state      <= sh8in_begin;
                  main_state       <= Data_read;
                  end
 Data_read: 
              if(FF == 0) 
                 shift8in;
            else 
               begin 
                  link_stop        <= YES;
                  link_sda         <= YES;
                  stop_state       <= stop_bit;
                  FF               <= 0;
                  main_state       <= Stop;
               end  
 Stop: 
              if(FF == 0)
               shift_stop;
            else 
               begin
                   ACK        <= 1; 
                   FF         <= 0;
                 main_state <= Ackn;
                end    
 Ackn:   
              begin
               ACK         <= 0;
               WF          <= 0;
               RF          <= 0; 
               main_state  <= Idle;
             end   
 default:    main_state <= Idle;
 endcase
end
//------------------------串行数据转换为并行数据任务----------------------------------
task shift8in; 
 begin 
  casex(sh8in_state)
   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;
                   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;
begin
 casex(sh8out_state)
       sh8out_bit7:  
                if(!SCL)
                  begin 
                        link_sda     <= YES;
                        link_write   <= YES;
                        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
//---------------------------  输出启动信号任务  ---------------------------------
task shift_head;
begin
 casex(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;
                         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
 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

程序最终通过Synplify器的综合,并在Actel 3200DX 系列的FPGA上实现布局布线,通过布线后仿真 。

3 ) EEPROM的信号源模块和顶层模块

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

信号源模型:

`timescale 1ns/1ns
`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:2048]; 
integer i,j;
integer OUTFILE;
assign DATA = (W_R) ?  8'bz : data_to_eeprom ;

//------------------------------------时钟信号输入------------------------------
always #(`timeslice/2)
   CLK = ~CLK; 
//----------------------------------- 读写信号输入------------------------------

initial 
   begin
     RESET = 1;
     i   = 0; 
     j   =0;
     W_R = 0;
     CLK = 0;       
     RD  = 0;
     WR  = 0;
     #1000 ;
     RESET = 0; 
repeat(15)  //连续写15次数据
      begin 
        #(5*`timeslice);
     WR = 1; 
     #(`timeslice);
     WR = 0;
    @ (posedge ACK);
     end
    #(10*`timeslice);
    W_R = 1;   //开始读操作
    repeat(15)  //连续读15次数据 
      begin
      #(5*`timeslice);
      RD = 1;
       #(`timeslice);
      RD = 0;
       @ (posedge ACK);
      end
   end                 
//-----------------------------------------写操作-----------------------------
initial 
  begin
    $display("writing-----writing-----writing-----writing");
    # (2*`timeslice);
    for(i=0;i<=15;i=i+1)
      begin
       ADDR = addr_mem[i];                 
       data_to_eeprom = data_mem[i];    
       $fdisplay(OUTFILE,"@%0h  %0h",ADDR, data_to_eeprom);
       @(posedge ACK) ;            
     end
 end   
//----------------------------------------读操作----------------------------
initial
  @(posedge W_R)
   begin
    ADDR = addr_mem[0];
    $fclose(OUTFILE);
    $readmemh("./eeprom.dat",ROM); 
    $display("Begin READING-----READING-----READING-----READING");
     for(j = 0; j <= 15; j = j+1)         
     begin
        ADDR = 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);     
     end
  end   

initial
  begin
   OUTFILE = $fopen("./eeprom.dat");
   $readmemh("./addr.dat",addr_mem); //地址数据存入地址存储器
   $readmemh("./data.dat",data_mem); //写入EEPROM的数据存入数据存储器
end

endmodule

  顶层模块:

 `include “./Signal.v”
 `include “./EEPROM.v”
 `include “./EEPROM_WR.v” 
 `timescale 1ns/1ns
  module Top;
  wire RESET;
  wire CLK;
  wire RD,WR;
  wire ACK;
  wire[10:0] ADDR;
  wire[7:0]  DATA; 
  wire SCL;
  wire SDA;
  Signal         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

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

image.png

EEPROM 的写时序

image.png

EEPROM 的读时序

说明:

以上编程、仿真、综合在PC WINDOWS NT 4.0操作系统、Synplify 、Actel Designer 、Altera Maxplus9.3及ModelSim Verilog环境下通过前后仿真,也在Unix Cadence Verilog-XL上通过前、后仿真(可综合到各种FPGA和ASIC工艺)。

原出处:OpenFPGA
作者:碎碎思

相关文章推荐

更多FPGA技术干货请关注IC设计技术专栏。
推荐阅读
关注数
20200
内容数
1307
主要交流IC以及SoC设计流程相关的技术和知识
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息