Jashwanth Bamidi

FPGA & digital Built and demonstrated

FPGA Edge Detection

Live video from a small camera goes into an FPGA and comes out on an HDMI monitor as edge outlines.

Role
Sole designer
When
2026
Tools
SystemVerilog, Vivado, XDC constraints, OV7670, SCCB / I2C, HDMI / TMDS
Camera clockPixel clock, 25 MHz5× serial clockcontrol delayed with dataOV76708-bit busCaptureRGB565Framebufferdual-clockRaster read800 × 525Line buffers3 × 640 pxSobel|Gx| + |Gy|AlignDE, HS, VSHDMITMDSSCCB configACK checkedClocking100 MHz → 25, 125 MHzTest patterns
Line buffers. Three row memories rotate roles each line: one is written while two supply the rows above, and shift registers hold neighbouring columns to form the 3×3 window.
Illustrative. Camera to HDMI, across three clock domains. Select a stage to see what it does. Block diagram of the design, not a resource report. OV7670 capture on the camera clock, a dual-clock framebuffer, raster read, line buffers and Sobel on the pixel clock, alignment, and HDMI serialisation on the 5x clock, with SCCB configuration and clocking as side paths.

What I did

I wrote the whole pipeline in SystemVerilog: camera setup, pixel capture, a dual-clock framebuffer, the Sobel core, HDMI output and the timing constraints.

Test patterns and status LEDs let me prove the display path before the camera worked.

Result

  • Live 640×480 camera-to-HDMI edge detection.
  • An earlier 320×240 capture build used 694 LUTs, 416 registers and 49.5 of 50 block-RAM tiles, and met timing.
  • No measured frame rate or latency; display timing is nominal.
  • The resource figures belong to the earlier build only.

Engineering challenge

Keeping control signals with their pixels

Every pipeline stage delays the pixel, so data-enable and both syncs are delayed by exactly the same amount. Without that, valid pixels land in blanking or edges shift sideways.

Try it

Drag the divider to compare the input with its edges.

InputSobel output

2,484 of 43,200 pixels marked as edges at threshold 40.

How the browser version computes it
Image
An original 240 × 180 geometric scene generated in code. No camera, no hardware.
Grayscale
g = (R + 2G + B) / 4, the add-and-shift approximation the RTL uses.
Kernels
Gx = [−1 0 1; −2 0 2; −1 0 1], Gy = [−1 −2 −1; 0 0 0; 1 2 1].
Magnitude
|Gx| + |Gy|, divided by 8 so the 0–2040 range fits 0–255, then compared with the threshold.
Edges of the frame
The outer one-pixel ring is set to zero, because the 3×3 window is not complete there.
Browser demonstration. Sobel on an original test image. The same arithmetic run in your browser on a generated image; not footage from the FPGA. An input image and its thresholded Sobel output with a movable divider and threshold control.

More detail

Three clock domains

The OV7670 sends one byte per pixel clock, two bytes per RGB565 pixel. The display wants a steady 640×480 raster on its own clock, and HDMI serialises at five times that.

Camera signals are captured on the camera's clock, and each frame crosses to the display through a dual-clock framebuffer.

Line buffers and the Sobel window

Three 640-pixel row memories rotate roles each line: one is written while two supply the rows above. Shift registers hold neighbouring columns to form the 3×3 window.

Edge strength is |Gx| + |Gy|, using shifts for the weight-two taps and an 8-bit threshold.

Bring-up

Synthetic bars and checkerboards proved display timing and the Sobel window first. SCCB ACK checks told a responding camera apart from one that only saw toggling pins.

Other design decisions
  • A dual-clock framebuffer

    True dual-port memory between camera and display. Instead of: synchronising the pixel bus or streaming straight through.

    Decouples the two rates cleanly, but uses most of the block RAM and doesn't prevent tearing on its own.

  • Three rotating line buffers

    Rotate bank roles each line. Instead of: a two-row design with more scheduling logic.

    One extra row buys simple bank selection and a window valid every pixel.