Work
03 FPGA & Digital Design 2025

Verilog LCD Driver

An HD44780 character display driven straight from a Spartan-6, with no processor in the loop.

Solo
VerilogFSMXilinx ISESpartan-6Testbench
The eleven-state machine. CLEAR_WAIT exists because of a datasheet violation.
The eleven-state machine. CLEAR_WAIT exists because of a datasheet violation.
Running on the Spartan-6.
Running on the Spartan-6.
Bench setup during bring-up.
Bench setup during bring-up.
ISE simulation waveform.
ISE simulation waveform.
Clear Display
1.52 ms required
Settle window
50 µs
Power-on
20.05 ms
Verified
28 / 28 bytes
The finding

Clear Display takes 1.52 ms against roughly 37 µs for every other command, and had been handed the same settle window.

Overview

A Verilog driver for a 16×2 HD44780 character LCD, taken through the whole FPGA flow: RTL, simulation, synthesis in Xilinx ISE and bring-up on a Spartan-6. There is no soft CPU: a small state machine initialises the display and writes two lines of text.

Problem

Drive a character LCD from an FPGA with no processor anywhere in the loop. Every delay sized by hand, from the datasheet.

System

An eleven-state machine: power-on wait, the HD44780 initialisation sequence, DDRAM addressing, then a write state streaming ASCII from an internal message ROM. RW is tied low, so the busy flag is never polled and every wait is a fixed delay, which is precisely why the timing had to be right.

tickrs, en, dataASCIIClock50 MHzTick generatorOne tick every 50 µsState machine11 statesHD4478016×2 LCDMessage ROMTwo linesreadyBoth lines written

How it works

  1. A tick generator divides the 50 MHz clock into one tick every 50 µs, so every delay is a count of ticks.
  2. The state machine waits for power-on (over 15 ms), then sends Function Set, Display On, Clear and Entry Mode.
  3. After Clear Display it waits in a state of its own for more than 1.52 ms, about forty times longer than any other command.
  4. Each byte is put on the bus and latched by one EN strobe; the display reads it on the falling edge.
  5. It writes "SALSABEEL K" and "SPARTAN6 LCD" from a message ROM, then raises ready.

Hardware

PartWhat it does
Spartan-6 board50 MHz clock
HD44780 16×2 LCD8-bit, write-only
Xilinx ISESynthesis (Vivado dropped Spartan-6)
Icarus VerilogSimulation and the self-checking testbench

Software

  • One synthesizable module with CLK_HZ and EN_US parameters, so a different board clock keeps the microsecond delays right.
  • A self-checking testbench with compressed timing that captures every byte on the falling edge of EN and checks the Clear Display wait against the datasheet.
  • A constraints template: 50 MHz clock, LVCMOS33, and slow slew on the lines that run over a ribbon cable.

My part

Whole design, taken through RTL, simulation, synthesis in Xilinx ISE and bring-up on hardware. Adapted from an open-source driver, then reconstructed from my project report after the original file was lost, and corrected in the process.

Challenges

The command that takes forty times longer

Most commands finish in about 37 µs, so one 50 µs window covers them. Clear Display needs 1.52 ms, and a command sent before it finishes is silently dropped: characters go missing from the start of line 1.

No busy flag

RW is tied low, so the busy flag is never read and every wait is a fixed delay. Simple, but every delay has to come from the datasheet.

Rebuilt from the report

The original file was lost. Rebuilding it from the project report reproduced the missing clear delay, which a command-by-command check against the datasheet caught.

Result

A dedicated wait state now covers the clear, proven by a self-checking testbench that captures every byte on the falling edge of EN and verifies all 28 against the expected sequence.

What's next

  • Re-run the corrected RTL on hardware (the photos are from the original run).
  • Extend the testbench to the last byte, which it doesn't check yet.