最近一直在做local dimming项目的FPGA硬件实现,算法的其中一步就是直方图统计,即数字图像的某一灰度级的像素数,这个直方图的源码找了半天才搞到,就在<<牟新刚周晓郑晓亮著: 基千FPGA的数字图像处理原理及应用>>这一本书有详细的描述。但有了这个代码,还得查看直方图处理的效果,那我只有搭建仿真查看,但modelsim一直出错,提示直方图模块调用的双口ram不存在,于是下面介绍modelsim独立仿真带有vivado的IP的解决办法。
后面还会附上我一直在用的仿真脚本,十分方便!
一:实现步骤
第一步在vivado中编译仿真库,将编译后的仿真库放在自己新建的文件夹,如D:/xilinx/xlib,我已经编译好了,如下图
第二步,找到编译库路径下的modelsim.ini文件,即下面右图中的红框文件,去掉只读属性,打开后选择包含编译库的代码,图2中的63-72即vivado中包含的编译库,复制后粘贴到modelsim10.5安装根目录下的modelsim.ini文件中,如第三张图中的83-94行,即为粘贴注释的,为了以后避免和alter及ISE14.7等编译库混合,用分号注释加上分割线,下次用到其他的编译库则注释就行。保存后勾选只读属性,如下图所示
第三步,仿真带IP核的文件前提是你在vivado生成了IP核,如下图所示,找到下面红框中的两个文件路径,复制后这两个文件加入到仿真工程路径D:\3FPGA_project\02LCD_project\histogram_sim\rtl,如下面的第二张图所示
第四步:在modelsim中独立仿真,我一般是用脚本,即do文件的形式,通过编译do文件tb_top.do和波形添加do文件tb_top_wave.do实现自动仿真,这样相对于手工的形式可以避免很多体力活,先打开modelsim切换路径到sim下,直接在modelsim中输入do tb_top.do就可加载波形,如下图所示:
第五步,波形显示
二:源码
1.设计模块源码histogram_2d的源码,已经将其中的双口ram的IP核调用和其中一些代码注释(报错的),因为书中的vivado版本比较老,故不能仿真运行,会报错。
1 `timescale 1ns/1ns 2 3 module histogram_2d( 4 rst_n, 5 clk, 6 din_valid, 7 din, 8 dout, 9 vsync, 10 dout_valid, 11 rdyOutput, 12 //`ifdef Equalize 13 hist_cnt_addr, 14 hist_cnt_out, 15 //`endif 16 //`ifdef LinearTransfer 17 lowCnt, 18 highCnt, 19 lowIndex, 20 highIndex, 21 //`endif 22 int_flag 23 ); 24 25 parameter DW = 14; 26 parameter IH = 512; 27 parameter IW = 640; 28 parameter TW = 32; 29 30 localparam TOTAL_CNT = IW * IH; 31 localparam HALF_WIDTH = (TW>>1); 32 33 input rst_n; 34 input clk; 35 input din_valid; 36 input [DW-1:0]din; 37 input rdyOutput; 38 39 output reg [HALF_WIDTH:0]dout; 40 input vsync; 41 output reg dout_valid; 42 output reg int_flag; 43 44 //`ifdef LinearTransfer 45 input [TW-1:0]lowCnt; 46 input [TW-1:0]highCnt; 47 output reg[DW-1:0]lowIndex; 48 output reg[DW-1:0]highIndex; 49 //`endif 50 51 //`ifdef Equalize 52 input [DW-1:0]hist_cnt_addr; 53 output reg [TW-1:0]hist_cnt_out; 54 //`endif 55 56 reg vsync_r; 57 reg dvalid_r; 58 reg dvalid_r2; 59 reg [DW-1:0]din_r; 60 reg [DW-1:0]din_r2; 61 wire hsync_fall; 62 wire hsync_rise; 63 reg [9:0]hsync_count; 64 reg count_en; 65 wire [DW-1:0]mux_addr_b; 66 wire [DW-1:0]mux_addr_b2; 67 wire [TW-1:0]q_a; 68 wire [TW-1:0]q_b; 69 reg [TW-1:0]counter; 70 wire [TW-1:0]count_value; 71 wire rst_cnt; 72 wire inc_en; 73 wire we_a; 74 wire we_b; 75 wire we_b_l; 76 reg we_b_h; 77 78 reg int_r; 79 80 wire [DW-1:0]addr_a; 81 wire [DW-1:0]clr_addr; 82 reg [DW-1:0]clr_addr_r; 83 reg [DW:0]out_pixel; 84 85 reg count_all; 86 //reg count_all_r; 87 reg count_en_r; 88 89 reg [TW-1:0]hist_cnt; 90 wire rstOutput; 91 92 93 wire [TW-1:0]dataTmp2; 94 wire clr_flag; 95 96 assign #1 hsync_fall = dvalid_r & (~(din_valid)); 97 assign #1 hsync_rise = (~(dvalid_r)) & din_valid; 98 99 always @(posedge clk or negedge rst_n) 100 if (((~(rst_n))) == 1‘b1) 101 hsync_count <= #1 {10{1‘b0}}; 102 else 103 begin 104 if (vsync_r == 1‘b1) 105 hsync_count <= #1 {10{1‘b0}}; 106 else if (hsync_fall == 1‘b1) 107 hsync_count <= hsync_count + 10‘b1; 108 end 109 110 always @(posedge clk or negedge rst_n) 111 if (((~(rst_n))) == 1‘b1) 112 count_en <= #1 1‘b0; 113 else 114 begin 115 if (hsync_count >= IH) 116 count_en <= #1 1‘b0; 117 else if (hsync_rise == 1‘b1) 118 count_en <= #1 1‘b1; 119 else 120 count_en <= #1 count_en; 121 end 122 123 assign mux_addr_b = ((count_en == 1‘b1)) ? din_r : 124 clr_addr; 125 assign mux_addr_b2 = ((count_en == 1‘b1)) ? din_r : 126 clr_addr_r; 127 128 129 always @(posedge clk) 130 begin 131 din_r2 <= #1 din_r; 132 dvalid_r2 <= #1 dvalid_r; 133 end 134 135 always @(posedge clk) 136 begin 137 if (rst_cnt == 1‘b1) 138 counter <= #1 {{TW-1{1‘b0}},1‘b1}; 139 else if (inc_en == 1‘b1) 140 counter <= #1 counter + {{TW-1{1‘b0}},1‘b1}; 141 else 142 counter <= #1 counter; 143 end 144 145 assign #1 rst_cnt = (((din_r != din_r2) | ((dvalid_r2 == 1‘b1) & (dvalid_r == 1‘b0)))) ? 1‘b1 : 146 1‘b0; 147 assign #1 inc_en = (((din_r == din_r2) & (dvalid_r2 == 1‘b1))) ? 1‘b1 : 148 1‘b0; 149 150 assign #1 we_a = ((((din_r != din_r2) & (dvalid_r2 == 1‘b1)) | ((dvalid_r2 == 1‘b1) & (dvalid_r == 1‘b0)))) ? 1‘b1 : 151 1‘b0; 152 assign #1 count_value = ((count_en == 1‘b1)) ? counter + q_b : 153 {TW{1‘b0}}; 154 155 assign #1 addr_a = din_r2; 156 157 assign dataTmp2 = {TW{1‘b0}}; 158 159 // hist_buffer dpram_bin_l( 160 // .address_a(addr_a), //addra 161 // .address_b(mux_addr_b), //addrb 162 // .clock(clk), 163 // .data_a(count_value[HALF_WIDTH - 1:0]), //dina 164 // .data_b(dataTmp2[HALF_WIDTH - 1:0]), 165 // .wren_a(we_a), 166 // .wren_b(we_b_l), 167 // .q_a(q_a[HALF_WIDTH - 1:0]), //douta 168 // .q_b(q_b[HALF_WIDTH - 1:0]) //doutb 169 // ); 170 // 171 hist_buffer dpram_bin_l ( 172 .clka(clk), // input wire clka 173 .ena(1), // input wire ena 174 .wea(we_a), // input wire [0 : 0] wea 175 .addra(addr_a[9 : 0]), // input wire [9 : 0] addra 176 .dina(count_value[HALF_WIDTH - 1:0]), // input wire [31 : 0] dina 177 .douta(q_a[HALF_WIDTH - 1:0]), // output wire [31 : 0] douta 178 .clkb(clk), // input wire clkb 179 .enb(1), // input wire enb 180 .web(we_b_l), // input wire [0 : 0] web 181 .addrb(mux_addr_b[9 : 0]), // input wire [9 : 0] addrb 182 .dinb(0), // input wire [31 : 0] dinb 183 .doutb(q_b[HALF_WIDTH - 1:0]) // output wire [31 : 0] doutb 184 ); 185 // 186 // defparam dpram_bin_l.AW = DW; 187 // defparam dpram_bin_l.DW = HALF_WIDTH; 188 189 // hist_buffer dpram_bin_h( 190 // .address_a(addr_a), 191 // .address_b(mux_addr_b2), 192 // .clock(clk), 193 // .data_a(count_value[TW - 1:HALF_WIDTH]), 194 // .data_b(dataTmp2[TW - 1:HALF_WIDTH]), 195 // .wren_a(we_a), 196 // .wren_b(we_b_h), 197 // .q_a(q_a[TW - 1:HALF_WIDTH]), 198 // .q_b(q_b[TW - 1:HALF_WIDTH]) 199 // ); 200 201 hist_buffer dpram_bin_h ( 202 .clka(clk), // input wire clka 203 .ena(1), // input wire ena 204 .wea(we_a), // input wire [0 : 0] wea 205 .addra(addr_a[9 : 0]), // input wire [9 : 0] addra 206 .dina(count_value[TW - 1:HALF_WIDTH]), // input wire [31 : 0] dina 207 .douta(q_a[TW - 1:HALF_WIDTH]), // output wire [31 : 0] douta 208 .clkb(clk), // input wire clkb 209 .enb(1), // input wire enb 210 .web(we_b_h), // input wire [0 : 0] web 211 .addrb(mux_addr_b2[9 : 0]), // input wire [9 : 0] addrb 212 .dinb(0), // input wire [31 : 0] dinb 213 .doutb(q_b[TW - 1:HALF_WIDTH]) // output wire [31 : 0] doutb 214 ); 215 216 // defparam dpram_bin_h.AW = DW; 217 // defparam dpram_bin_h.DW = HALF_WIDTH; 218 219 always @(posedge clk or negedge rst_n) 220 if (((~(rst_n))) == 1‘b1) 221 count_en_r <= #1 1‘b0; 222 else 223 count_en_r <= #1 count_en; 224 225 assign rstOutput = count_en_r | (~(rdyOutput)); 226 227 reg [DW-1:0]lowIndex_tmp; 228 reg [DW-1:0]highIndex_tmp; 229 reg [DW-1:0]highIndex_tmp2; 230 reg bFindMax; 231 reg bFindMin; 232 233 always @(posedge clk or negedge rst_n) 234 if ((~(rst_n)) == 1‘b1) 235 begin 236 lowIndex_tmp <= {DW{1‘b0}}; 237 highIndex_tmp <= {DW{1‘b1}}; 238 bFindMin <= 1‘b0; 239 bFindMax <= 1‘b0; 240 highIndex_tmp2 <= {DW{1‘b0}}; 241 end 242 else 243 begin 244 if (vsync_r == 1‘b0 & vsync == 1‘b1) 245 begin 246 lowIndex_tmp <= {DW{1‘b0}}; 247 highIndex_tmp <= {DW{1‘b1}}; 248 highIndex_tmp2 <= {DW{1‘b0}}; 249 lowIndex <= lowIndex_tmp; 250 if (bFindMax == 1‘b1) 251 highIndex <= highIndex_tmp; 252 else 253 highIndex <= highIndex_tmp2; 254 bFindMin <= 1‘b0; 255 bFindMax <= 1‘b0; 256 end 257 else 258 begin 259 if (out_pixel[0] == 1‘b1) 260 begin 261 if ((~(q_b == {HALF_WIDTH{1‘b0}}))) 262 highIndex_tmp2 <= clr_addr - 4‘h1; 263 if ((hist_cnt >= lowCnt) & bFindMin == 1‘b0) 264 begin 265 lowIndex_tmp <= clr_addr - 4‘h1; 266 bFindMin <= 1‘b1; 267 end 268 if (hist_cnt >= (TOTAL_CNT - highCnt) & bFindMax == 1‘b0) 269 begin 270 highIndex_tmp <= clr_addr - 4‘h1; 271 bFindMax <= 1‘b1; 272 end 273 end 274 end 275 end 276 277 // hist_buffer hist_cnt_buf( 278 // .address_a(out_pixel_r2), 279 // .address_b(hist_cnt_addr), 280 // .clock(clk), 281 // .data_a(hist_cnt), 282 // .data_b(), 283 // .wren_a(dout_valid), 284 // .wren_b(1‘b0), 285 // .q_a(), 286 // .q_b(hist_cnt_temp) 287 // ); 288 // defparam hist_cnt_buf.AW = DW; 289 // defparam hist_cnt_buf.DW = TW; 290 291 hist_buffer hist_cnt_buf ( 292 .clka(clk), // input wire clka 293 .ena(1), // input wire ena 294 .wea(dout_valid), // input wire [0 : 0] wea 295 .addra(out_pixel[9:0]), // input wire [9 : 0] addra 296 .dina(hist_cnt), // input wire [31 : 0] dina 297 .douta(), // output wire [31 : 0] douta 298 .clkb(clk), // input wire clkb 299 .enb(1), // input wire enb 300 .web(0), // input wire [0 : 0] web 301 .addrb(hist_cnt_addr[9:0]), // input wire [9 : 0] addrb 302 .dinb(0), // input wire [31 : 0] dinb //data_b 303 .doutb(hist_cnt_temp) // output wire [31 : 0] doutb 304 ); 305 306 endmodule
2.原创的脚本文件
A添加信号和显示波形的tb_top_wave.do
1 #添加信号和显示其波形 2 onerror {resume} 3 quietly WaveActivateNextPane {} 0 4 add wave -noupdate -divider {input paramters} 5 add wave -noupdate -radix unsigned /tb_top/CLK_FREQ 6 add wave -noupdate -radix unsigned /tb_top/CLK_PERIOD 7 8 add wave -noupdate -divider {histogram_2d input} 9 add wave -noupdate /tb_top/inst_hist/clk 10 add wave -noupdate /tb_top/inst_hist/rst_n 11 add wave -noupdate /tb_top/inst_hist/din_valid 12 add wave -noupdate /tb_top/inst_hist/din 13 14 add wave -noupdate -divider {histogram_2d output} 15 add wave -noupdate /tb_top/inst_hist/dout 16 add wave -noupdate /tb_top/inst_hist/vsync 17 add wave -noupdate /tb_top/inst_hist/dout_valid 18 19 add wave -noupdate -divider {end signal} 20 21 TreeUpdate [SetDefaultTree] 22 WaveRestoreCursors {{Cursor 1} {912366093 ps} 0} 23 configure wave -namecolwidth 150 24 configure wave -valuecolwidth 100 25 configure wave -justifyvalue left 26 configure wave -signalnamewidth 0 27 configure wave -snapdistance 10 28 configure wave -datasetprefix 0 29 configure wave -rowmargin 4 30 configure wave -childrowmargin 2 31 configure wave -gridoffset 0 32 configure wave -gridperiod 1 33 configure wave -griddelta 40 34 configure wave -timeline 0 35 configure wave -timelineunits ns 36 update 37 WaveRestoreZoom {891247063 ps} {925431255 ps}
B新建work库,编译.v文件和启动顶层仿真文件,及执行添加信号和显示波形的tb_top_wave.do的编译do文件tb_top.do
1 #不需要新建modelsim工程,直接运行.do文件就可以仿真 2 quit -sim 3 #新建work库 4 vlib work 5 6 #将work库映射到当前工作目录 7 #vmap [-help] [-c] [-del] [<logical_name>] [<path>] 8 vmap work 9 10 #编译所有.v文件到work工作库 11 #-work <path> Specify library WORK 12 #-vlog01compat Ensure compatibility with Std 1364-2001 13 #-incr Enable incremental compilation 14 #"rtl/*.v" 当前工作目录下的rtl文件夹中的所有.v文件,支持相对路径,但是要加双引号“” 15 #vlog 16 17 vlog -work work -vlog01compat -incr "../testbench/prim_sim.v" 18 vlog -work work -vlog01compat -incr "../testbench/tb_top.v" 19 20 vlog -work work -vlog01compat -incr "../rtl/histogram_2d.v" 21 vlog -work work -vlog01compat -incr "../rtl/*.v" 22 #vlog -work work -vlog01compat -incr "../rtl/uart_master_src/*.v" 23 24 25 #编译所有.vhd文件 26 #-work <path> Specify library WORK 27 #-93 Enable support for VHDL 1076-1993 28 #-2002 Enable support for VHDL 1076-2002 29 #vcom 30 31 #启动仿真顶层文件 32 #-L <libname> Search library for design units instantiated from Verilog and for VHDL default component binding 33 #+nowarn<CODE | Number> Disable specified warning message (Example: +nowarnTFMPC) 34 #-t [1|10|100]fs|ps|ns|us|ms|sec Time resolution limit VHDL default: resolution setting from .ini file) 35 # (Verilog default: minimum time_precision in the design) 36 #-novopt Force incremental mode (pre-6.0 behavior) 37 38 vsim +nowarnTFMPC -L work -novopt -l tb_top.log work.tb_top 39 40 #产生一个wave log format(WLF)...... 41 log -r /* 42 43 #打开wave窗口 44 view wave 45 46 #添加仿真信号 47 #在已经添加好信号和设置好格式的wave窗口,点击【File】->【Save Fomat】 48 #存为任意名字的.do文件,该文件包含了加载哪些信号及其显示格式的命令 49 do tb_top_wave.do 50 51 #设置运行时间 52 run -all 53 54 #dataflow调试 55 #具体方法是在仿真后执行命令 view dataflow 就可以打开dataflow文件, 56 #在dataflow的窗口菜单中点击add中的view all nets就可以观察到各个模块之间的逻辑联系, 57 #模块一般都为initial模块、always模块、assign模块等等。点击中一个模块,则这个模块变为红色。 58 #这时候在view菜单下点击show wave就可以在窗口下方弹出wave窗口, 59 #不同的是这个wave窗口所显示的信号变量仅为点击中的模块所包括的信号变量, 60 #这时候也可以点击仿真run –all小图标来仿真有关这个模块的输入输出关系。 61 #view dataflow
三,总结
本文通过实践得出,不同于下面的博客。书中的设计思路及网上资料很有帮助,但具体细节实现上会碰到问题。故多尝试自己动手编写代码实现,多借鉴别人的算法框架和思路。
参考博客:
https://www.cnblogs.com/ninghechuan/p/8305925.html
(Modelsim独立仿真Vivado Clocking Wizard IP Core)
https://cloud.tencent.com/developer/article/1529571