基于FPGA的数字图像处理【2.0】

3.7 常用语法

3.7.1 参数化

        正如前面所述,图像宽度、高度、位宽和处理尺寸等参数最好作为可配置的参数,可达到代码易维护、易移植和可读性好的目的。参数化主要有两种方式:通过define关键字和parameter关键字定义参数。
        parameter关键字类似于C语言中的形参,可以在其他模块调用时实例化参数,不同的是这个参数是不可运行时更改的,而是编译时就确定好的。

示例如下:
//Canny算子模块
module canny(
rst_n,
clk,
din_valid,
din,
dout,
vsync,
vsync_out,
dout_valid
);
parameter   DW = 14;    //图像数据位宽
parameter   KSZ = 3;     //Sobel算子尺寸
parameter   IH = 512;    //图像高度
parameter   IW = 640;    //图像宽度
parameter   ThrHigh = 20;  //阈值化过程高阈值
parameter   ThrLow = 10;  //阈值化过程低阈值
//cordic module data widthparameter DW_OUT = 20; //output data width :
20 bits,with
4 frac bits,can not be modified
parameter   DW_IN = 16;   //input data width :
16 bits,can not be
modified
//本地参数
localparam NMS_LATENCY = 5; //Non-Maximum
Suppression
localparam HT_LATENCY =
4;  //Hysteresis Thresholding Latency
input     rst_n;
input     clk;
input     din_valid;
input [DW-1:0]din;
output [DW-1:0]dout;
input     vsync;
output     vsync_out;
output     dout_valid;
模块引用也有两种方式,分别如下:
方式1:
wire    canny_dvalid_in;
wire  [8 - 1:0]canny_data_in;
wire    canny_vsync_in;
wire    canny_dvalid;
wire  [8 - 1:0]canny_data;
wire    canny_vsync;wire rst_n;
wire clk;
canny #(8,3,512,640,20,10,20,16)
canny_ins(
.rst_n (rst_l),
.clk  (clk),
.din  (canny_data_in),
.din_valid (canny_dvalid_in),
.dout_valid (canny_dvalid),
.vsync(canny_vsync_in),
.vsync_out(canny_vsync),
.dout (canny_data)
);
方式2:
canny canny_ins(
.rst_n (rst_n),
.clk  (clk),
.din  (canny_data_in),
.din_valid (canny_dvalid_in),
.dout_valid (canny_dvalid),
.vsync(canny_vsync_in),
.vsync_out(canny_vsync),
.dout (canny_data)
);
defparam canny_ins.DW = 8;
defparam canny_ins.KSZ = 3;
defparam canny_ins.IH = 512;defparam canny_ins.IW = 640;
defparam canny_ins.ThrHigh = 20;
defparam canny_ins.ThrLow = 10;
defparam canny_ins.DW_OUT = 640;
defparam canny_ins.DW_IN = 640;

        其中,第一种方式要求参数次序完全按照模块定义中的参数定义顺序来写,容易出错,并且相对于第二种方法不够直观,特别是在参数比较多的情况下,建议采用第二种方式实例化模块。
        模块定义中的参数定义为默认参数,当调用模块时没有例化参数,将会例化默认参数。另外一个关键字define类似于C语言中的define,主要用于本地模块的一些参数定义,例如状态机及常量定义等。实例如下:实例:SDRAM的初始化操作状态机 


'define I_NOP 5'd0 //等待上电200us稳
定期结束
'define    I_PRE    5'd1    //预充电状态
'define I_TRP 5'd2 //等待预充电完成
tRP
'define    I_AR1    5'd3    //第1次自刷新
'define I_TRF1 5'd4 //等待第1次自刷新
结束tRFC
'define    I_AR2    5'd5    //第2次自刷新
'define I_TRF2 5'd6 //等待第2次自刷新
结束tRFC
'define    I_AR3    5'd7    //第3次自刷新
'define I_TRF3 5'd8 //等待第3次自刷新
结束tRFC
'define    I_AR4    5'd9    //第4次自刷新
'define I_TRF4 5'd10 //等待第4次自刷新
结束tRFC
'define    I_AR5    5'd11    //第5次自刷新
'define I_TRF5 5'd12 //等待第5次自刷新
结束tRFC
'define    I_AR6    5'd13    //第6次自刷新
'define I_TRF6 5'd14 //等待第6次自刷新
结束tRFC
'define    I_AR7    5'd15    //第7次自刷新
'define I_TRF7 5'd16 //等待第7次自刷新
结束tRFC
'define    I_AR8    5'd17    //第8次自刷新
'define I_TRF8 5'd18 //等待第8次自刷新
结束tRFC
'define    I_MRS    5'd19    //模式寄存器设置
'define    I_TMRD   5'd20    //等待模式寄存器设
置完成tMRD
'define    I_DONE   5'd21    //初始化完成
reg [4:0] init_state;         // SDRAM初始化状态
寄存器
always @ (posedge clk or negedge rstn)
if(!rstn) init_state <= 'I_NOP;
else
case (init_state)
'I_NOP: init_state <= done_200us ? 'I_PRE:'I_NOP;
//上电复位后200us'I_PRE: init_state <= (TRP_CLK == 0) ?
'I_AR1:'I_TRP;
//预充电状态
'I_TRP: init_state <= ('end_trp) ? 'I_AR1:'I_TRP;
//预充电等待TRP_CLK个时钟周期
'I_AR1: init_state <= (TRFC_CLK == 0) ?
'I_AR2:'I_TRF1;
//第1次自刷新
'I_TRF1:init_state <= ('end_trfc) ? 'I_AR2:'I_TRF1;
//等待第1次自刷新结束
'I_AR2: init_state <= (TRFC_CLK == 0) ?
'I_AR3:'I_TRF2;
//第2次自刷新
'I_TRF2:init_state <= ('end_trfc) ? 'I_AR3:'I_TRF2;
//等待第2次自刷新结束
'I_AR3: init_state <= (TRFC_CLK == 0) ?
'I_AR4:'I_TRF3;
//第3次自刷新
'I_TRF3:init_state <= ('end_trfc) ? 'I_AR4:'I_TRF3;
//等待第3次自刷新结束
'I_AR4: init_state <= (TRFC_CLK == 0) ?
'I_AR5:'I_TRF4;
//第4次自刷新
'I_TRF4:init_state <= ('end_trfc) ? 'I_AR5:'I_TRF4;
//等待第4次自刷新结束
'I_AR5: init_state <= (TRFC_CLK == 0) ?
'I_AR6:'I_TRF5;//第5次自刷新
'I_TRF5:init_state <= ('end_trfc) ? 'I_AR6:'I_TRF5;
//等待第5次自刷新结束
'I_AR6: init_state <= (TRFC_CLK == 0) ?
'I_AR7:'I_TRF6;
//第6次自刷新
'I_TRF6:init_state <= ('end_trfc) ? 'I_AR7:'I_TRF6;
//等待第6次自刷新结束
'I_AR7: init_state <= (TRFC_CLK == 0) ?
'I_AR8:'I_TRF7;
//第7次自刷新
'I_TRF7:init_state <= ('end_trfc) ? 'I_AR8:'I_TRF7;
//等待第7次自刷新结束
'I_AR8: init_state <= (TRFC_CLK == 0) ?
'I_MRS:'I_TRF8;
//第8次自刷新
'I_TRF8:init_state <= ('end_trfc) ? 'I_MRS:'I_TRF8;
//等待第8次自刷新结束
'I_MRS: init_state <= (TMRD_CLK == 0) ?
'I_DONE:'I_TMRD;
//模式寄存器设置
'I_TMRD:init_state <= ('end_tmrd) ? 'I_DONE:'I_TMRD;
//等待模式寄存器设置完成
'I_DONE:init_state <= 'I_DONE; //SDRAM的初始化设置完
成标志
defaul
3.7.2 条件编译

        条件编译在图像处理领域非常有用,特别是图像处理的算法处理方面。由于资源限制,处理尺寸不可能像软件那样达到运行时调整,但是有的时候需要对不同的尺寸进行测试,或者算法需要两个尺寸的算子进行配合。这个时候为两个尺寸算子设计两套独立的电路是非常麻烦的事情,用Verilog的generate语句可以实现条件编译功能,这个功能类似于C语言中的#ifdef语句。
        一个简单的例子是二维卷积的过程中,根据不同的处理尺寸例化不同数目的行缓存(关于行缓存的概念请参考映射技术的相关章节)。实例如下:

parameter KSZ = 3;//处理尺寸
reg rst_all;
reg [DW-1:0]line_din[0:KSZ-2];
wire [DW-1:0]line_dout[0:KSZ-2];
wire [KSZ-2:0]line_empty;
wire [KSZ-2:0]line_full;
wire [KSZ-2:0]line_rden;
reg [KSZ-2:0]line_wren;
wire [9:0] line_count[0:KSZ-2];
generate
begin : line_buffer
genvar i;
for (i = 0; i <= KSZ - 2; i = i + 1)
begin : buf_inst
line_buffer #(DW,IW)
line_buf_inst(.rst(rst_all),
.clk(clk),
.din(line_din[i]),
.dout(line_dout[i]),
.wr_en(line_wren[i]),
.rd_en(line_rden[i]),
.empty(line_empty[i]),
.full(line_full[i]),
.count(line_count[i])
);
end
end
endgenerate
此外,对于不同的参数,电路的细节可能不一致,如下所示:
generate
if (KSZ == 3)
begin : MAP16
//针对尺寸为3的算法进行处理
end
endgenerate
generate
if (KSZ == 5)
begin : MAP17
//针对尺寸为5的算法进行处理
end
endendgenerate
3.7.3 位宽匹配

        Verilog与VHDL相比,位宽的匹配不够严格,也就是一个表达式两边的信号可能位宽不一致,这无疑带来了很大的潜在风险。在这种情形下,位宽匹配是必不可少的步骤。下面是一个位宽匹配的实例:
示例 1个简单的计数器(计数宽度可根据参数例化)

parameter DW = 8;//数据位宽
reg [DW-1:0]trig_cnt;
always @(posedge clk or negedge reset_l)
if (reset_l== 1'b0)
trig_cnt <= #1 {DW{1'b0}};
else
trig_cnt <= #1 trig_cnt + {{DW-1{1'b0}},1'b1 };

        {DW{1'b0}}为位宽为DW的数字0,{{DW-1{1'b0}},1'b1}为位宽为DW的数字1,将 1'b1 前面填充DW-1个0达到位宽匹配的目的,实际上是利用了Verilog的复制语法。

3.7.4 二维数组

        图像处理是一个二维的计算领域,二维数组和for循环在Verilog电路的设计中十分有用。实际上在软件处理时,整幅图像是放在一个二维数组里面,这个数组的尺寸分别是图像的宽度和高度。在FPGA中我们很少会把整幅图像这样放,这是由于一幅图像尺寸通常会非常大,这样放会占用相当大的存储空间。
        实际应用到的FPGA中的二维数组是处理算法中的窗口,例如一个典型的二维卷积矩形窗口,算法有时候需要对这个窗口的所有像素进行同时操作,这个时候就需要将窗口的数据放入一个二维数组中。如下所示:

parameter DW = 8;
parameter KSZ = 3;
reg [DW-1:0]window_buf[0:KSZ*KSZ-1];

        window_buf为定义的二维数组,数组的个数为矩形窗口内的像素总数,对于尺寸为3的窗口,这个数目为9。假定在某一时刻得到了当前窗口内所有的像素,则以下的代码展示了求取在当前窗口的像素和。

integer i;
reg [DW+4-1:0]window_sum;
always @(posedge clk or negedge rst_n)
if ((~rst_n) == 1'b1)
window_sum <= #1 {DW+4{1'b0}};
else
begin
window_sum <= window_buf[0] + window_buf[1] +
window_buf[2]+window_buf[3]+window_buf[4]+window_buf[5]+win
dow_buf[6]+window_buf[7]+ window_buf[8];
end

        上述代码看起来有点冗长,实际上,这样写会使综合出来的组合电路非常长,综合出来的频率将会很低。一般情况下不会这么写,而是采用两两相加的方法进行处理。我们将在线性滤波的相关章节介绍这个问题。

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BinaryStarXin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值