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