Some time ago I bought a Digilent Nexys 2 FPGA development board and a Digilent PmodCLS LCD module. I spent some time implementing a more or less trivial CPU in the FPGA, and various bits and pieces to aid with debugging such as a driver for the 4-digit 7-segment LED display on the board (ideal for displaying the contents of 16-bit registers) and debug LEDs and switches to select what to display. I’ve been using this primarily as an exercise in learning Verilog.
I used SPI to send bytes to the LCD module, and hooked up this hardware to the address decoder such that I could simply issue writes from the CPU to send strings to the LCD module. It’s kinda a test project, the hardware equivalent of a hello world program.
The CPU is a very simple 16-bit RISC CPU with 4 orthogonal registers. It’s nothing special, not pipelined, takes quite a few clock cycles per instruction, and has a pretty rubbish instruction set. Conspicuously lacking are indirect addressing modes, which is a shame as it makes writing software quite hard.
The test program I used for this demo was rather complicated by the need for self-modifying code to write meaningful loops for string handling. That made debugging quite interesting. I wrote an assembler in perl, as assembling this kind of code by hand is rather too time-consuming.
The source code for the software running on the CPU is reproduced below. It’s not the model of great code, but it was quite a lot of fun to write and debug.
There’s also a video, running through one cycle.
Here’s the code. Execution starts at the beginning, at address zero.
ld r2 compare st r2 comparestash ld r2 testend st r2 compare ld r2 textloop st r2 textloopstash writetext: ld r3 one textloop: ld r0 text compare: b testend ; mov r0 r0 beq loopend st r0 lcdreg ld r1 textloop add r1 r1 r3 st r1 textloop b textloop loopend: ld r1 textloop add r1 r1 r3 st r1 textloop ld r3 comparestash st r3 compare b textloop testend: mov r0 r0 beq gobacktostart ld r2 testend st r2 compare b delay gobacktostart: ld r2 textloopstash st r2 textloop delay: ld r0 count1 ld r1 count2 ld r2 zero ld r3 one delayloop: sub r0 r0 r3 sbc r1 r1 r2 beq delaycontinue b delayloop delaycontinue: mov r0 r0 beq goroundagain b delayloop ; ready to go round again goroundagain: ld r2 testend st r2 compare b writetext stop: b stop ds #0 ds #0 b stop zero: ds #0 one: ds #1 comparestash: ds #0 textloopstash: ds #0 count1: ds #0xffff count2: ds #0x002f text: ds #27 ; ESC ds #ord('[') ds #ord('j') ; j (clear screen and home cursor) ds #27 ; ESC ds #ord('[') ds #ord('0') ; wrap line at 16 characters ds #ord('h') ; set display mode ds #27 ; ESC ds #ord('[') ds #ord('3') ; 3 (display on, backlight on) ds #ord('e') ; e (control backlight) ds #27 ; ESC ds #ord('[') ds #ord('0') ; cursor off ds #ord('c') ; set cursor mode ds #32 ; SPACE ds #ord('H') ds #ord('e') ds #ord('l') ds #ord('l') ds #ord('o') ds #ord(',') ds #32 ; SPACE ds #ord('W') ds #ord('o') ds #ord('r') ds #ord('l') ds #ord('d') ds #ord('!') ds #0 ds #27 ; ESC ds #ord('[') ds #ord('1') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #32 ; SPACE ds #ord('.') ds #ord('.') ds #ord('o') ds #ord('o') ds #ord('O') ds #ord('O') ds #ord('(') ds #ord(')') ds #ord('O') ds #ord('O') ds #ord('o') ds #ord('o') ds #ord('.') ds #ord('.') ds #0 ds #27 ; ESC ds #ord('[') ds #ord('0') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #ord('T') ds #ord('h') ds #ord('i') ds #ord('s') ds #32 ; SPACE ds #ord('i') ds #ord('s') ds #32 ; SPACE ds #ord('a') ds #ord('n') ds #32 ; SPACE ds #ord('F') ds #ord('P') ds #ord('G') ds #ord('A') ds #0 ds #27 ; ESC ds #ord('[') ds #ord('1') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #ord('a') ds #ord('n') ds #ord('d') ds #32 ; SPACE ds #ord('a') ds #32 ; SPACE ds #ord('C') ds #ord('P') ds #ord('U') ds #32 ; SPACE ds #ord('i') ds #ord('n') ds #ord('s') ds #ord('i') ds #ord('d') ds #ord('e') ds #0 ds #27 ; ESC ds #ord('[') ds #ord('0') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #ord('[') ds #ord('^') ds #ord('^') ds #ord(']') ds #ord('N') ds #ord('e') ds #ord('x') ds #ord('y') ds #ord('s') ds #32 ; SPACE ds #ord('2') ds #ord('[') ds #ord('^') ds #ord('^') ds #ord(']') ds #32 ; SPACE ds #ord('[') ds #ord('v') ds #ord('v') ds #ord(']') ds #ord('P') ds #ord('m') ds #ord('o') ds #ord('d') ds #ord('C') ds #ord('L') ds #ord('S') ds #ord('[') ds #ord('v') ds #ord('v') ds #ord(']') ds #32 ; SPACE ds #0 ds #27 ; ESC ds #ord('[') ds #ord('0') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #ord('v') ds #ord('e') ds #ord('r') ds #ord('i') ds #ord('l') ds #ord('o') ds #ord('g') ds #ord('-') ds #ord('|') ds #ord('-') ds #ord('x') ds #ord('i') ds #ord('l') ds #ord('i') ds #ord('n') ds #ord('x') ds #ord('-') ds #ord('-') ds #ord('-') ds #ord('a') ds #ord('s') ds #ord('m') ds #ord('-') ds #ord('-') ds #ord('|') ds #ord('-') ds #ord('-') ds #ord('p') ds #ord('e') ds #ord('r') ds #ord('l') ds #0 ds #27 ; ESC ds #ord('[') ds #ord('0') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #ord('C') ds #ord('P') ds #ord('U') ds #ord('<') ds #ord('-') ds #ord('-') ds #ord('-') ds #ord('S') ds #ord('P') ds #ord('I') ds #ord('-') ds #ord('-') ds #ord('>') ds #ord('L') ds #ord('C') ds #ord('D') ds #ord('C') ds #ord('P') ds #ord('U') ds #ord('<') ds #ord('-') ds #ord('>') ds #ord('P') ds #ord('S') ds #ord('R') ds #ord('A') ds #ord('M') ds #ord('.') ds #ord('c') ds #ord('o') ds #ord('d') ds #ord('e') ds #0 ds #27 ; ESC ds #ord('[') ds #ord('0') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #ord('>') ds #ord('>') ds #ord('L') ds #ord('E') ds #ord('D') ds #ord('s') ds #32 ; SPACE ds #ord('f') ds #ord('o') ds #ord('r') ds #ord('e') ds #ord('v') ds #ord('e') ds #ord('r') ds #ord('<') ds #ord('<') ds #ord('>') ds #ord('>') ds #ord('.') ds #32 ; SPACE ds #ord('.') ds #32 ; SPACE ds #ord('.') ds #32 ; SPACE ds #32 ; SPACE ds #ord('.') ds #32 ; SPACE ds #ord('.') ds #32 ; SPACE ds #ord('.') ds #ord('<') ds #ord('<') ds #0 ds #27 ; ESC ds #ord('[') ds #ord('1') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #ord('>') ds #ord('>') ds #ord('o') ds #32 ; SPACE ds #ord('o') ds #32 ; SPACE ds #ord('o') ds #32 ; SPACE ds #32 ; SPACE ds #ord('o') ds #32 ; SPACE ds #ord('o') ds #32 ; SPACE ds #ord('o') ds #ord('<') ds #ord('<') ds #0 ds #27 ; ESC ds #ord('[') ds #ord('1') ; row 1 ds #59 ; semicolon ds #ord('0') ; column 0 ds #ord('H') ; set display mode ds #ord('>') ds #ord('>') ds #ord('.') ds #32 ; SPACE ds #ord('.') ds #32 ; SPACE ds #ord('.') ds #32 ; SPACE ds #32 ; SPACE ds #ord('.') ds #32 ; SPACE ds #ord('.') ds #32 ; SPACE ds #ord('.') ds #ord('<') ds #ord('<') ds #0 ds #0 lcdreg: equ #0x7ff
2 Responses to Driving an LCD module from an FPGA