一 简述
最近恶补基础知识,借了<<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
其中的Π/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
为了避免多次调用任务造成的地址冲突,添加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
仿真结果如下所示:
后面还有很多要看,但以理解原理和动手实践为主。通过编代码和仿真,能够感觉更真实。