连续学习FPGA基础课程接近一个月了,迎来第一个有难度的综合设计,图像的边沿检测算法sobel,用verilog代码实现算法功能。
一设计功能
(一设计要求)
(二系统框图)
根据上面的系统,Verilog代码如下:注意的是,VGA模块的时钟输入有两个,一是50M,二是25M。PLL的IP核的输入时钟连接顶层时钟,产生的输出时钟连接各个功能模块,有两个一是50M,二是25M。50M连接串口接收,sobel_ctrl控制模块。25M连接VGA_ram的vga显示部分和RAM的读地址的时钟,50M连接VGA_ram的RAM的写地址时钟。
module top_sobel( output wire hsync, wire clk_25out; wire uart_flag; wire sobel_flag; uart_rx uart_rx_m0( sobel_ctrl inst_sobel_ctrl ( vga_ram inst_vga_ram (
endmodule |
二设计思路
在原有基础上,自己动手设计,仿真验证,调试直到成功。
(一)我自己觉得这次的功能和上次的双FIFO流水线很紧密,所以这次,应该是先把双FIFO的逻辑弄懂,即把他们的时序图自己大致画一下,然后我觉得关键是怎么采集三行三列的9个数,我的想法是,比如第零行用FIFO1存储,然后再加三个寄存器,打拍操作。每来一个串口接收标志信号就送FIFO1的数据到如寄存器1中,然后是寄存器2,再是寄存器3。这样不断循环,就可以采集九个数据,最后再按照步骤进行相应的加法和乘法,绝对值操作。
刚开始要循序渐进,可以弄20X20的数据,而且只是实现到DX这一步,等完成了再往下继续加功能。
(二)设计知识点
1.打拍操作:同步复位,没有复位信号
第二点,我的FIFO1和FIFO2数据是在rd_en读使能信号控制下进行寄存器的打拍操作,而对这就个数进行运算则是在add_flag控制下,赋值输出(原因是rd_en 提前了add_flag一个时钟周期)
尤老师,讲为啥有wr_en_pre1和wr_en_pre12.主要是让FIFO的写使能信号和读使能信号同步。
sobel算法实现过程介绍
第三点,仔细看了几遍sobel算法的介绍,明白只需要采集到9个点,然后再按照步骤实现功能即可。
三所遇问题及解决办法
由于这次编写的综合设计模块,众多,而且代码量有上千行,我先暂时不详解每个模块的设计代码,而是我在亲自动手设计图像边沿检测的收获。
问题一:我直接在v3的源码中修改,想直接仿真运行,却发现modelsim报错:显示路径错误
* Error: (vopt-1933) Unable to create temporary directory D:/netclass/firstlevel/net19_double_fifo/double_fifo/work/_tempmsg # No such file or directory. (errno = ENOENT) # Error loading design |
原因:一是可能路径太长,二是原工程的路径和当前不符合。所以要么把工程直接放在更目录或者自己重新建立一个。
我的解决办法,自己重新建立工程,如IP核等,或者路径更改,可以移除工程在添加进去。
问题二:仿真时找不到文本,提示如下
** Warning: (vsim-7) Failed to open readmem file "./data.txt" in read mode. # No such file or directory. (errno = ENOENT) : sim/tb_top_uart.v(45) # Time: 0 ps Iteration: 0 Instance: /tb_top_uart 下面是错误目录下
|
解决办法:我是问了尤老师才知道应该放在modelsim的目录下即和ISE工程同一目录下
问题三:怎么对86X4的数据进行仿真
答案是并转串。即本来是86X4的矩阵,但我可以先在TXT文档中用344X1的数据替代,直接在仿真中把86改成344即可。
`timescale 1ns / 1ps module tb_top_uart;
// Inputs reg sclk; reg rst_n; reg rx;
reg [7:0] mem[343:0];
// Outputs wire tx;
// Instantiate the Unit Under Test (UUT) top_dfifo uut ( .clk(sclk), .rst_n(rst_n), .rx(rx), .tx(tx) );
initial begin // Initialize Inputs sclk = 0; rst_n = 0; rx = 1;
// Wait 100 ns for global reset to finish #100; rst_n =1; // Add stimulus here
end
initial begin $readmemb("./data.txt",mem); end
always #10 sclk = ~sclk;
initial begin #200; rx_byte(); end
task rx_byte(); integer i; integer j; begin for(j=0;j<344;j=j+1)begin for (i=0;i<344;i=i+1)begin rx_bit(mem[i]); end end end endtask
task rx_bit(input [7:0] data); integer i; begin for(i=0;i<10;i=i+1) begin case (i) 0:rx =0; 1:rx =data[i-1]; 2:rx =data[i-1]; 3:rx =data[i-1]; 4:rx =data[i-1]; 5:rx =data[i-1]; 6:rx =data[i-1]; 7:rx =data[i-1]; 8:rx =data[i-1]; 9:rx =1; endcase #104160; end end endtask endmodule |
问题三,怎么进行绝对值的计算
方法:先自己网上搜了下,大概是利用原码反码,补码的关系。即最高位为符号位,1表示负数,绝对值为取反加一。0表示正数,绝对值等于本身。
//abs_dx reg [7:0]abs_dx; reg [7:0]abs_dy; always@(posedge clk or negedge rst_n) if(!rst_n) abs_dx<=0; else if(flag_abs & dx[7]==1) abs_dx<=~dx+1; else if(flag_abs & dx[7]==0) abs_dx<=dx;
|
问题四,怎么写VGA控制程序,在里面调用一个RAM。用来存储198X198个数据,VGA模块负责RAM的读写,让RAM里写入sobel_ctrl模块处理好数据,读出来的数据需要给rgb进行显示。
我的想法是:要做一个新东西,就首先学会联系已学过的东西(基础),既然用RAM读写这198X198数据,那么首先得搞明白RAM。再自己适当修改一下读写逻辑,如数据的读写地址等等就欧克。
我选择的RAM类型为:Simple Dual Port RAM,该ram包含两个地址总线,一个写地址和一个读地址,分别控制两个地址总线可以控制该ram的读和写。还有一个关键信号:wr_en,控制读写逻辑。本RAM位宽为8深度为256的ram. 下面的代码是根据上面的RAM的读写时序和设定的位宽深度设计的:
而这次的写使能信号是由 sobel_ctrl的pi_flag控制的,还有一个关键点是写地址是50M的系统时钟,而读地址是25M的时钟。所以需要在顶层加一个PLL输出两个时钟,一个是50M,一个是25M,PLL的输入时钟接顶层时钟50M,然后把PLL输出的时钟分别连在串口模块,sobel模块,VGA模块。
|
关键点:在VGA里调用一个40K的ram存储198X198的数据,怎么设计写使能信号和读写地址值得注意。先将它的代码展示如下
module vga_ram( reg [15:0] addrb,addra; parameter MAX_value = 16'd39203; reg [8:0]x; //行移动计数器最大439 reg dec_x;//行计数器减一切换标志信号 reg [9:0]cnt_h; parameter h_max =10'd799; //行计数器 //vsync场同步信号
//the flag of one_s_flag
always@(posedge clk or negedge rst_n)
always @(posedge clks or negedge rst_n) begin
|
问题五,怎么产生200X200的数据,即把一个200X200像素的图片转换为200X200的数据阵列?
以前的经验是,直接弄一个txt文本储存200X200的数据,不过是并转串,而且是用在仿真中。 尤老师的经验是,用MATLAB处理,即给一个200X200的像素图片,用MATLAB的相应语句转换产生一个200X200的数据阵列,再复制到友善串口助手发送,之后发现显示器还是黑色的,他推测可能是阈值过大。
我觉得遇到这种完全新的,还是先记录问题,再看哈视频,然后自己动手做。 在图像边沿检测的视频二50分钟,我看到了建完所有的模块。 下面是MATLAB的图片转阵列的代码,第一行是读取图片(直接把图片 粘贴在当前文件夹下,再v3edu修改成图片相应的名字)第二行是转换的图片的阵列大小,这个是根据MATLAB相应的工作路径下显示可以转换的范围,而不是胡乱搞的。
操作示意图如上
解决办法:即出现多沿触发时,要同一个信号如CLK都是上升沿,或rst_n都为下降沿有效。
|
问题五。怎么debug?
首先在顶层,看看各个模块的连接有没有错。第二步,检查控制模块sobel_ctrl的各个模块和信号的逻辑有没有错。第三步,逻辑分析仪:在ise14.7中建立了ICON和ILA这两个IP核(弄懂这)
问题六:分成两部分一是没有图像显示,原来时没有管脚约束文件。。
第二个问题是,RAM的数据没有读出来,即移动的方框一直显示黑色,即RAM输出的po_data一直为零。(rgb信号全1为白,全零为黑)
故解决办法,明天好好改哈RAM的读写逻辑。
通过综合器的警告,我发现,在顶层模块中,每个功能模块没有和顶层的时钟信号连接,即只连接的自己模块的,这没有时钟驱动源。我用pll模块产生了两个时钟输出:一个50M和25M,然后对应连接各个模块就欧克勒。
|
最终结论确实是RAM模块的读写地址时钟不一样,还有就是顶层模块,除了PLL的输入时钟连接系统时钟,其他模块的时钟信号都是连接的PLL的输出时钟,50M 或25M。
最终显示效果如下,在看到图像那一刻很开心,比较自己亲自调了一周程序,还好没放弃:
皮卡丘原图:
经过sobel算法处理后的图片:
知识点:一是怎么采集3X3的9个数,直接用2个FIFO,在每个FIFO用三个寄存器,在标志信号的控制下进行打拍 操作。二是怎么把一个图片转换为一个如200X200的矩阵数列,直接用MATLAB转换即可。
我的收获是:一是做一个新东西,在原有基础上想办法。二是,要拆分设计验证,不能一把搞完所有模块,直接去上板验证。