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

  

仿真结果如下所示:

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

 



 





 




 

 

 

 

 

 

 



 

posted @ 2021-07-24 23:02  菜鸟芯片师  阅读(1625)  评论(0编辑  收藏  举报