连续学习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. 关于spring data redis repository @RedisHash注解的对象上有DateTime属性字段的问题

    当你save保存的时候你会发现出现StackOverflow Exception,很明显出现了无限循环,可是仅仅是一个save操作,哪里来的无限循环呢? 最终发现就是DateTime导致的,因为将对象 ...

  2. Solution -「UOJ #46」玄学

    \(\mathcal{Description}\)   Link.   给定序列 \(\{a_n\}\) 和 \(q\) 次操作,操作内容如下: 给出 \(l,r,k,b\),声明一个修改方案,表示 ...

  3. 使用动态时间规整 (DTW) 解决时间序列相似性度量及河流上下游污染浓度相似性识别分析

    时间序列相似性度量方法 时间序列相似性度量常用方法为欧氏距离ED(Euclidean distance)和动态时间规整DTW(Dynamic Time Warping).总体被分为两类: 锁步度量(l ...

  4. Linux命令行模式下安装VMware Tools详细步骤

    在Linux命令行模式安装VMware Tools 方法/步骤1: 首先启动CentOS 7,在VMware中点击上方"VM",点击"Install VMware Too ...

  5. 关于 Xcode 更新 appleID 更换

    可能不少人会遇到 前一位同事走之后,他的 appID帐号下载的东西更新不了 下面给予大家一个解决办法  例如 Xcode  1.打开引用程序目录 2.找到Xcode,右键"显示包内容&quo ...

  6. 微服务从代码到k8s部署应有尽有系列(七、支付服务)

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

  7. Nginx频繁报“500 Internal Server Error”错误

    服务器导致访问量激增,频繁报"500 Internal Server Error"错误.我查了一下nginx的错误日志(apt-get方式安装的nginx的错误日志在/var/lo ...

  8. 渗透利器burp suite新版本v2020.9.1及v2020.8汉化版下载

    Burp suite是一款抓包渗透必备软件.burp Suite是响当当的web应用程序渗透测试集成平台.从应用程序攻击表面的最初映射和分析,到寻找和利用安全漏洞等过程,所有工具为支持整体测试程序而无 ...

  9. 在Linux发行版上使用7zip的方法

    学习如何在 Ubuntu 和其他 Linux 发行版中安装和使用 7zip 7zip介绍 7Zip(更适当的写法是 7-Zip)是一种在 Windows 用户中广泛流行的归档格式.一个 7Zip 归档 ...

  10. C# 使用NPOI处理Excel模板-【前面部分固定,中间是动态的几行,尾部是固定的部分】

    今天同组的兄弟问我,他有一个导出的模板,大概如下: [前面部分固定,中间是动态的几行,尾部是固定的部分].其实这个很像单链表往单链表在指定插入数据. 他问我怎么做才好,他想到的做法是:因为这些动态列的 ...