Quick Start
- 下载并安装 Vivado 2024.2(或更高版本),确保包含 Vitis 和 ModelSim/QuestaSim 仿真支持。
- 新建 Vivado 工程,选择器件 xc7z020clg484-1(Zynq-7020 开发板典型型号)。
- 创建顶层模块 top_edge_detection,例化 Sobel 和 Canny 两个子模块。
- 编写 Testbench,输入 640×480 灰度图像(8-bit 像素,BMP 格式转为 COE 文件),时钟 25 MHz(VGA 像素时钟)。
- 运行行为仿真,观察 Sobel 输出(单比特边缘标志)和 Canny 输出(双阈值边缘标志)。
- 综合后查看资源利用率(LUT/FF/DSP/BRAM)和最大工作频率(Fmax)。
- 上板验证:通过 VGA 或 HDMI 输出边缘图像,对比 Sobel 与 Canny 的噪声抑制效果。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Zynq-7020 (xc7z020clg484-1) | 主流 SoC FPGA,含 ARM Cortex-A9 双核 | Artix-7 (xc7a35t) 或 Spartan-7 |
| EDA 版本 | Vivado 2024.2 | 支持 SystemVerilog、IP Integrator、HLS 2024.2 | Vivado 2023.1 或 2025.1 |
| 仿真器 | QuestaSim 2024.2 (Vivado 内置) | 支持 VHDL/Verilog 混合仿真,速度快 | Vivado Simulator (xsim) 或 ModelSim |
| 时钟/复位 | 25 MHz 像素时钟,异步复位低有效 | 640×480@60Hz 标准 VGA 时钟 | 50 MHz 或 100 MHz(需降频或 FIFO 缓存) |
| 接口依赖 | VGA (RGB444) 或 HDMI (TMDS) | 输出边缘图像到显示器 | UART 或以太网(传输像素数据) |
| 约束文件 | top.xdc | 时钟周期 40 ns (25 MHz),IO 标准 LVCMOS33 | 自动推导(不推荐) |
目标与验收标准
- 功能点:Sobel 模块输出单比特边缘标志(1 表示边缘),Canny 模块输出双阈值边缘标志(0=非边缘,1=弱边缘,2=强边缘)。
- 性能指标:Sobel 延迟 ≤ 3 行(含行缓存),Canny 延迟 ≤ 5 行(含高斯滤波、梯度计算、NMS、双阈值)。
- 资源上限:Sobel 使用 LUT ≤ 800,FF ≤ 600,BRAM ≤ 2 个(18K);Canny 使用 LUT ≤ 2500,FF ≤ 1800,BRAM ≤ 6 个。
- Fmax:≥ 100 MHz(实际像素时钟 25 MHz,留裕量)。
- 验收方式:仿真波形中 Sobel 输出与 MATLAB 参考结果逐像素匹配(误差 < 5%);上板后 VGA 显示边缘清晰,无断裂或噪声。
实施步骤
1. 工程结构与顶层模块
// top_edge_detection.sv
module top_edge_detection (
input logic clk_25m, // 像素时钟 25 MHz
input logic rst_n, // 异步复位,低有效
input logic [7:0] pixel_in, // 灰度像素输入(0-255)
input logic vsync, // 场同步
input logic hsync, // 行同步
input logic de, // 数据使能
output logic [7:0] pixel_out_sobel, // Sobel 边缘输出(0或255)
output logic [7:0] pixel_out_canny, // Canny 边缘输出(0/128/255)
output logic vsync_out,
output logic hsync_out,
output logic de_out
);
// 实例化 Sobel 模块
logic [7:0] sobel_edge;
sobel_edge_det u_sobel (
.clk (clk_25m),
.rst_n (rst_n),
.pixel_in (pixel_in),
.de (de),
.edge_out (sobel_edge)
);
// 实例化 Canny 模块
logic [1:0] canny_edge; // 0=非边缘, 1=弱边缘, 2=强边缘
canny_edge_det u_canny (
.clk (clk_25m),
.rst_n (rst_n),
.pixel_in (pixel_in),
.de (de),
.edge_out (canny_edge)
);
// 输出映射:Sobel 单比特转 8-bit;Canny 双阈值转 8-bit
assign pixel_out_sobel = sobel_edge ? 8'd255 : 8'd0;
assign pixel_out_canny = (canny_edge == 2'd2) ? 8'd255 :
(canny_edge == 2'd1) ? 8'd128 : 8'd0;
// 同步延迟输出控制信号(延迟与像素处理流水线匹配)
delay_line #(.STAGES(5)) u_delay_vsync (.clk(clk_25m), .din(vsync), .dout(vsync_out));
delay_line #(.STAGES(5)) u_delay_hsync (.clk(clk_25m), .din(hsync), .dout(hsync_out));
delay_line #(.STAGES(5)) u_delay_de (.clk(clk_25m), .din(de), .dout(de_out));
endmodule逐行说明
- 第 1 行:模块声明,SystemVerilog 语法,端口列表。
- 第 2-3 行:时钟和复位。clk_25m 是 25 MHz 像素时钟;rst_n 是异步复位,低电平有效,用于初始化所有寄存器。
- 第 4 行:pixel_in 是 8-bit 灰度像素值(0-255),来自摄像头或测试图像。
- 第 5-7 行:视频同步信号 vsync、hsync、de(数据使能),用于 VGA/HDMI 时序控制。
- 第 8-10 行:Sobel 和 Canny 输出,pixel_out_sobel 是 8-bit(0 或 255),pixel_out_canny 是 8-bit(0、128 或 255)。
- 第 11-13 行:输出同步信号,经延迟后输出,确保与像素数据对齐。
- 第 15-20 行:实例化 sobel_edge_det 模块。输入时钟、复位、像素、数据使能;输出边缘标志。
- 第 22-28 行:实例化 canny_edge_det 模块。canny_edge 是 2-bit 信号(0/1/2),分别表示非边缘、弱边缘、强边缘。
- 第 30-32 行:将 Sobel 的单比特 edge_out 转换为 8-bit 像素值(0 或 255)。
- 第 33-35 行:将 Canny 的 2-bit 信号映射为 8-bit 像素:强边缘=255,弱边缘=128,非边缘=0。
- 第 37-40 行:例化延迟线模块 delay_line,将控制信号延迟 5 个时钟周期,与 Sobel/Canny 的流水线延迟匹配。delay_line 内部用移位寄存器实现。
2. Sobel 边缘检测模块
// sobel_edge_det.sv
module sobel_edge_det (
input logic clk,
input logic rst_n,
input logic [7:0] pixel_in,
input logic de,
output logic edge_out
);
// 行缓存:3 行,每行 640 像素(8-bit)
logic [7:0] line_buf [0:2][0:639];
logic [7:0] p00, p01, p02, p10, p11, p12, p20, p21, p22;
logic [10:0] col_cnt;
logic [10:0] row_cnt;
// 写指针控制
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
col_cnt <= 0;
row_cnt <= 0;
end else if (de) begin
if (col_cnt == 639) begin
col_cnt <= 0;
row_cnt <= row_cnt + 1;
end else begin
col_cnt <= col_cnt + 1;
end
// 写入行缓存
line_buf[0][col_cnt] <= pixel_in;
line_buf[1][col_cnt] <= line_buf[0][col_cnt];
line_buf[2][col_cnt] <= line_buf[1][col_cnt];
end
end
// 3x3 窗口读取
always_comb begin
p00 = line_buf[0][col_cnt]; p01 = line_buf[0][col_cnt+1]; p02 = line_buf[0][col_cnt+2];
p10 = line_buf[1][col_cnt]; p11 = line_buf[1][col_cnt+1]; p12 = line_buf[1][col_cnt+2];
p20 = line_buf[2][col_cnt]; p21 = line_buf[2][col_cnt+1]; p22 = line_buf[2][col_cnt+2];
end
// 梯度计算:Gx 和 Gy
logic [10:0] gx, gy;
always_comb begin
gx = (p02 + 2*p12 + p22) - (p00 + 2*p10 + p20);
gy = (p00 + 2*p01 + p02) - (p20 + 2*p21 + p22);
end
// 幅度近似:|Gx| + |Gy|
logic [10:0] mag;
always_comb begin
mag = (gx[10] ? (~gx + 1) : gx) + (gy[10] ? (~gy + 1) : gy);
end
// 阈值比较(阈值=128)
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
edge_out <= 1'b0;
else if (de && (col_cnt >= 2) && (row_cnt >= 2))
edge_out <= (mag > 10'd128) ? 1'b1 : 1'b0;
else
edge_out <= 1'b0;
end
endmodule逐行说明
- 第 1-7 行:模块声明,输入像素和 de,输出单比特 edge_out。
- 第 9 行:line_buf 是 3 行 × 640 像素的二维数组,用 BRAM 实现。每个元素 8-bit。
- 第 10 行:p00~p22 是 3×3 窗口的 9 个像素值。
- 第 11-12 行:col_cnt 和 row_cnt 用于追踪当前像素坐标(0-639 列,0-479 行)。
- 第 14-28 行:写指针控制。在 de 有效时递增列计数器,每行结束递增行计数器。同时将 pixel_in 写入 line_buf[0],并将 line_buf[0] 移位到 line_buf[1] 和 line_buf[2]——实现行延迟。
- 第 30-33 行:组合逻辑读取 3×3 窗口。注意 col_cnt 是当前写入列,窗口中心是 (col_cnt+1, row_cnt+1)。
- 第 35-39 行:计算 Gx 和 Gy。使用 11-bit 有符号数(10:0)避免溢出。Gx = (右列 - 左列) 的 Sobel 卷积核;Gy = (下行 - 上行)。
- 第 41-44 行:幅度近似:|Gx| + |Gy|。gx[10] 是符号位,若为 1 则取补码(绝对值)。
- 第 46-53 行:阈值比较。当 de 有效且列/行计数大于等于 2(即有效窗口形成后),若 mag > 128 则输出 1,否则 0。复位时输出 0。
3. Canny 边缘检测模块(简化版)
// canny_edge_det.sv
module canny_edge_det (
input logic clk,
input logic rst_n,
input logic [7:0] pixel_in,
input logic de,
output logic [1:0] edge_out
);
// 阶段1:高斯滤波(3x3 均值近似)
logic [7:0] gauss_out;
gaussian_filter u_gauss (
.clk(clk), .rst_n(rst_n), .pixel_in(pixel_in), .de(de), .pixel_out(gauss_out)
);
// 阶段2:Sobel 梯度计算(复用 Sobel 模块,但输出幅度和方向)
logic [10:0] mag; // 梯度幅度
logic [2:0] orient; // 方向量化(0-7,对应 0°-315°)
sobel_gradient u_grad (
.clk(clk), .rst_n(rst_n), .pixel_in(gauss_out), .de(de),
.mag(mag), .orient(orient)
);
// 阶段3:非极大值抑制(NMS)
logic [10:0] nms_mag;
nms_3x3 u_nms (
.clk(clk), .rst_n(rst_n), .mag(mag), .orient(orient), .de(de),
.nms_out(nms_mag)
);
// 阶段4:双阈值检测
logic [1:0] edge_raw;
dual_threshold #(.TH_HIGH(180), .TH_LOW(60)) u_thresh (
.clk(clk), .rst_n(rst_n), .mag_in(nms_mag), .de(de),
.edge_out(edge_raw)
);
// 阶段5:边缘跟踪(滞后阈值,简化版:仅保留强边缘及与强边缘相连的弱边缘)
edge_tracking u_track (
.clk(clk), .rst_n(rst_n), .edge_in(edge_raw), .de(de),
.edge_out(edge_out)
);
endmodule逐行说明
- 第 1-7 行:模块声明,输出 edge_out 为 2-bit(0/1/2)。
- 第 9-12 行:阶段1——高斯滤波。使用 3×3 均值核(1/9 归一化),减少噪声。例化 gaussian_filter 模块,该模块内部用行缓存和加法树实现。
- 第 14-19 行:阶段2——Sobel 梯度计算。例化 sobel_gradient 模块,输出幅度 mag(11-bit)和方向 orient(3-bit,量化到 8 个方向)。
- 第 21-25 行:阶段3——非极大值抑制。例化 nms_3x3 模块,在 3×3 邻域内沿梯度方向比较,只保留局部极大值。
- 第 27-31 行:阶段4——双阈值检测。TH_HIGH=180,TH_LOW=60。若 nms_mag > 180 则为强边缘(2),若在 60-180 之间则为弱边缘(1),否则为非边缘(0)。
- 第 33-37 行:阶段5——边缘跟踪。例化 edge_tracking 模块,检查弱边缘是否与强边缘 8 邻域相连,若相连则提升为强边缘,否则抑制。简化版仅做一次连通性判断。
4. 时序与约束
# top.xdc
# 时钟约束
create_clock -name clk_25m -period 40.000 [get_ports clk_25m]
# 输入延迟约束(假设外部器件输出延迟 2 ns)
set_input_delay -clock clk_25m -max 2.000 [get_ports pixel_in]
set_input_delay -clock clk_25m -min 1.000 [get_ports pixel_in]
# 输出延迟约束(VGA 建立时间 1.5 ns)
set_output_delay -clock clk_25m -max 1.500 [get_ports pixel_out_sobel]
set_output_delay -clock clk_25m -max 1.500 [get_ports pixel_out_canny]
# 异步复位约束(false path)
set_false_path -from [get_ports rst_n]逐行说明
- 第 2 行:创建 25 MHz 时钟,周期 40 ns。这是所有时序分析的基准。
- 第 4-5 行:输入延迟约束。pixel_in 来自外部 ADC 或摄像头,假设最大延迟 2 ns,最小 1 ns。确保 Vivado 正确分析输入建立/保持时间。
- 第 7-8 行:输出延迟约束。VGA 接收器要求数据在时钟上升沿前 1.5 ns 稳定。
- 第 10 行:将异步复位端口设为 false path,避免 Vivado 分析其到寄存器的时序(因为复位是异步的,且通常不要求时序收敛)。
5. 验证与仿真脚本
# run_sim.tcl
# 启动仿真并添加波形
restart
add wave -position end sim:/top_edge_detection/*
add wave -position end sim:/top_edge_detection/u_sobel/*
add wave -position end sim:/top_edge_detection/u_canny/*
# 加载测试图像(COE 文件)
mem load -infile {test_image.coe} -format hex /top_edge_detection/u_testbench/image_mem
# 运行 800 行(640*480 + 消隐区)
run 800000 ns逐行说明
- 第 2 行:重启仿真,清除之前的状态。
- 第 3-5 行:添加顶层和子模块的所有信号到波形窗口,便于观察。
- 第 7 行:将 test_image.coe 文件加载到 Testbench 中的图像存储器(image_mem)。COE 文件格式:每行一个 8-bit 十六进制像素值。
- 第 9 行:运行仿真 800 μs,对应约 800 行像素(每行 640 像素 + 消隐区 ≈ 800 个时钟周期)。
常见坑与排查
- 行缓存溢出:如果 de 信号不连续(如消隐期),行缓存写入指针可能错位。确保 de 只在有效像素区域为高。
- 窗口初始化延迟:Sobel 和 Canny 都需要前 2 行像素填充行缓存,因此前 2 行输出为 0。检查 col_cnt 和 row_cnt 的起始条件。
- 阈值选择不当:Sobel 阈值 128 可能对低对比度图像漏检;Canny 的 TH_HIGH/TH_LOW 比例建议 2:1 到 3:1。通过仿真调整。
- 时序违例:如果 Fmax 低于 100 MHz,检查关键路径(通常是加法树或比较器)。可插入流水线寄存器。
原理与设计说明
Sobel 与 Canny 的核心差异:Sobel 是简单的梯度阈值法,计算快、资源少,但对噪声敏感,边缘较粗(单像素宽但可能断裂);Canny 是多阶段算法(高斯滤波 → 梯度 → NMS → 双阈值 → 边缘跟踪),边缘更细、连续性好,但资源消耗约 3-5 倍,延迟增加 2 行。




