Skip to content

About

SPI Master Controller on Verilog

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

Repository files navigation

SPI Master Controller + LCD Display for Altera Cyclone IV (EP4CE6F17C8)

Русская версия — README-RU.md

A synthesisable, parameterisable design pair written in Verilog-2001, verified in simulation and brought up on real hardware (ALINX PIAX301V2):

  1. SPI master controller with FIFO buffering, programmable timing, multi-CS support and a memory-mapped register interface, suitable for direct connection to soft CPU cores (Nios II, PicoRV32, etc.) through a thin Avalon-MM / AXI4-Lite / Wishbone adapter.
  2. LCD controller for the ALINX AN430 / TM043NBH02 parallel-RGB 480x272 panel mounted on the ALINX PIAX301V2 board. Provides background patterns and 7-segment hex value overlays, controlled through the same bus protocol as the SPI master.

Both subsystems share a single 50 MHz clock domain. The XPT2046 touch controller on the AN430 board is served by the SPI master via CS[3], and the on-board LCD acts as a live dashboard for SPI bring-up.

A full long-form write-up of the whole journey — from spec to signals on a logic analyser — lives in docs/ (31 chapters + appendices A-E, in Russian).

At a glance (verified results)

  • Silicon cost on EP4CE6 (the smallest device of the family), full spi_lcd build: 1,743 / 6,272 LE (28 %), 854 dedicated registers, 0 memory bits / 0 PLLs (FIFOs in LE, single clock by design).
  • Timing met: worst setup slack +1.032 ns on the 50 MHz clock.
  • 29 self-checking tests, all green: 18 SPI cases (38 checks), 7 LCD cases (13 checks), 4 integration cases. Full regression runs in seconds via make, no license required (Icarus Verilog).
  • Hardware-proven: live touch coordinates on the LCD, "SPI: FOUND/NONE" detection that flips both ways when the touch cable is unplugged.

Highlights

  • All four SPI modes (0, 1, 2, 3); MSB-first and LSB-first ordering.
  • Word lengths of 8, 16, 24 or 32 bits, selectable at run time (any other value at START raises the ERR_WORD_LEN sticky flag).
  • Independent TX/RX FIFOs (parameterised depth, defaults to 8 entries).
  • Multi-slave support with parameterised number of CS lines (default 4).
  • Programmable CS-setup, CS-hold and inter-word delays (in clk cycles).
  • Continuous-burst behaviour: while the TX FIFO is not empty, transfers chain under the same active CS, separated by the inter-word delay.
  • Sticky error flags (TX/RX overflow, start-with-empty, bad word length).
  • Maskable interrupt output (done / RX valid / error).
  • Synchronous single-clock-domain implementation. Asynchronous external reset is brought in through spi_reset_sync (async assert, sync deassert).
  • MISO is brought into the FPGA clock domain through a parameterisable multi-flop synchroniser.
  • Bit-indexed access to the active TX/RX word (no shift-register wrap-around): identical correctness for MSB-first and LSB-first; immune to LSB-side mis-alignment bugs typical of shifting implementations.

Project layout

.
├── rtl/
│   ├── spi_defs.vh             SPI register map + bit definitions
│   ├── spi_reset_sync.v        async-assert / sync-deassert reset cleaner
│   ├── spi_fifo.v              single-clock FIFO with flush
│   ├── spi_reg_if.v            SPI memory-mapped register interface
│   ├── spi_engine.v            SPI FSM, SCLK / MOSI / CS generation
│   ├── spi_master_top.v        SPI integration (FIFOs + reg IF + engine)
│   ├── spi_master_fpga_top.v   SPI-only FPGA top (debug bring-up board)
│   ├── demo_init.v             Boot-time init sequencer (ROM-driven master)
│   ├── spi_probe.v             XPT2046 presence probe (live detector)
│   ├── spi_lcd_fpga_top.v      Combined SPI + LCD FPGA top (PIAX301V2)
│   └── lcd/
│       ├── lcd_defs.vh         LCD register map
│       ├── lcd_timing.v        Generic RGB timing generator
│       ├── lcd_pattern.v       Background patterns (solid/bars/gradient/checker)
│       ├── lcd_seg7.v          Single 7-segment digit glyph
│       ├── lcd_hex_display.v   N-digit hex overlay
│       ├── lcd_font5x7.v       Compact 5x7 bitmap font (16 glyphs)
│       ├── lcd_status_label.v  "SPI: FOUND / SPI: NONE" status overlay
│       ├── lcd_reg_if.v        LCD memory-mapped register interface
│       └── lcd_top.v           LCD integration (single clk-en pipeline)
├── sim/
│   ├── tb_spi_slave_ref.v      independent reference SPI slave model
│   ├── tb_spi_master.v         SPI self-checking testbench
│   ├── tb_lcd_top.v            LCD self-checking testbench
│   └── tb_spi_lcd_top.v        Integration test (init -> probe -> detect)
├── quartus/
│   ├── spi_master.qpf/.qsf/.sdc   SPI-only project (debug board)
│   ├── spi_lcd.qpf/.qsf/.sdc      Combined SPI + LCD project (PIAX301V2)
│   ├── build.sh                   Command-line compile wrapper
│   └── output_files/              Generated .sof / .pof / .rpt land here
├── datasheets/                 Board schematics (PIAX301V2, AN430)
├── docs/                       Long-form article (part0..part7 + appendices)
├── Makefile                    Icarus Verilog simulation driver
├── README.md                   This file (English)
└── README-RU.md                Russian version

Register map (32-bit data, 4-bit byte address)

Addr Name R/W Description
0x0 CONTROL R/W Bit 0 EN, 1 START (W), 2 SOFT_RST (W), 3 CPOL, 4 CPHA, 5 LSB_FIRST, 6 IRQ_EN
0x1 STATUS R See below
0x2 CLK_DIV R/W SCLK half-period = (CLK_DIV + 1) clk cycles; CLK_DIV >= 1
0x3 CS_SELECT R/W Index of active CS (0..NUM_CS-1)
0x4 WORD_LEN R/W 8, 16, 24 or 32
0x5 TX_DATA W Push one word to the TX FIFO (right-justified)
0x6 RX_DATA R Pop one word from the RX FIFO
0x7 DELAY_CFG R/W [7:0] CS_SETUP, [15:8] CS_HOLD, [23:16] INTER (clk cycles)
0x8 IRQ_STATUS R Sticky IRQ source bits (DONE, RX_VALID, ERROR)
0x9 IRQ_MASK R/W Per-source enable mask
0xA ERROR_CLR W Writing W1C to this register clears sticky errors / IRQs and flushes both FIFOs

STATUS bits

Bit Name Description
0 BUSY Engine is in the middle of a transfer
1 DONE Sticky: set when a transfer completes
2 RX_VALID RX FIFO contains at least one word
3 TX_READY TX FIFO is not full
4 ENABLED CONTROL.EN
5 TX_EMPTY TX FIFO is empty
6 RX_FULL RX FIFO is full
8 ERR_TX_OVF Sticky: host wrote TX_DATA while FIFO full
9 ERR_RX_OVF Sticky: RX FIFO overflowed
10 ERR_START Sticky: START while TX FIFO was empty
11 ERR_WORD_LEN Sticky: bad WORD_LEN at START

Programming sequence (single-byte transfer, mode 0)

write CLK_DIV    = 4              // SCLK = clk / (2*(4+1)) = 5 MHz @ 50 MHz
write CS_SELECT  = 0
write WORD_LEN   = 8
write DELAY_CFG  = 0x00_02_02_02  // 2 cycles of setup / hold / inter
write TX_DATA    = 0xA5
write CONTROL    = EN | START | (CPOL<<3) | (CPHA<<4)
poll  STATUS until (DONE && !BUSY) or wait for IRQ
read  RX_DATA
write ERROR_CLR  = (1 << IRQ_DONE)  // ack

LCD controller

Register map (4-bit address, 32-bit data)

Addr Name R/W Description
0x0 CONTROL R/W Bit 0 EN, 1 BL_EN, [3:2] PATTERN, 4 HEX0_EN, 5 HEX1_EN
0x1 STATUS R 0 VSYNC_ACT, 1 HSYNC_ACT, 2 DE, 3 FRAME_TOGGLE
0x2 BG_COLOR R/W 24-bit {R, G, B}, zero-extended
0x3 FG_COLOR R/W 24-bit {R, G, B}, zero-extended
0x4 HEX0_POS R/W {y[15:0], x[15:0]} -- top-left of hex display 0
0x5 HEX0_VALUE R/W 32-bit value to render (8 hex digits, MSB-first)
0x6 HEX1_POS R/W top-left of hex display 1
0x7 HEX1_VALUE R/W hex display 1 value
0x8 FRAME_CNT R Frame counter (wraps), increments at each VSYNC start

Background patterns (CONTROL[3:2])

Value Mode
0 SOLID -- fills the screen with BG_COLOR
1 COLOR_BARS -- 8 vertical bars (white, yellow, cyan, green,
magenta, red, blue, black)
2 GRADIENT -- analytic R = x, G = y, B = (x + y)/2
3 CHECKER -- 32x32 squares alternating FG_COLOR / BG_COLOR

Hex displays are rendered on top of the background. Each hex display shows 8 hex digits of a 32-bit register value, with the most-significant nibble drawn on the left. Width / height / segment thickness can be adjusted at module instantiation time (defaults: 24x40 cells, 4 px thick segments).

Combined SPI + LCD address map (top-level spi_lcd_fpga_top)

The top exposes a single 6-bit-address parallel bus that addresses both peripherals:

addr[5:4] Peripheral addr[3:0] address space
00 SPI master 0x00..0x0F
01 LCD controller 0x10..0x1F

Both modules return zero for unselected addresses, so the read mux is a simple OR.

Build and simulate

Requires Icarus Verilog and (optionally) GTKWave.

make sim_spi   # SPI master testbench       (18 cases, 38 checks)
make sim_lcd   # LCD controller testbench   ( 7 cases, 13 checks)
make sim_int   # Top-level integration test ( 4 cases, init/probe/detect)
make sim       # run all three
make wave_spi  # open SPI VCD in GTKWave
make wave_lcd  # open LCD VCD in GTKWave
make wave_int  # open integration VCD in GTKWave
make clean

The SPI testbench instantiates an independent reference SPI slave (sim/tb_spi_slave_ref.v) and covers reset state, modes 0..3 MSB-first with 8/16/24/32-bit words, LSB-first 8/16-bit, multi-word bursts under a single CS, multi-CS routing, sticky error flags, the full IRQ sequence and SOFT_RST recovery.

The integration testbench instantiates spi_lcd_fpga_top plus a tiny behavioural XPT2046 model: it verifies that demo_init reaches init_done, that spi_probe correctly reports detected = 0 with a floating MISO, and that it flips to detected = 1 (with the right ADC value) once the model starts responding.

Quartus build

Two projects are provided side-by-side in quartus/:

Revision Top entity Use case
spi_lcd spi_lcd_fpga_top Production target, ALINX PIAX301V2 (SPI + LCD).
spi_master spi_master_fpga_top SPI core only, bring-up board with debug bus pins.

The spi_lcd top is fully self-contained. After reset:

  1. demo_init.v walks a ROM table of register writes that bring up the LCD (COLOR_BARS background + two hex overlays + status label) and configure the SPI master (CLK_DIV=24 -> SCLK 1 MHz, WORD_LEN, CS_SELECT=3, EN).
  2. spi_probe.v then takes over the bus and, every scan, performs two 24-bit XPT2046 conversions: control 0xD0 (X) and 0x90 (Y). The results drive:
    • the on-screen "SPI: FOUND" (green) / "SPI: NONE" (red) label;
    • LCD HEX0 = X coordinate (0x00000XXX), HEX1 = Y coordinate (0x00000YYY), updated live each scan so they change while touching. PENIRQ (active-low) is sampled and exposed as pen_down.

Top-level parameters (spi_lcd_fpga_top)

Parameter Default Purpose
SPI_PROBE_CONTINUOUS 1 1: back-to-back touch scans (bring-up). 0: periodic polling.
SPI_PROBE_PERIOD_BITS 22 When not continuous, poll period ~ 2^N clk cycles (~84 ms).
DIAG_PINWALK 0 1: route free-running counter bits straight to SCLK/MOSI/CS — a binary "physics vs logic" probe for hardware bring-up (see docs ch. 27).

Pin map for the AN430 daughter board on PIAX301V2 (touch IC = XPT2046):

Signal FPGA pin Direction Note
clk_50m E1 in 50 MHz crystal
reset_n N13 in Push button, active-low; 2FF-synced
SPI DCLK F8 out spi_sclk (1 MHz at CLK_DIV=24)
SPI CS# E7 out spi_cs_n (single-line, drives CS[3])
SPI DIN D8 out spi_mosi (master -> XPT2046)
SPI DOUT E9 in spi_miso (XPT2046 -> master, pull-up)
BUSY C8 in spi_busy
PENIRQ E8 in (OD) weak pull-up, pen-down indicator
spi_irq R12 out composite core IRQ (observation)

The LCD pin assignments come unchanged from the official ALINX 17_2_sd_sdram_an430_lcd demo. The full pinout (clock, reset, touch SPI and the RGB888 bus) is tabulated in docs/appendices.md, appendix B.

Opening in Quartus Prime GUI

  1. Launch Quartus Prime (Standard or Lite, 13.1 / 17.x / 25.x are all OK).
  2. File -> Open Project...
  3. Navigate to the quartus/ folder of this repo.
  4. Select spi_lcd.qpf (or spi_master.qpf for the SPI-only build).
  5. On first open Quartus generates spi_lcd_assignment_defaults.qdf and the db/ cache directories. That is expected; they are listed in quartus/.gitignore.
  6. Processing -> Start Compilation (or press Ctrl+L) to run the full flow (Analysis & Synthesis -> Fitter -> Assembler -> TimeQuest).
  7. The programming file is produced at quartus/output_files/spi_lcd.sof. Load it via Tools -> Programmer.

Building from the command line

If you prefer the shell:

cd quartus/
./build.sh spi_lcd        # full compile of the SPI + LCD revision
./build.sh spi_master     # full compile of the SPI-only revision

build.sh is a thin wrapper around quartus_sh --flow compile <revision>; the equivalent invocation is quartus_sh --flow compile spi_lcd.

Timing constraints

Each project ships an SDC file that declares the primary clock and the generated clocks for spi_sclk and lcd_dclk, plus conservative output delays for the LCD bus (against the TM043NBH02 datasheet) and the SPI bus (loose, suitable for slow slaves like the XPT2046). Refine the input / output delays once your operating point is fixed.

Recommended operating range

  • System clock: 25..100 MHz.
  • CLK_DIV >= 1 (controller refuses divisor of zero).
  • With the default 2-stage MISO synchroniser, CLK_DIV >= 2 is recommended so that the half-SCLK period is at least 3 clk cycles - long enough to absorb the 2-cycle synchroniser latency. Set MISO_SYNC_STAGES = 1 (or 0, source-synchronously) and tighten the SDC constraints for faster operation.

Limitations and possible extensions

  • Single clock domain only; no slave-mode support (master only).
  • FIFOs use LE-based storage for portability. For deeper buffers convert spi_fifo.v to use M9K block RAM with a separate-read-pointer scheme.
  • No DMA: every word passes through the register interface (fine at 1-5 MHz, a bottleneck at 25+ MHz with large blocks).
  • Touch readout is raw ADC (no calibration / filtering); polling is timer-driven rather than gated by PENIRQ.
  • spi_master_fpga_top exposes a simple parallel debug bus. For a real product, wrap spi_master_top in an Avalon-MM / AXI4-Lite / Wishbone adapter; the protocol is already single-cycle and ready-friendly.
  • The 2-FF synchroniser on MISO is the only async input in the design; all other I/Os are synchronous to clk.

See docs/part7_results.md for the full, annotated roadmap (Avalon/AXI bridge, M9K FIFO + DMA, touch calibration, slave mode + QSPI, formal verification).

About

SPI Master Controller on Verilog

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages