连续学习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(
input s_clk,
input wire rst_n,
input wire rx,

output wire hsync,
output wire vsync,
output wire [7:0] rgb
);

wire clk_25out;
wire clk_50M;
gen_clk25M gen_clk_ins
(// Clock in ports
.CLK(s_clk), // IN
// Clock out ports
.CLK_25MOUT1(clk_25out), // OUT
.CLK_50M(clk_50M)); // OUT

wire uart_flag;
wire [7:0]uart_data;

wire sobel_flag;
wire [7:0]sobel_data;

uart_rx uart_rx_m0(
.sclk(clk_50M),
.rst_n(rst_n),
.rx(rx),
.po_data(uart_data),
.po_flag(uart_flag)
);

sobel_ctrl inst_sobel_ctrl (
.clk (clk_50M),
.rst_n (rst_n),
.pi_flag (uart_flag),
.pi_data (uart_data),
.po_flag (sobel_flag),
.po_sum (sobel_data)
);

vga_ram inst_vga_ram (
.clk (clk_25out),
.clks (clk_50M),
.rst_n (rst_n),
.pi_flag (sobel_flag),
.pi_data (sobel_data),
.hsync (hsync),
.vsync (vsync),
.vga_rgb (rgb)
);

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的读写时序和设定的位宽深度设计的:

module ctrl_ram(

input      wire                    clk,

input      wire                    rst_n,

input      wire      [7:0]       pi_data,

output   wire      [7:0]       po_data

);

reg               wr_en;

reg [7:0]       wr_addr;

reg [7:0]       rd_addr;

always @(posedge clk or negedge rst_n) begin

if (rst_n == 1'b0) begin

wr_en <= 1'b1;

end

else if(rd_addr == 'd255) begin

wr_en <= 1'b1;

end

else if (wr_addr == 'd255) begin

wr_en <= 1'b0;

end

end

always @(posedge clk or negedge rst_n) begin

if (rst_n == 1'b0) begin

wr_addr <= 'd0;

end

else if (wr_en == 1'b1) begin

wr_addr <= wr_addr + 1'b1;

end

else begin

wr_addr <= 'd0;

end

end

always @(posedge clk or negedge rst_n) begin

if (rst_n == 1'b0) begin

rd_addr <= 'd0;

end

else if (wr_en == 1'b0) begin

rd_addr <= rd_addr + 1'b1;

end

else begin

rd_addr <= 'd0;

end

end

ram_256x8 ram_256x8_inst (

.clka(clk), // input clka

// .wea(wr_en), // input [0 : 0] wea

.wea(1'b0),

.addra(wr_addr), // input [7 : 0] addra

.dina(pi_data), // input [7 : 0] dina

.clkb(clk), // input clkb

.addrb(rd_addr), // input [7 : 0] addrb

.doutb(po_data) // output [7 : 0] doutb

);

endmodule

而这次的写使能信号是由 sobel_ctrl的pi_flag控制的,还有一个关键点是写地址是50M的系统时钟,而读地址是25M的时钟。所以需要在顶层加一个PLL输出两个时钟,一个是50M,一个是25M,PLL的输入时钟接顶层时钟50M,然后把PLL输出的时钟分别连在串口模块,sobel模块,VGA模块。

关键点:在VGA里调用一个40K的ram存储198X198的数据,怎么设计写使能信号和读写地址值得注意。先将它的代码展示如下

module vga_ram(
input wire clk,
input wire clks,
input wire rst_n,
input wire pi_flag,
input wire [7:0] pi_data,
output reg hsync,
output reg vsync,
output reg [7:0] vga_rgb
);

reg [15:0] addrb,addra;
wire [7:0] doutb;

parameter MAX_value = 16'd39203;

reg [8:0]x; //行移动计数器最大439
reg [8:0]y; //场移动计数器最大279

reg dec_x;//行计数器减一切换标志信号
reg dec_y;//场计数器减一切换标志信号

reg [9:0]cnt_h;
reg [9:0]cnt_v;

parameter h_max =10'd799;
parameter v_max = 10'd524;

//行计数器
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_h<=10'd0;
else if(cnt_h==h_max)
cnt_h<=10'd0;
else
cnt_h<=cnt_h+1'b1;
//场计数器
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_v<=10'd0;
else if(cnt_v==v_max & cnt_h==h_max)
cnt_v<=10'd0;
else if(cnt_h==h_max)
cnt_v<=cnt_v+1'b1;
//hsync 行同步信号
always@(posedge clk or negedge rst_n)
if(!rst_n)
hsync<=1'b1;
else if(cnt_h==10'd95)
hsync<=1'b0;
else if(cnt_h==h_max)
hsync<=1'b1;

//vsync场同步信号
always@(posedge clk or negedge rst_n)
if(!rst_n)
vsync<=1'b1;
else if(cnt_v=='d1 & cnt_h==h_max)
vsync<=1'b0;
else if(cnt_v==v_max & cnt_h==h_max)
vsync<=1'b1;

parameter T100MS = 23'd2_599_999;
//div counter
reg [22:0]div_cnt;
always@(posedge clk or negedge rst_n)begin
if(rst_n==0)
div_cnt<=23'd0;
else if(div_cnt==T100MS)
div_cnt<=23'd0;
else
div_cnt<=div_cnt+1'b1;
end

//the flag of one_s_flag
reg one_s_flag;
always@(posedge clk or negedge rst_n)begin
if(rst_n==0)
one_s_flag<=1'b0;
else if(div_cnt==(T100MS-1))begin
one_s_flag<=1'b1;
end
else begin
one_s_flag<=1'b0;
end
end
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
x<=9'd0;
dec_x<=1'b0;
end
else begin
case(dec_x)
0:begin
if(x==9'd439)begin
x<=x;
dec_x<=1'b1;
end
else if(one_s_flag) begin
x<=x+1'b1;
dec_x<=1'b0;
end
end
1:begin
if(x==9'd0)begin
x<=x;
dec_x<=1'b0;
end
else if(one_s_flag) begin
x<=x-1'b1;
dec_x<=1'b1;
end
end
default: ;
endcase
end

always@(posedge clk or negedge rst_n)
if(!rst_n)begin
y<=9'd0;
dec_y<=1'b0;
end
else begin
case(dec_y)
0:begin
if(y==9'd279)begin
y<=y;
dec_y<=1'b1;
end
else if(one_s_flag)begin
y<=y+1'b1;
dec_y<=1'b0;
end
end
1:begin
if(y==9'd0)begin
y<=y;
dec_y<=1'b0;
end
else if(one_s_flag)begin
y<=y-1'b1;
dec_y<=1'b1;
end
end
default: ;
endcase
end

always@(posedge clk or negedge rst_n)
if(!rst_n)
vga_rgb<=8'b0;
else if(cnt_h>10'd144+x &cnt_h<=10'd343+x & cnt_v>10'd35+y & cnt_v<=10'd234+y)
vga_rgb<=doutb;
else if(cnt_h>10'd144 &cnt_h<=10'd783 & cnt_v>10'd35 & cnt_v<=10'd194)
vga_rgb<=8'b111_000_00;
else if(cnt_h>10'd144 &cnt_h<=10'd783 & cnt_v>10'd194 & cnt_v<=10'd354)
vga_rgb<=8'b000_111_00;
else if(cnt_h>10'd144 &cnt_h<=10'd783 & cnt_v>10'd354 & cnt_v<=10'd514)
vga_rgb<=8'b000_000_11;
else
vga_rgb<=8'b0;

always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
// reset
addrb <= 'd0;
end
else if(cnt_h >=143+x && cnt_h <=340+x && cnt_v >=35+y && cnt_v <=232+y && addrb=='d39203)begin
addrb <= 'd0;
end
else if (cnt_h >=143+x && cnt_h <=340+x && cnt_v >=35+y && cnt_v <=232+y) begin
addrb <= addrb + 1'b1;
end
end

always @(posedge clks or negedge rst_n) begin
if (rst_n == 1'b0) begin
// reset
addra <= 'd0;
end
else if (pi_flag == 1'b1 && addra == 'd39203) begin
addra <= 'd0;
end
else if (pi_flag == 1'b1) begin
addra <= addra + 1'b1;
end
end

RAM40K ram_inst(
.clka(clks),
.wea(pi_flag),
.addra(addra),
.dina(pi_data),
.clkb(clk),
.addrb(addrb),
.doutb(doutb)
);
endmodule

问题五,怎么产生200X200的数据,即把一个200X200像素的图片转换为200X200的数据阵列?

以前的经验是,直接弄一个txt文本储存200X200的数据,不过是并转串,而且是用在仿真中。

尤老师的经验是,用MATLAB处理,即给一个200X200的像素图片,用MATLAB的相应语句转换产生一个200X200的数据阵列,再复制到友善串口助手发送,之后发现显示器还是黑色的,他推测可能是阈值过大。

我觉得遇到这种完全新的,还是先记录问题,再看哈视频,然后自己动手做。

在图像边沿检测的视频二50分钟,我看到了建完所有的模块。

下面是MATLAB的图片转阵列的代码,第一行是读取图片(直接把图片 粘贴在当前文件夹下,再v3edu修改成图片相应的名字)第二行是转换的图片的阵列大小,这个是根据MATLAB相应的工作路径下显示可以转换的范围,而不是胡乱搞的。

clc;

clear all;

rgbimage=imread('./v3edu.jpg','jpg');%读取rgb图像

grayimage=rgbimage(1:200,1:200,1);%去其中r分量作为传递图像

grayimage=bitshift(grayimage,-5);%右移5位取R分量的高三位

fid=fopen('imagedata.txt','w+');%打开文件返回句柄

fprintf(fid,'%02x ',grayimage');%将图像转置行列对换(默认matlab

imshow(rgbimage);

操作示意图如上

解决办法:即出现多沿触发时,要同一个信号如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转换即可。

我的收获是:一是做一个新东西,在原有基础上想办法。二是,要拆分设计验证,不能一把搞完所有模块,直接去上板验证。

14FPGA综设之图像边沿检测的sobel算法的更多相关文章

  1. 边沿检测电路设计verilog

    Abstract 边沿检测电路(edge detection circuit)是个常用的基本电路. Introduction 所谓边沿检测就是对前一个clock状态和目前clock状态的比较,如果是由 ...

  2. Atitit 图像清晰度 模糊度 检测 识别 评价算法 源码实现attilax总结

    Atitit 图像清晰度 模糊度 检测 识别 评价算法 源码实现attilax总结 1.1. 原理,主要使用像素模糊后的差别会变小1 1.2. 具体流程1 1.3. 提升性能 可以使用采样法即可..1 ...

  3. Atitit 图像清晰度 模糊度 检测 识别 评价算法 原理

    Atitit 图像清晰度 模糊度 检测 识别 评价算法 原理 1.1. 图像边缘一般都是通过对图像进行梯度运算来实现的1 1.2. Remark: 1 1.3.  1.失焦检测. 衡量画面模糊的主要方 ...

  4. FPGA学习笔记之格雷码、边沿检测、门控时钟

    一.格雷码 格雷码的优点主要是进位时只有一位跳变,误码率低. 1.二进制转格雷码 我们观察下表: 二进制码 格雷码 00 00 01 01 10 11 11 10 二进制码表示为B[],格雷码表示为G ...

  5. YOLT:将YOLO用于卫星图像目标检测

    之前作者用滑动窗口和HOG来进行船体监测,在开放水域和港湾取得了不错的成绩,但是对于不一致的复杂背景,这个方法的性能会下降.为了解决这个缺点,作者使用YOLO作为物体检测的流水线,这个方法相比于HOG ...

  6. 边沿检测方法-FPGA入门教程

    本节实验主要讲解FPGA开发中边沿检测方法,我们在设计中会经常用到.这个地方大家一定要理解. 1.1.1.原理介绍 学习HDL语言设计与其他语言不一样,HDL语言设计需要考虑更多的信号的电气特性,时序 ...

  7. verilog 之数字电路 边沿检测电路

    由代码可知:此边沿检测电路是由两个触发器级联而成,sign_c_r 输出是sign_c_r2的输入.并且有异步复位端没有使能端.最后输出:由触发器的输出取反和直接输出相与.如下的RTL图.

  8. 使用Caffe完成图像目标检测 和 caffe 全卷积网络

    一.[用Python学习Caffe]2. 使用Caffe完成图像目标检测 标签: pythoncaffe深度学习目标检测ssd 2017-06-22 22:08 207人阅读 评论(0) 收藏 举报 ...

  9. FPGA编程技巧系列之按键边沿检测

    抖动的产生: 通常的按键所用开关为机械弹性开关,当机械触点断开.闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开.因而在闭合及断开的瞬间均伴随有一连串的 ...

随机推荐

  1. Large Sacle Distributed Deep Networks

    本文是谷歌发表在NeurIPS 2012上的一篇论文,主要讨论了在几万个CPU节点上训练大规模深度网络的问题,并提出了一个名为DistBelief的软件框架.在该框架下实现了两种大规模分布式训练算法: ...

  2. java-poi 批量导入excel数据

    1,首先,前端发送MultipartFile类型文件,后端接收 2,分别创建多个ImportParams对象(easypoi),对应工作蒲 注意:pom中 要有相对应的配置 <!-- easyp ...

  3. jsp页面获取请求参数问题记录

    同一个请求可以从请求路径中获取参数,使用param.参数名 window.location.href = "admin/page.html?pageNum="+pageNum+&q ...

  4. WEB服务蜜罐部署实验

    实验目的 了解WEB蜜罐的基本原理,掌握Trap Server的使用. 实验原理 Trap Server是一款WEB服务器蜜罐软件,它可以模拟很多不同的服务器,例如Apache. HTTP Serve ...

  5. 2022李宏毅作业hw1—新冠阳性人员数量预测。

    ​ 事前  : kaggle地址:ML2021Spring-hw1 | Kaggle 我的git地址: https://github.com/xiaolilaoli/lihongyi2022homew ...

  6. python运算符优先级及部分运算

    在python里面,有很多运算符,比如:算术运算符.赋值运算符.比较运算符.逻辑运算符.成员运算符.身份运算符和位运算符等.这里主要来看看这些运算符的优先级:从上到下优先级依次递减. 优先顺序 运算符 ...

  7. 微服务从代码到k8s部署应有尽有系列(九、事务精讲)

    我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...

  8. 进程&线程(三):外部子进程subprocess、异步IO、协程、分布式进程

    1.外部子进程subprocess python之subprocess模块详解--小白博客 - 夜风2019 - 博客园 python subprocess模块 - lincappu - 博客园 之前 ...

  9. Vue之路由的使用

    零.传统路由与SPA的区别 传统开发方式下,URL改变后,就会立刻发生请求去请求整个页面,这样可能请求加载的资源过多,可能会让页面出现白屏. 在SPA(Single Page Application) ...

  10. 六、Java方法

    Java方法 何为方法 System.out.println(),那么它是什么呢? ​ System是一个类,out是一个对象,println()是一个方法 Java方法是语句的集合,它们在一起执行的 ...