• 数据流转换


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

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

    设计概述:

    如图:该设计分为三个模块,模块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
     

     

  • 相关阅读:
    第九节 堆栈的概念和python代码实现
    第八节 单向循环链表简单介绍和python代码实现
    第七节 双向链表简单介绍和python代码实现
    第六节 单链表简单介绍和python代码实现
    第五节 顺序表的原理与python中的list类型
    第四节 抽象数据类型
    第三节 Python列表类型性能测试以及内置字典操作的时间复杂度分析
    第二节 大O表示法和时间复杂度
    MySQL优化指南
    Java类加载机制详解
  • 原文地址:https://www.cnblogs.com/Oursbuzouxunchanglu/p/Ours_shujliuzhuanhuan.html
Copyright © 2020-2023  润新知