zoukankan      html  css  js  c++  java
  • FPGA:PLL&RAM的原理及代码

    IP核是面向可编程逻辑门阵列(FPGA)芯片优化的,实现电子设计中常用功能的封装模块;包括固化在芯片内部的硬IP核,以及可编程调用的软IP核;

    IP核通过 菜单栏Tools >>MegaWizard Plug-In Manager 来创建或修改;也可以这样查看各种IP核,以及芯片支持的IP核种类;

    本文主要参考野火的教程;

    1 PLL核

      1.1 PLL的简单原理,与使用无关,可跳过,只做原理了解;

      PLL phase lock loop锁相环:通过对输入时钟分频,倍频来输出需要的时钟信号的IP核;

      

        1.1.1 倍频原理

          首先将输入信号ref_clk输入到鉴频鉴相器中;该输入信号ref_clk的负反馈信号,经过分频器后也输入到鉴频鉴相器中;

          鉴频鉴相器通过比较两个信号的频率,输出对应的电压差,倍频时电压差大于1;

          电压差信号通过环路滤波器LF,过滤掉带宽内外的噪声,维持信号的稳定性;

          电压差信号继续通过压控振荡器VCO,震荡输出当前电压差对应的频率信号;

          频率一部分作为倍频信号输出,一部分通过分频器DIV之后,再输入回鉴频鉴相器作为比较信号;

        1.1.2 分频原理

          首先将输入信号ref_clk通过分频器,分频后输入鉴频鉴相器中;同时该信号滤波震荡后的频率信号也输入到鉴频鉴相器中;

          鉴频鉴相器通过比较两个信号的频率,输出对应的电压差,分频时电压差小于1;

          电压差信号通过环路滤波器LF,过滤掉带宽内外的噪声,维持信号的稳定性;

          电压差信号继续通过压控振荡器VCO,震荡输出当前电压差对应的频率信号;

          频率一部分作为分频信号输出,一部分输入回鉴频鉴相器作为比较信号;

      1.2 PLL核创建

        想要创建一个pll核,首先使用tools >> MegaWizard Plug-In Manager >>按需求创建ip核;

        然后编写一个pll核的实例化模块pll.v,编写一个pll的测试模块pll_tb.v;

        然后通过Assignments >> settings >> EDA Tool Settings >> simulation  >>配置一下testbench,使用RTL simulation来查看分频信号是否正确;

    2 RAM核

      RAM(random access memory)随机存取存储器,可以从任何指定地址写入或读出数据,掉电丢失;

      Altera一共推出了单端口RAM和双端口RAM两种IP核;

      1)读操作:全部存储器类型的读操作均在上升沿触发;

      2)写操作:上升沿触发的存储器类型:M9K, M10K, M20K, M144K, M-RAM;

           下降沿触发的存储器类型:M4K, M512;

      2.1 单端口RAM

        2.1.1 对于单端口 RAM,只有一组地址线,读写操作不能同时进行;

          以下代码首先在上升沿配置了读写使能信号,和读写计数值;然后下降沿通过对使能信号和计数值的判断,来配置地址和数据;

          读写使能和计数的最后一个字节,刚好在对ram核处理的上升沿变化,但是因为是非阻塞赋值,取值为上升沿之前的值,所以可以取到正确值;

    /**ipcore.v 单端口ram的实例代码*********/
    module ipcore(
        input sys_clk,
        input sys_rst_n,
        
        output ram1_wren,
        output wire[9:0] wire_input_data,    //调试查看信号
        output ram1_rden,
        output wire[9:0] wire_output_data,   //调试查看信号
        output reg[4:0] ram1_address
    );
        
    /**ram 实例ram1_inst***************************/    
    reg[9:0] ram1_input_data;                //ram的输入信号
    wire[9:0] ram1_output_data;              //ram的输出信号
    reg[7:0] cnt;
    ram ram1_inst
    (
        .address     (ram1_address),
        .clock        (sys_clk),
        .data            (ram1_input_data),
        .rden            (ram1_rden),
        .wren            (ram1_wren),
        .q                (ram1_output_data)
    );
    /**步骤1:配置写入计数值cnt,读写xx_en信号***********/
    assign ram1_wren = (cnt< 8'd8)? 1'b1:1'b0;
    assign ram1_rden = (cnt>=8'd8)? 1'b1:1'b0;
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n) begin
            cnt <= 8'd0;
        end
        else if(cnt != 8'd15)
            cnt <= cnt + 1'b1;
        else
            cnt <= 8'd0;
    end
    /**步骤2:在时钟的下降沿配置地址和收发数据***********/
    assign wire_input_data = ram1_input_data;     //调试用 
    assign wire_output_data = ram1_output_data;   //调试用
    always@(negedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n) begin                
            ram1_address <= 5'd0;
            ram1_input_data <= 10'd0;
        end
        else if(ram1_wren && !cnt) begin    //写使能
            ram1_address <= 5'd0;
            ram1_input_data <= 10'd0;
        end
        else if(ram1_wren && cnt ) begin    //写使能
            ram1_address <= ram1_address + 1'b1;
            ram1_input_data <= ram1_input_data + 1'b1;
        end
        else if(ram1_rden && cnt==8'd8) begin//读使能
            ram1_address <= 5'd0;
        end
        else if(ram1_rden && cnt!=8'd8) begin//读使能
        ram1_address <= ram1_address + 1'b1;
        end
    end
    /***************************************************/
    /**为什么使用下降沿来改修数据呢?因为ram数据都在上升沿采样,所以上升沿的数据应该是稳定的;
    ***看网上说尽量不要使用下降沿,因为下降沿上升沿所需时间不同,在高速系统中不稳定;
    ***************************************************/
        
    endmodule

        2.1.2 testbench

    /**ipcore_tb.v *****/
    `timescale 1ns/1ns
    module ipcore_tb();
    
    reg sys_clk;
    reg sys_rst_n; 
    initial begin
        sys_rst_n = 1'b0;
        sys_clk = 1'b1;
        #40 sys_rst_n = 1'b1;
    end
    
    always #10 sys_clk <= ~sys_clk;
    /*50MHz,20ns一个周期;*/
    
    wire ram1_rden;
    wire ram1_wren;
    wire[4:0] ram1_address;
    wire[9:0] wire_output_data;
    wire[9:0] wire_input_data;
    
    ipcore i1 (
        .sys_clk(sys_clk),
        .sys_rst_n(sys_rst_n),
        
        .ram1_wren(ram1_wren),
        .wire_input_data(wire_input_data),
        .ram1_rden(ram1_rden),
        .wire_output_data(wire_output_data),
        .ram1_address(ram1_address)
    );
    
    endmodule

        2.1.3 仿真截图

        

      2.2 简单双端口 RAM
        读操作和写操作有专用地址端口(一个读端口和一个写端口),即写端口只能写不能读,而读端口只能读不能写;
      2.3 真正双端口 RAM
        有两个地址端口用于读写操作(两个读/写端口),即两个地址端口都可以进行读写操作;
  • 相关阅读:
    OC-KVO简介
    注册审核
    应用权限
    关于函数执行的一点知识
    设置权限
    文件操作实例:文件管理器(网页版)
    文件操作
    正则表达式
    全局变量和递归
    案例:简单留言板
  • 原文地址:https://www.cnblogs.com/caesura-k/p/13403922.html
Copyright © 2011-2022 走看看