STSaed Thaher
← All projects
CompleteSep – Nov 2026MIT 6.205 Digital Systems LaboratoryUrbana board · Spartan-7 XC7S50

Seven labs, one FPGA: from a blinking LED to a DDR3 video pipeline.

Each lab added a layer to the same board: serial protocols, then video timing, then a camera, then real-time image filtering, then external memory. By the end, 720p video crosses three clock domains and a DDR3 chip on its way to the screen, with timing met at every step.

SystemVerilogVivadococotbUART / SPIHDMI / TMDSBlock RAMClock-domain crossingDDR3 / Wishbone

00At a glance

0/7
designs meeting timing on the board
post-route, zero failing endpoints
0MHz
fastest clock: the TMDS serializer
5× the 74.25 MHz pixel clock
0
clock cycles from pixel in to filtered pixel out
line buffer 2 + convolution 3
0
clock domains in the final design
200 · 83.33 · 74.25 MHz, plus DDR3 at 333.33

Mine

The RTL for each lab's modules: counters and PWM, debouncing, SPI and UART, video timing, TMDS encoding, Pong, the frame buffer and pipeline alignment, center of mass, line buffers and convolution, the DDR3 traffic generator and command FIFO; plus my cocotb testbenches.

Course-provided

Top-level skeletons and pin constraints, clock-wizard and memory wrappers, the camera front end, the kernel coefficient table and a divider.

Open source

The DDR3 memory controller in Lab 7, which I drive over its Wishbone interface.

01The progression

Fig. 1 · Pick a lab

Counters, PWM and a seven-segment decoder

The vocabulary: a parameterized counter, a pulse-width modulator built on it, an RGB LED controller and a binary-to-seven-segment decoder, each with a testbench before it touched the board.

142 LUT112 FFslack +4.213 ns at 100 MHz

Talking to the outside world over SPI

Button debouncing and edge detection, a multiplexed seven-segment controller, and an SPI controller that reads an external ADC: 17-bit transactions on a 2 MHz serial clock, three channels sampled round-robin every millisecond.

245 LUT335 FFslack +4.165 nsSPI clock 2 MHz

UART both ways, and five seconds of audio in block RAM

A UART transmitter and receiver at 115,200 baud. A microphone is sampled at 8 kHz through the SPI ADC and streamed to a laptop; audio sent back from the laptop lands in a 40,000-sample block RAM and plays through an 8-bit PWM output with a 392 kHz carrier.

205 LUT16 block RAM tilesslack +4.027 ns868 clocks per bit

720p HDMI from first principles, then Pong

A video timing generator for 1280×720 at 60 Hz, an 8b/10b TMDS encoder that tracks running disparity, and a serializer clocked at five times the pixel rate from an MMCM with a 742.5 MHz VCO. Test patterns first, then a playable Pong drawn in hardware.

534 LUTslack +0.659 nspixel 74.25 MHzserial 371.25 MHz

A camera, a frame buffer and tracking

Camera pixels are written into a 320×180, 16-bit dual-clock block RAM on a 200 MHz clock and read on the 74.25 MHz pixel clock, so the memory itself is the clock-domain crossing. A thresholded color mask feeds a center-of-mass module that steers a crosshair. Every signal is delayed to match the memory's two-cycle latency.

918 LUT32.5 block RAM tiles8 DSPslack +0.263 ns

Real-time 3×3 convolution

A four-row line buffer in block RAM presents three vertically aligned rows; a three-stage pipelined convolution multiplies, sums, shifts and clamps each color channel. Six filters run in parallel on the 320-wide stream and a seventh blurs the full 1280-wide image, all at pixel rate.

2,584 LUT41.5 block RAM tiles (55%)slack +0.079 nslatency 5 cycles

A 720p frame buffer in DDR3

A full 1280×720 frame does not fit in block RAM. Pixels are packed eight to a 128-bit word, cross into the memory controller's clock domain through an asynchronous FIFO, are written to and read from DDR3 by a traffic generator that interleaves both directions, and cross again to the pixel clock: 115,200 words per frame.

4,133 LUT3,798 FFslack +0.263 nsDDR3 333.33 MHz
Fig. 2 · The designs grow
Lab 1 · logic142 LUT
Lab 2 · SPI245
Lab 3 · UART and audio205
Lab 4 · HDMI534
Lab 5 · camera918
Lab 6 · convolution2,584
Lab 7 · DDR34,133 LUT
Look-up tables used after placement. As the designs grew thirty-fold, slack shrank from over 4 ns to under 0.1 ns, and timing started to drive the design.

02Serial: one frame, bit by bit

The receiver is a five-state machine: idle, start, data, stop, and a one-cycle valid pulse. At 115,200 baud from a 100 MHz clock, each bit lasts 868 clock cycles, and the asynchronous input passes through a two-flip-flop synchronizer before the state machine sees it.

Fig. 3 · An 8N1 frame

03Video: the raster nobody sees

A 720p frame is not 1280×720 pixel slots. It is 1650×750, and the invisible margin carries the sync pulses that tell the monitor where lines and frames begin. Multiply by 60 frames per second and you get the pixel clock exactly: 74.25 MHz. Each pixel then becomes ten bits per color channel, sent serially at five times that rate on both clock edges.

Fig. 4 · The 720p raster, to scale

04Convolution at pixel rate

A 3×3 filter needs three rows at once from a stream that arrives one pixel at a time. The line buffer supplies them; the convolution keeps a 3×3 window in registers and computes one output per clock. This is the same arithmetic the hardware performs, including the 5-6-5 bit channels, the right shift and the clamp:

Fig. 5 · The six kernels
A generated test image filtered in your browser with the kernels from the lab. The clamp to each channel's range is why edge detectors show bright lines on black.

05Three clock domains and a DDR3 chip

Fig. 6 · Lab 7 dataflow
Teal paths cross clock domains through asynchronous FIFOs. The command FIFO remembers what was asked of the memory, so each response can be matched to its address and direction.

The camera produces pixels on its own schedule, the monitor consumes them on another, and the memory controller runs on a third clock. My traffic generator sits in the middle: it alternates read and write requests, records each in a command FIFO, and throttles reads when the output FIFO approaches full, so neither side ever waits on the other.

Latency is part of the interface. A block RAM answers two cycles late. Every signal that travels beside it must be delayed by exactly as much, or the picture shears.
Cross domains with memory, not wires. A dual-clock RAM or an asynchronous FIFO; never a bare multi-bit bus.
Slack is a budget. At +0.079 ns in Lab 6, one more level of logic in the adder would have failed timing. Splitting the convolution into three registered stages is what keeps that number positive.