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.
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.
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.
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.