• FPGA经典:Verilog传奇与基于FPGA的数字图像处理原理及应用


    一 简述

         最近恶补基础知识,借了<<Verilog传奇>>,《基于FPGA的嵌入式图像处理系统设计》和<<基千FPGA的数字图像处理原理及应用>>这三本书。

    <<Verilog传奇>>是关于Verilog基础知识的,总共九章。由于书籍内容太多没时间看,故一般都是了解整本书的大致内容,遇到问题时能回忆起在那看过,再返回来仔细研究。后面两本书是图像处理的FPGA应用,有很多基本的图像处理操作,如常见的直方图技术,各种滤波,图像分割等。重点是图像处理算法的FPGA映射,还有图像仿真验证平台。

    (一)<<Verilog传奇>>总结:

    第一章用来介绍Verilog语言的基本知识, 包括发展历史、设计流芯片结构和可综合性等。 这里的重点是帮大家建立Yerilog语言与其他学科之间的联系。帮助大家掌握设计数字逻辑系统需要考量的有关内容。

    第二章,除了介绍IEEE有关Verilog语言的标准体系及非RTL级的设计之外,还重 点说明了常量、 变量及结构化模块的内容, 是学习Verilog语言的基础。

    第三、 四章, 分别介绍了用assign和always定义的组合逻辑电路的描述方法。 其中包含各种运算符操作的写法, 以及条件、 多选的描述。 最后, 以多路选择器为例, 说明 了如何分析一个组合逻辑电路系统。

    第五章, 介绍了时序逻辑电路系统的Verilog语言描述方法, 包括D触发器及D触 发器链的写法。 本章还介绍了如何拆分组合电路以适应系统操作时间要求的概念。 在此基础上, 介绍了三种系统速度与面积平衡的方法: 并行化设计流水线设计时分复用 设计

    第六章, 结合工程实践, 介绍了经常遇到的若干问题, 其中包括复位系统设计、 可变移位操作、 有限状态机设计 多时钟系统处理及循环操作的处理。

    第七章, 介绍了与[P核设计有关的灵活编程问题。 其中涉及任务与函数的写法、 利 用宏定义方法改变系统参数、 利用参数方法改变系统参数及生成块方法改变系统结构。最后综合运用这几种方法, 给出了一个简单的JP核一数字分频系统 设计的例子。

    第八章, 说明了Verilog语言中不可综合的部分。 其一为仿真所需的数据类型、复杂 运算和并行块设计; 其二包含预编译命令; 其三为系统任务与函数。 本章还介绍了测试向量的概念及其编写方法, 以及Yerilog语言与其他语言接口的问题。

    第九章, 以 “ 直接数字式频率合成器” 系统为例, 综合前面各章介绍的知识, 采用 了R OM查找表、 折线法和CORDIC算法分别进行了实现。本章不仅希望读者学会如何 综合使用Yerilog语言, 还进一步介绍了部分算法与算法定点化的知识。

    (二)<<Verilog传奇>>阅读建议

    首先, 粗读/跳读第一章到第二章的第二讲, 了解基本概念。 本书的基本假设是读者都掌握了《数字电子技术》这一门课程。其次详细阅读第二章第三讲到第五章,这是基础内容。第六章,七章为进阶内容。第八章,是测试和验证。第九章是一个复杂的例子, 重点是阅读作者的思想、 设计过程及代码风格。

    由于,我曾经学过Verilog语言,故直接从第六章,第七章开始。第六章(按键与复位,可变移位宽度的移位操作,有限状态机及其代码,多时钟系统,循环控制),第七章(函数与任务,宏定义与宏判断,参数,生成块,数字分频器核的设计)。今天下午看了函数与任务,后面详细介绍。

    (三)  <<基千FPGA的数字图像处理原理及应用>>总结

    重点讲解图像处理算法移植到FPGA中的基本思路和方法,突出工程应用。每一章均附有C/C++实现代码,同时用循序渐进、自顶向下的方式设计FPGA算法模块, 针对每一个模块设计了详细的实现框图,确保读者能理解算法设计的原理。

    此外,每个算法都配有Verilog实现方法,并给出仿真结果。本书还提出了一个通用的利用Modelsim和VS实现图像处理的仿真测试平台。这个仿真平台是我学习的重点,在书本的第五章,系统仿真。主要是动手搭建视频验证的仿真平台,给算法提供模拟的视频源,联合matlab查看算法处理效果。借鉴书本的思路,用在自己的图像处理项目中。
    本书内容概述如下:

    (I)第1~5章是基础章节, 重点介绍数字图像处理和FPGA程序设计的基础知识。
    第1章简单介绍了图像处理的基础知识, 包括图像处理的发展现状, 还地介绍了图像从获取到显示存储的基本流程。

    第2章首先介绍了FPGA的发展现状, 生产厂家及其开发流程。接着介绍了基千FPGA的图像处理的基本开发流程。

    第3章主要介绍了在FPGA中应用的编程语言。本章并没有详细介绍Verilog语法,而是从工程应用的角度介绍常用的设计方法和实例。

    第4章主要介绍了把软件算法映射到FPGA常用的技巧。首先介绍了应用较广泛的流水线设计方法, 接着介绍了FPGA硬件计算技术, 包括一些常用的计算转换、查找表、浮点计算、C ordie计算等方法。最后介绍了在图像处理中用途非常多的存储器映射, 并提出了一些其他设计技巧。

    第5章首先简要介绍了仿真测试软件Modelsim的使用, 接着重点介绍了一个通用的视频图像处理仿真测试系统。这个测试系统包括完整的视频模拟、视频捕获, 以及testbench设计, 并结合基于MFC 的VC上位机来实现测试系统的搭建。

    (2)第6~10章主要介绍算法实现。
    第6章介绍直方图操作, 主要介绍几种常用直方图操作的FPGA实现: 直方图统计、直方图均衡、直方图规定及直方图线性拉伸。

    第7章介绍基千图像处理的线性滤波。首先, 介绍了均值滤波算法、高斯滤波算法、Sobel 算子及FFT等常见的几种线性滤波原理。其次, 介绍了均值滤波算法和Sobel算子的FPGA实现。第8章主要介绍基千图像处理的非线性滤波算法, 包括排序滤波的基本原理及其FPGA 实现方法。

    第9章主要介绍基千图像处理的形态学滤波算法, 包括形态学滤波的基本概念,包括形态学膨胀、形态学腐蚀、开运算及闭运算等。重点介绍了基千FPGA 的Tophat滤波的原理及实现方法。

    第10章主要介绍基千图像处理的常见的分割算法,包括全局阙值分割、局部自适应阀值分割及Canny 算子。重点介绍基千FPGA 的局部自适应阙值分割和Canny 算子的设计与实现。

    第11 章主要介绍与视频和图像处理相关的输入/输出接口, 包括CameraLink、火线接口、USB 接口、千兆以太网等视频输入接口和CVT 标准,以及VGA, PAL, DVI,HDMI 等视频输出接口。其中, 给出了VGA 和PAL 接口的Verilog 代码实现。


    二  函数

    函数,理解为对于给定的输入(一个或多个)进行处理后返回输出值。在MATLAB中,有sum函数,max函数等。

    在Verilog中,函数一是常用来计算数学公式的值。如波特率计算公式:divp10x = (10 * fsysclk) / (16 * baud)

    二是函数能够被多次调用,避免冗余。

    (一)函数的声明与调用:

    函数的声明有两种方式:

    //express1
    function[range] function_name(ports_list);
    begin
       ...
    end
    endfunction
    //example1
    function [7:0] getbyte (input [15:0] address);
    begin
    . . .
    getbyte = result_expression;
    end
    endfunction
    //express2
    function[range] function_name;
    ports_list;
    begin
       ...
    end
    endfunction
    //example2
    function [7:0] getbyte;
    input [15:0] address;
    begin
    . . .
    getbyte = result_expression;
    end
    endfunction
    

     函数只能有一个输出,可以通过多个信号的拼接完成多个信号输出。

    编写函数代码的原则:

    (1)    函数定义只能模块中完成,不能出现在过程块(即always)。

    (2)    函数至少一个输入端口;不能包含输出端口和双向端口;

    (3)    在函数结构中,不能使用任何形式的时间控制语句(#,wait)也不能使用disable中止语句。

    (4)    函数定义不能出现always语句。

    (5)    函数内部可以调用函数,但不能调用任务;

    (6)    函数调用即可在过程块语句,也可以在assign赋值语句出现;

    (7)    函数调用语句不能单独出现,只能作为赋值语句的右端操作数;

    下面用一个函数计算游泳池的面积,掌握函数的使用:

     下面是计算上图的面积的Verilog代码,体现了函数的声明,调用。

     1 module function_total
     2 
     3   (
     4 
     5     input CLK, input RST,
     6 
     7     input[7:0] width,
     8 
     9     output reg[16:0]area
    10 
    11   );
    12 
    13  
    14 
    15 //Load other module(s)
    16 
    17  
    18 
    19 //Definition for Variables in the module
    20 
    21  
    22 
    23 //Functions for area calculation
    24 
    25 function[15:0] circle(input[7:0] diameter);
    26 
    27 begin
    28 
    29     circle = (24'd201 * {16'h0, diameter} * {16'h0, diameter}) / 256;
    30 
    31 end
    32 
    33 endfunction
    34 
    35  
    36 
    37 function[15:0] square(input[7:0] width);
    38 
    39 begin
    40 
    41     square = {8'h0, width} * {8'h0, width};
    42 
    43 end
    44 
    45 endfunction
    46 
    47  
    48 
    49 function[16:0] total(input[7:0] width);
    50 
    51 begin
    52 
    53     total = {2'h0, square(width)} + {2'h0, circle(width)};
    54 
    55 end
    56 
    57 endfunction
    58 
    59  
    60 
    61 //Logical
    62 
    63 always @(posedge CLK, negedge RST)
    64 
    65 begin
    66 
    67     if (!RST)
    68 
    69     //Reset
    70 
    71     begin
    72 
    73        area <= 17'h0000;
    74 
    75     end
    76 
    77     else
    78 
    79     //Data comes
    80 
    81     begin
    82 
    83         area <= total(width);
    84 
    85     end
    86 
    87 end
    88 
    89  
    90 
    91 endmodule
    func_total

    其中的Π/4 = (24'd201/256),diameter为圆的直径。代码注意点:一是体现了函数中调用函数。二是信号位宽的变化。

    square = {8'h0, width} * {8'h0, width};

    //平方,则每个变量前补充相同输入变量位宽,即输出square为输入位宽的两倍。

    total = {2'h0, square(width)} + {2'h0, circle(width)};

    //为避免相加溢出,位宽又扩宽两位,仅仅把低17位的值赋给total。

    三  任务

     

    与函数的调用类似,任务的调用只有一种形式,如表7.2 所示。与函数的调用不同的是:任务的调用是在代码里单独一行书写的。

    下面是几点需要强调的规则:

    (I) 任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出及

    双向端口;

    (2)在任务定义的描述语句中,可以使用出现不可综合操作符合语句(使用最为频

    繁的就是延迟控制语旬),但这样会造成该任务不可综合;

    (3)在任务中既可以调用其他的任务或函数,也可以调用自身;

    (4) 在任务定义结构内不能出现initial always 过程块;

    (5)可以在任务中中断正在执行的任务,但其是不可综合的;当任务被中断后,程

    序流程将返回到调用任务的地方继续向下执行;

    (6)任务调用语句只能出现在过程块内;

    (7)任务调用语句和一条普通的行为描述语句的处理方法一致;

    (8)当被调用输入、 输出或双向端口时, 任务调用语句必须包含端口名列表, 且信号端口顺序和类型必须和任务定义结构中的顺序和类型一致; 需要说明的是, 任务的输 出端口必须和寄存器类型的数据变量对应;

    (9)综合任务只能实现组合逻辑, 也就是说, 调用可综合任务的时间为 “ 0"; 而在面向仿真的任务中可以带有时序控制,如时延,因此面向仿真的任务的调用时间不为“ 0”。

    下面为任务声明及调用例子

     1 task total(input[7:0] width, output[l7:0J area); 
     2 begin 
     3 area <= {2'h0, square(width)} + {2'h0, circle(width)};
     4  end 
     5 endtask 
     6 
     7 always @(posedge CLK, negedge RST)
     8 begin
     9     if (!RST)
    10     //Reset
    11     begin
    12        area <= 17'h0000;
    13     end
    14     else 
    15     //Data comes
    16     begin
    17         total(width, area);//task call(调用)
    18     end
    19 end
    Task example

    为了避免多次调用任务造成的地址冲突,添加automatic使任务成为可重入的。这时在调用任务时,会自动给任务声明变量分配动态地址空间,从 而有效避免了地址空间的冲突。

    一个模块里的任务可以在其他模块调用。

    1 module module_main; 
    2 task task_1...
    3 task task_2...
    4 endmodule
    5 //other
    6 module mainm1; 
    7 m1.task_1(...)
    8 m2.task_1(...)
    9 endmodule

    上面对于仿真验证特别管用。

    三 总结

     今天下午就看了一讲,着重是自己看一遍,然后动手敲一下代码,之后仿真验证结果。计算游泳池面积的仿真代码:

    `timescale 1ns / 1ps        
    
    module tb_top();
    //========================================================
    //parameters
    parameter CLK_FREQ   = 50.000;//ddr reference clock frequency, unit: MHz
    parameter CLK_PERIOD = 1000.0/CLK_FREQ; //unit: ns 
    
     // parameter  FREQ = 100_000_000 ;
     // parameter  BAUDRATE    = 115200  ;
    //=======================================================
    reg          clk;            // 50M
    reg          rst_n         ;
    reg [7:0] input_data;
    
    
    
    wire[16:0] output_data;
    
    
    
    //========================================================
    GSR GSR(.GSRI(1'b1));
    
    //==============================================  
    //rst_n
    initial 
    begin
        rst_n = 1'b0;
        input_data = 0;
        #200;
        rst_n = 1'b1;
        input_data = 8'd1;//1+(pi/4)=1
            #200;
      
        input_data = 8'd4;//16+12=28
        #200;
         input_data = 8'd6;//36+28=64
         #200;
               #200;
      
        input_data = 8'd10;//100+78.5=178.5
        #200;
         rst_n = 1'b0;
         #200;
         $stop;
    end
    
    
    //----------------------------------------------------
    //ref clk
    initial 
    begin
    	clk = 1'b0;
    end
    
    always  #(CLK_PERIOD/2.0) clk = ~clk;
    
    //==================================================
    //TX
        function_total inst_function_total (.CLK(clk), .RST(rst_n), .width(input_data), .area(output_data));
    
    
              
    endmodule
    

      

    仿真结果如下所示:

    后面还有很多要看,但以理解原理和动手实践为主。通过编代码和仿真,能够感觉更真实。












  • 相关阅读:
    8行代码批量下载GitHub上的图片
    python经典面试算法题1.1:如何实现链表的逆序
    pandas处理excel的常用方法技巧(上)
    numpy---python数据分析
    python、C++经典算法题:打印100以内的素数
    Java中数组、集合、链表、队列的数据结构和优缺点和他们之间的区别
    Hystrix
    Java中的static关键字解析
    Spring Boot
    Springcloud和Dubbo的区别。Eureka和Ribbon和Hystrix和zuul
  • 原文地址:https://www.cnblogs.com/Xwangzi66/p/15056625.html
Copyright © 2020-2023  润新知