数据流转换

注释:这是一个将并行数据转换为串行数据的一个代码,经过多次修改、仿真,虽然功能仿真正确,但是在进行时序仿真的时候还是不对,可是自己在分模块仿真的时候没有错误,希望哪位高手哈哪里编写不好指点哈。

并行数据流转换成串行数据流

设计概述:

如图:该设计分为三个模块,模块M1的作用是把四位的并行数据转换为符合以下协议的串行数据流,数据流用scl和sda两条传输线,sclk作为输入的时钟信号,data[3:0]为输入 数据,ack为M1请求M0发新的数据信号。模块M2把串行数据流内的信息接收到,并转换为相应16条信号线的高电平,相当于一个4—16译码电路,即如果收到的串行数据是0010,那么输出的16条信号线中的outhigh[2]为高电平,其余为低电平。M0是发送数据信号模块,它接收到M1的数据请求,就像M1发送数据,测试M1和M2的功能是否正确。

 

 通信协议scl为不断输出的时钟信号,由M1模块产生,如果scl为高电平时,sda由高变低的时候表示串行数据流开始;如果scl为高电平时,sda由低变高,串行数据结束。sda信号的串行数据位必须在scl为低电平的时候变化,也就是说我们的M1模块要有效的发送数据必须在scl为低电平才能发送。

设计分析

其实这个设计的难点是如何来写这个通信协议,例如,M1什么时候开始向M0请求数据,什么时候开始将并行数据转换为串行数据,怎么转换,什么时候结束,以及M2模块什么时候开始接收数据?只有正确理解了这个传输协议,才能让我们的M1和M2模块正常工作。那么我们就来具体分析:

我的分析思路是从各个模块的功能在到协议,首先是M1模块,它扮演个中间角色,它本身的功能是将接收到的并行数据转换成串行数据,与上一个模块有关的信号有三个,最重要的是ack,即请求数据信号,它决定是否从上一个模块接收数据;与下一个模块有关的有2个信号,scl和sda,scl是时钟信号,由它产生,提供给下一个模块,sda是串行数据总线,它的作用是传输数据,而这个数据的传输必须符合我们的通信协议,协议分析,三个点:开始位、数据传输位、结束位,由协议知,开始为满足的条件是scl为高,sda由高变低,数据传输的时间实在scl为低的时候,结束位是在scl为高,sda由低变高的时候,所以我们在写数据传输的时候一定要按照这个协议来写。但是,我们在何时请求数据呢?得到数据后什么时候开始转换?这些都必须考虑进去。

看代码具体分析:

  1. always @ (posedge ack)   //请求新数据存入到并行总线上要转换的数据

         databuf <= data;      //存入要转换的数据

第一段代码是接收上一个模块的数据,它触发的条件是在ack的上升沿的时候,而我们知道,ack对于M1是输出信号,而不是输入时钟,所以这个信号的触发不是每时每刻的。那么我们怎么来提供这个触发条件呢?这个触发是在下面状态机模块产生的。        

//------------主状态机:产生控制信号,根据databuf中保存的数据,按照协议产生sda串行信号--------------      

always @ (negedge sclk or negedge rst_n)

         if(!rst_n)//开始一进来我们就将ack拉低,状态进入ready状态,目的是通过这个配合获得ack的一个上升沿,请求并存储上一个模块的数据。

         begin

                     link_sda <= 0;

                     state <= ready;

                     sdabuf <= 1;

                     ack <= 0;

         end

         else begin

                     case(state)

                     ready : if(ack)

                                                        begin

                                                        link_sda <= 1;

                                                        state <= start;

                                                        end

                                        else

                                                        begin

                                                        link_sda <= 0;

                                                        state <= ready;

                                                        ack <= 1;

                                                        end

                            start : if(scl && ack)         //产生起始信号

                                                        begin

                                                        sdabuf <= 0;

                                                        state <= bit1;

                                                        end

                                                        else state <= start;

                            bit1 : if(!scl)               //开始传输四位的并行数据

                                                        begin

                                                        sdabuf <= databuf[3];

                                                        state <= bit2;

                                                        ack <= 0; //ack拉低,取消请求数据,与上一个模块断开

                                                        end

这段代码的巧妙之处在于很好的利用了ack,通过状态机的方式获得了上一个模块的数据,后面的代码都按照协议写,没什么特别的。

 

然后是M2模块,对于这个模块需要注意的就是什么时候开始接收数据。其实M1已经分析得很清楚了,我们按照协议写就可以了,首先是收到起始信号,即在sda的下降沿的时候,然后开始接收数据,接收完之后开始停止并把接收到的四位数据保存,将其译码输出。相关部分代码:

//-------sda由高电平变成低电平时,表示有数据输入了----------------------------------------------

  1. always @ (negedge sda)

         if(scl)

                   StartFlag <= 1; //由传输协议知,在sda拉低后,scl为高电平数据起始标志位置1

         else if(EndFlag)

                   StartFlag <= 0;

接收到起始信号,获得信号标志位,告诉后面相关模块开始工作

//-------sda由低电平变成高电平时,表示数据接收完毕----------------------------------------------

  1. always @ (posedge sda)

         if(scl)

         begin

                   EndFlag <= 1;   //由传输协议知,在sda拉低后,scl为高电平数据结束标志位置1

                   pdatabuf <= pdata; //把接收到的四位数据存入寄存器中

         end

         else

                   EndFlag <= 0;

数据接收完毕,保存。

最后是模块M0,它就是收到M1的有效命令后就发送数据给M1。相关代码:

always @ (negedge rst_n or posedge ack)

  if(!rst_n)

    data_r <= 0;

  else if(ack)

    data_r <= data_r+1;

  else if(data==15)

    data_r <= 4'b0000;

assign data = data_r;//每次给下一个模块一次数据

 

assign data = data_r;收到命令,发送一个数据。               

always @ (posedge clk or negedge rst_n)

         if(!rst_n)

                   sclk <= 0;

         else

                   sclk <= ~sclk;

产生下一个模块的时钟。

在仿真的时候,我先是一个模块一个模块进行仿真的,没有问题,可是在我把所有模块连起来仿真的时候就出现问题了。问题是这样的,M0高三位的数据没有发送出去,只发送了最后一位,但是数据是并行输出的,不可能只有最后一位发送出去了啊,如果我发送1111,译码结果为最低位为1,如果发送1110,译码结果为初始状态,一位一位的检查,只有在最后一位为1的时候,译码才发生变化,并且是最低位为1.但是我对后两个模块进行仿真的时候,先仿真M1,输出没有问题,让后在仿真M2的时候scl和sda的时序是按照M1的仿真结果来弄的,结果正确,意思就是将这两个模块连在一起是正确的,那么问题就出在M1向M0请求数据这里了,要是没有请求到数据,那么输出始终不变才对,但是data=4‘b0001的时候译码发生变化,而却只要最低位为1,译码就是最低位为1,即1111,,1101等的输出结果是一样的,这让我百思不得其解啊!!后来经一再检查才发现顶层模块中的data位没定义对,本来该是4位自己弄成一位去了。但是我仿真的时候QUARTUS自带的和Modelsim仿真不一样,后面的严格些!经过一再仿真修改,虽然功能仿真是正确的,但是时序仿真不正确,估计是经过多次分频照成的影响吧。还是不明白为什么。

小结

这个练习主要是让自己熟悉如何来写通信协议,我觉得代码中最好的地方就是ack信号的写法了,它在每次请求数据都只有一次高脉冲,保证每次转换都只获得一次数据,其次就是M1写协议、M2何时开始接收数据这些。由于自己出现了一个小错误,就是data位宽,在顶层模块出错了,导致仿真的时候结果不对,浪费了自己很多的时间,对于输出变量一定要赋初值。

所有代码:

1. 顶层模块

module BingToCuan(
             clk,
             rst_n,
             outhigh
             );
  input clk;
  input rst_n;
  output [15:0] outhigh;


  wire ack;
  wire sclk;
  wire [3:0]data;
  wire sda;
  wire scl;

 sigdata sigdata_M0(
             .clk(clk),
             .rst_n(rst_n),
             .ack(ack),
             .sclk(sclk),
             .data(data)
             );
     
ptosda ptosda_M1(
             .sclk(sclk),
             .rst_n(rst_n),
             .ack(ack),
             .scl(scl),
             .sda(sda),
             .data(data)     
     );
out16hi out16hi_M2(
             .scl(scl),
             .sda(sda),
             .outhigh(outhigh)    
             );     
endmodule

模块M0:

module sigdata(
          clk,
          rst_n,
          ack,
          sclk,
          data
          );
  input clk;
  input rst_n;
  input ack;

  output sclk;
  output [3:0] data;
  reg [3:0] data_r;
  reg sclk;


  always @ (negedge rst_n or posedge ack)
      if(!rst_n)
          data_r <= 0;
      else if(ack)
          data_r <= data_r+1;
      else if(data==15)
          data_r <= 4'b0000;
 
  assign data = data_r;
 
  always @ (posedge clk or negedge rst_n)
    if(!rst_n)
        sclk <= 0;
    else
        sclk <= ~sclk;
 
endmodule

//---------------------------------------------------------------------------------------------

模块M1:

//**************************************************

/*****模块功能:将接收到的并行数据转换成串行数据输出*****/

//**************************************************

module ptosda(
          sclk,
          rst_n,
          data,
          scl,
          ack,
          sda
          );
   input sclk;   //输入时钟信号
   input rst_n;  //复位信号
   input [3:0] data; //四位并行数据输入
   output scl;   //输出时钟信号,为下一个模块提供时钟
   output sda;   //输出串行数据位
   output ack;

   reg scl;
   reg link_sda;  //控制数据输出到总线上
   reg ack;   //向上一个模块寻求新的数据信号寄存器
   reg sdabuf;   //串行数据输出寄存器
   reg [3:0] databuf; //并行数据输入寄存器
   reg [7:0] state;

  assign sda = link_sda ? sdabuf : 1'b0;//通过link_sda很好的控制了数据在总线上的传输

parameter ready = 8'b0000_0000,
        start = 8'b0000_0001,
        bit1 = 8'b0000_0010,
        bit2 = 8'b0000_0100,
        bit3 = 8'b0000_1000,
        bit4 = 8'b0001_0000,
        bit5 = 8'b0010_0000,
        stop = 8'b0100_0000,
        IDLE = 8'b1000_0000;

//-------------为下一个模块产生时钟信号------------------------------------------------------------

always @ (posedge sclk or negedge rst_n)
   if(!rst_n)
      scl <= 1;
   else 
      scl <= ~scl;

//-------------将并行数据转换成串行数据-------------------------------------------------------------

  always @ (posedge ack) //请求新数据存入到并行总线上要转换的数据
     databuf <= data; //存入要转换的数据
 
//------------主状态机:产生控制信号,根据databuf中保存的数据,按照协议产生sda串行信号-------------- 
always @ (negedge sclk or negedge rst_n)
   if(!rst_n)
   begin
      link_sda <= 0;
      state <= ready;
      sdabuf <= 1;
      ack <= 0;
   end
   else begin
        case(state)
          ready : if(ack)
                begin
                  link_sda <= 1;
                  state <= start;
                end
                 else 
                 begin
                  link_sda <= 0;
                  state <= ready;
                  ack <= 1;
                end
         start : if(scl && ack) //产生起始信号,由通信协议在scl为高的时候sda为0表示开始发送数据
                begin
                  sdabuf <= 0;
                  state <= bit1;
                end
              else state <= start;
         bit1 : if(!scl)  //由通信协议,数据只能在SCL为低的时候变化
               begin
                sdabuf <= databuf[3];
                state <= bit2;
                ack <= 0;
              end
             else state <= bit1;
         bit2 : if(!scl)
              begin
                sdabuf <= databuf[2];
                state <= bit3;
              end
             else state <= bit2;
         bit3 : if(!scl)
              begin
                sdabuf <= databuf[1];
                state <= bit4;
              end
             else state <= bit3;
         bit4 : if(!scl)
               begin
                sdabuf <= databuf[0];
                state <= bit5;
               end
             else state <= bit4;
         bit5 : if(!scl)   //为产生结束信号做准备,先把sda拉低
              begin
                sdabuf <= 0;
                state <= stop;
              end
             else state <= bit5;
         stop : if(scl)   //在scl为高的时候,把sda由低变高,产生结束信号
              begin
                sdabuf <= 1;
                state <= IDLE;
              end
             else state <= stop;
         IDLE : begin
                 link_sda <= 0; //把sdabuf与sda串行总线断开,结束数据转换完成。
                 state <= ready;
               end
         default : begin
                 link_sda <= 0;
                 sdabuf <= 1;
                 state <= ready;
                end
         endcase
      end
endmodule

//-------------------------------------------------------------------------------------------------------------- 

模块M2:

//*******************************************************************************

/****模块功能:把串行数据流内的信号接收到,并转换为相应的16条信号的高电平,4—16译码****/

//*******************************************************************************

module out16hi(
            scl,
            sda,
            outhigh
            );

  input scl;
  input sda;     //串行数据输入

  output [15:0] outhigh;  //根据输入的串行数据设置该电平位

  reg [5:0] mstate;   //状态寄存器
  reg [3:0] pdata,pdatabuf; //记录串行数据位时,用寄存器和最终数据寄存器
  reg [15:0] outhigh;   //输出寄存器
  reg StartFlag,EndFlag;  //数据开始和结束标志位

//-------sda由高电平变成低电平时,表示有数据输入了----------------------------------------------
always @ (negedge sda)
   if(scl)
        StartFlag <= 1;  //由传输协议知,在sda拉低后,scl为高电平数据起始标志位置1
     else if(EndFlag)
        StartFlag <= 0;

//-------sda由低电平变成高电平时,表示数据接收完毕----------------------------------------------
always @ (posedge sda)
    if(scl)
     begin
        EndFlag <= 1;  //由传输协议知,在sda拉低后,scl为高电平数据结束标志位置1
        pdatabuf <= pdata; //把接收到的四位数据存入寄存器中
     end
    else 
      EndFlag <= 0;
 
parameter ready = 6'b00_0000,
       sbit0 = 6'b00_0010,
       sbit1 = 6'b00_0100,
       sbit2 = 6'b00_1000,
       sbit3 = 6'b01_0000,
       sbit4 = 6'b10_0000;
   
//---------------把接收到的数据译码输出相应的高电平----------------------------------------------   
always @ (pdatabuf)
 begin
  case(pdatabuf)
   4'b0001 : outhigh = 16'b0000_0000_0000_0001;
   4'b0010 : outhigh = 16'b0000_0000_0000_0010;
   4'b0011 : outhigh = 16'b0000_0000_0000_0100;
   4'b0100 : outhigh = 16'b0000_0000_0000_1000;
   4'b0101 : outhigh = 16'b0000_0000_0001_0000;
   4'b0110 : outhigh = 16'b0000_0000_0010_0000;
   4'b0111 : outhigh = 16'b0000_0000_0100_0000;
   4'b1000 : outhigh = 16'b0000_0000_1000_0000;
   4'b1001 : outhigh = 16'b0000_0001_0000_0000;
   4'b1010 : outhigh = 16'b0000_0010_0000_0000;
   4'b1011 : outhigh = 16'b0000_0100_0000_0000;
   4'b1100 : outhigh = 16'b0000_1000_0000_0000;
   4'b1101 : outhigh = 16'b0001_0000_0000_0000;
   4'b1110 : outhigh = 16'b0010_0000_0000_0000;
   4'b1111 : outhigh = 16'b0100_0000_0000_0000;
   4'b0000 : outhigh = 16'b1000_0000_0000_0000;
  endcase
 end

//---------------------在检测到开始标志位的时候,每次scl正跳变沿接收数据,共四位----------------------------------- 
always @ (posedge scl)
    if(StartFlag)
       case(mstate)   
          sbit0 : begin
                 mstate <= sbit1;
                 pdata[3] <= sda;
                 //$display("I am in sdabit0");
               end
          sbit1 : begin
                 mstate <= sbit2;
                 pdata[2] <= sda;
                 //$display("I am in sdabit1");
               end
          sbit2 : begin
                 mstate <= sbit3;
                 pdata[1] <= sda;
                 //$display("I am in sdabit2");
               end           
          sbit3 : begin
                 mstate <= sbit4;
                 pdata[0] <= sda;
                 //$display("I am in sdabit3");
               end 
          sbit4 : begin
                 mstate <= sbit0;
                 //$display("I am in sdabit4");
               end 
          default : mstate <= sbit0;
        endcase
     else mstate <= sbit0;
   
 endmodule
 

 

 

posted @ 2012-02-10 13:12  慢慢来,别慌  阅读(1960)  评论(0编辑  收藏  举报