博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

硬件语言编写规范与技巧

Posted on 2015-11-16 19:53  期待1991  阅读(506)  评论(0编辑  收藏  举报

  重要:总之一句话,与外部信号通信时一定要多一个心眼。

  在下面的讲解中,你会发现4、6小节都用了双重甚至是三重的always语句。那是因为此时接收的数据是异步的,通过多重always语句可以避免冒险竞争,可以同步时钟信号。

  也许你也可以看出在1小节中,我们用了双重的“<=”赋值语句,其目的是检测到wr_state信号的变化,因为是外部信号,即使是存入寄存器也还是不太稳定,所以我们就把数据传给wr1和wr2两个寄存器,这样就稳定了,然后比较前后,就可以知道变化了。

  1、那么当我们想知道寄存器的变化情况该怎么办?

    可以用下列语句:

      wire wr_state = mcu_cs_n || mcu_wr_n;  //外部信号

      always@(posedge clk or negedge rst_n) begin

        wr1<=wr_state;

        wr2<=wr1;

        end

      wire pos_wr=~wr2&&wr1;  //这样就可以间接地检测到寄存器的变化了。

 

  2、我们如何检测变化?

    wire wr_state=mcu_cs_n||mcu_wr_n;  //同时都是低电平的时候

    wire pos_wr=~wr2&&wr1;  //这是很常见的对一个信号稳定后的检测

    与 或 非 都可以进行想要的处理。

  3、关于数据是否稳定的问题?

    且看下面的语句:

      always@(posedge clk or negedge rst_n) begin

        if(wr_state)   //这里里可以检测到变化

          mcu_addr_r<=mcu_addr;  //地址锁存

          mcu_db_r<=mcu_db;  //数据锁存

        end

     分析:这里在检测到wr_state变化时,就马上锁存数据,那么,总线(mcu_addr和mcu_db)上的数据此时是否稳定呢,不可而知。所以比较保险的做法,就是延迟一两个时钟周期,再锁存数据。

  4、那么问题来了,我们如何延迟一两个时钟周期呢?

      always@(posedge clk or negedge rst_n) begin

        if(!rst_n) begin

          reqr1<=1'b1;

          reqr2<=1'b1;

          reqr3<=1'b1;

        end

        else begin

          reqr1<=req;

          reqr2<=req1;

          req3<=req2;

        end

      wire pos_req1=reqr1&~reqr2;  //延迟一个时钟周期

      wire pos_req2=reqr2&~reqr3;  //延迟两个时钟周期

    分析:

      这种写法不仅可以检测到req的数据变化,还可以检测到延迟的数据变化,还可以随意延迟N个时钟周期,在延迟期间,其他数据就得以稳定。

  5、如何看待程序中的硬件连接? 

    可以用下列图示,表示硬件连接。

 

 

  6、不可在两个always语句中对同一个寄存器赋值,如何破解?

    可以申请两个寄存器。

    ***************************************

    reg key_rst;

    always @(posedge clk or negedge rst_n)
      if (!rst_n) key_rst <= 3'b111;
      else key_rst <= {sw3_n,sw2_n,sw1_n};

    ***************************************

    reg low_sw;

    always @(posedge clk or negedge rst_n)
      if (!rst_n) low_sw <= 3'b111;
      else if (cnt == 20'hfffff) 
      low_sw <= {sw3_n,sw2_n,sw1_n};

     ***************************************

    wire[2:0] key = key_rst & (~low_sw);

  7、编写程序要从硬件连接的角度去思考

    如第7要点,对于同一个按键,我们要看到的是下面的硬件连接。

    话说只有这样的硬件连接才看着舒服些,如果是接着在key_rst后面在获取按键的值就有些不妥了,因为key_rst后面已经有了一些组合逻辑的的门电路,本身就比较复杂了。

  8、输出可以是reg寄存器吗?不可以,最好是wire型。

    reg就是用always语句处理,wire就是用assign语句处理,所以通常是assign语句作为输出,如下代码:

    reg d1;
    reg d2;
    reg d3;

    always @ (posedge clk or negedge rst_n)
      if (!rst_n) begin
        d1 <= 1'b0;
        d2 <= 1'b0;
        d3 <= 1'b0;
      end
      else begin //某个按键值变化时,LED将做亮灭翻转
        if ( led_ctrl[0] ) d1 <= ~d1;   //这里不可以直接用led_d3<=led_ctrl,因为led不是reg型,况且,输出不能是寄存器,只能是wire型
        if ( led_ctrl[1] ) d2 <= ~d2;
        if ( led_ctrl[2] ) d3 <= ~d3;
      end

    assign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻转输出
    assign led_d2 = d2 ? 1'b1 : 1'b0;
    assign led_d1 = d3 ? 1'b1 : 1'b0;

   9、同一个寄存器不可以在两个always语句中赋值,但是可以在两个语句中判断该寄存器。

    reg led_dir;
    reg led_on;
    always@(posedge clk or negedge rst_n)
      if(!rst_n) begin
        led_dir<=1'b1;
        led_on<=1;
        end
      else if(key_r_later[0])//按键0---LED开关
        led_on<=~led_on;
      else if(key_r_later[1])
        led_dir<=1'b1;
      else
        led_dir<=1'b0;

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

    always@(posedge clk or negedge rst_n)
      if(!rst_n) begin
        led_r<=4'b0001;
        end
      else if(cnt==24'hffffff&&led_on) begin
        if(led_dir)
          led_r<={led_r[2:0],led_r[3]};
        else
          led_r<={led_r[0],led_r[3:1]};
        end

  10、对于input类型的端口  

    我们没法改变其原值。因为他是由外部输入决定的,因此,我们不可以在对其赋值。我没只能是读取其值。

  11、可以滤波的一种写法

    由于FPGA的周期很短,全频率很高,计算速度很快,这些有可能在检测的时候引进一些干扰波。怎样滤除这些干扰,下面就是一种方法。

    always @ (posedge clk or negedge rst_n)

      begin
        if(!rst_n)

          begin
            rs232_rx0 <= 1'b0;
            rs232_rx1 <= 1'b0;
            rs232_rx2 <= 1'b0;
            rs232_rx3 <= 1'b0;
          end
        else

          begin
            rs232_rx0 <= rs232_rx;
            rs232_rx1 <= rs232_rx0;
            rs232_rx2 <= rs232_rx1;
            rs232_rx3 <= rs232_rx2;
          end
      end
    //下面的下降沿检测可以滤掉<20ns-40ns的毛刺(包括高脉冲和低脉冲毛刺)
    assign neg_rs232_rx = rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0; 

    // 0123

    // 0000------0             0111-------0

    // 1000------0             0011-------1

    // 1100------0   ---->  0001-------0

    // 1110------0             0000-------0

    // 1111------0             1000-------0

  11、如何一次性输出所有数组寄存器的数据?

     if(num == 4'd12)  //到这里是一起都传递过去

      begin 
        num <= 4'd0; 
        rx_data_r <= rx_temp_data; //但是我们不能在这里传递,所以需要一个寄存器过渡。
      end

    assign  rx_data = rx_data_r;

  12、模块中有两种输入,第一种是clk和rst_n;第二种是其他引脚输入,如 bps_clk,我们如何应对?

    对于第一种情况:

      reg num;  //自己创建寄存器

      always@(posedge clk or negedge rst_n)

        if(!rst_n)

          num<=1'b0;

        else

          num<=num+1'b1;  //这里对寄存器赋值

    对于第二种情况:

      input num;

      reg num;  //哎!实事求是吧,这也是其中一种情况。。。。。。

      always@(posedge clk or negedge rst_n)

        if(!rst_n)

          num<=1'b0;

        else

          num<=num+1'b1;  //这里对寄存器赋值

   13、与FPGA外部端口的连接,如何设置???

    FPGA与外部信号的链接,是一对一的,与端口传输的是多少bit无关。

    例如:

      module ps2_top(clk,rst_n,ps2k_clk,ps2k_data,rs232_tx);

      input clk,rst_n;

      input ps2k_clk;

      input ps2k_data; //这里传输的8bit数据,但是我们秉持一对一的原则。

      output rs232_tx;//这是传输1bit数据

    

      reg[7:0] ps2k_data_r;//重新定义一个寄存器,用来储存8比特数据。

 

      endmodule

  14、注意的是在不同的if下,往往会产生不同的结果,一定要想清楚。

    反正是注意就行了,慢慢就熟练了。

 

    else if(num==4'd10)//接收完毕
      begin
        if(ps2_byte_r==8'hf0)//如果是松开按键
        key_f0<=1'b1;//松开标志位为1,说明是无效数据
      else //如果是按下按键
        begin
        if(!key_f0)//如果是0(说明这是按下的键,应该接收)
          begin //说明有键按下
          ps2_int_r<=1'b1;//开始传输
          ps2_dyte_r1<=ps2_byte_r; //锁存当前键值
          end
        else //如果是1(说明这是松开的按键,我们就不用接收)
          begin
          ps2_int_r<=1'b0;
          key_f0<=1'b0;
          end
        end
      end