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




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.
How it works
- A tick generator divides the 50 MHz clock into one tick every 50 µs, so every delay is a count of ticks.
- The state machine waits for power-on (over 15 ms), then sends Function Set, Display On, Clear and Entry Mode.
- 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.
- Each byte is put on the bus and latched by one EN strobe; the display reads it on the falling edge.
- It writes "SALSABEEL K" and "SPARTAN6 LCD" from a message ROM, then raises ready.
Hardware
| Part | What it does |
|---|---|
| Spartan-6 board | 50 MHz clock |
| HD44780 16×2 LCD | 8-bit, write-only |
| Xilinx ISE | Synthesis (Vivado dropped Spartan-6) |
| Icarus Verilog | Simulation 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.