JayV
Published © MIT

Soft RiscV Text to Speech - no coding

Create your FGPA application without writing one single line of code, using Lattice Prompt

IntermediateFull instructions provided8 hours3
Soft RiscV Text to Speech - no coding

Things used in this project

Hardware components

LFMXO4-110-EVN
×1

Software apps and online services

Lattice Prompt
Radiant 2026.1
Propel 2026.1

Story

Read more

Schematics

Schematic

Lattice Propel Schematic

Code

MachXO4_TTS_Prompt_2026_1.md

Plain text
LLM Prompt file
# MachXO4 Text-to-Speech: Project Prompt (Propel and Radiant 2026.1)


This prompt rebuilds the project in two parts. Part A creates the speech engine as a reusable Propel library block (TTS_APB). Part B builds the final application: a Propel RISC-V SoC that uses this block on the MachXO4 Development Board. Run Part A first; Part B starts from the installed library block.


Revision: 1.5 (matches release SM_v1.5 and library block custom:module:tts_apb:1.5.0).


## General rules (both parts)

- Tools: Lattice Propel 2026.1.1 (SoC, IP catalog, RISC-V GCC toolchain) and Lattice Radiant 2026.1.1 with LSE synthesis; Questa 2026.1.1 for RTL simulation; Python 3 for models and test tools. Use the Lattice Prompt MCP tools for IP configuration, builds, timing checks and programming.
- Device: LFMXO4-110HC-6BBG484C, speed grade 6, on the MachXO4 Development Board. Working directory: C:\lscc\workspace\MachXO4_TTS2.
- HDL: Verilog-2001. All I/O LVCMOS33. Pins given in this prompt must not be changed.
- Record every decision, deviation and open item in design_plan.html. Deliver a design report as HTML and as a Word document and keep it current.
- Save every step as its own release folder: bitstream, JEDEC file, programming chain file, programming script (dry run by default), README with SHA256 hashes and a sources archive.
- Always ask for approval before programming the board.
- Simulation rule: simulate when the speech engine (TTSCPU firmware, ROM tables or RTL) changes. A change that only affects the RISC-V firmware needs no simulation: compile, load the memory image, build, check timing, release.
- Use known tool paths and commands; do not search whole install trees. Never start the GUI programs ippack.exe or ippackc.exe from a shell.
- Formal wording, no emojis, no personal data. Cite an internal policy number only if one is supplied.

## Part A: TTS engine as a Propel library block (TTS_APB)


### A.1 Objective


Create an English text-to-speech engine that runs entirely inside the FPGA (no external memory, no neural network) and package it as a memory-mapped APB peripheral for the Propel IP catalog. The block receives ASCII text through APB register writes and produces speech on one PWM output pin.


### A.2 Block interface

- Bus: one APB slave interface APB_S0 (AMBA3 APB r1p0), 32-bit data, 16-byte register window. Clock and reset come from the APB clock and reset of the SoC (24 MHz).
- External port: pwm_o only (10-bit PWM, 32 kHz carrier at 24 MHz, period 750 clocks).

| Offset | Register | Content |
|---|---|---|
| 0x00 | ID | Read only: 0x54545301 |
| 0x04 | CTRL | Write: bit 0 START (speak the completed line), bit 1 CLEAR (discard queued text, ignored while busy) |
| 0x08 | STATUS | Bit 0 BUSY, bit 1 READY (complete line waiting), bit 2 DONE (sticky), bit 3 PLAY, bit 4 DROP (sticky), bits 15..8 LEN (length of the oldest complete line, 1..128, 0 when none) |
| 0x0C | DATA | Write: one text character; read: last character written |


### A.3 Text handling

- Two line buffers of 128 characters each (ping-pong). One line can be spoken while the next is received; a further line is dropped as a whole (DROP).
- A line ends at CR or LF (CR LF counts once), or by itself at the 128th character. Only printable ASCII is stored; empty lines are ignored.
- Control characters: space = word gap 75 ms; "-" = half gap 45 ms; "," = short pause 125 ms; ". ; :" = sentence pause 170 ms; "?" = rising intonation then 170 ms; "!" = emphasis of the last word then 170 ms.
- Tone command #<Hz>,<ms># anywhere in a line, several per line: a sine tone up to 7.9 kHz and up to 20 s with 4 ms fade in and out. The same control characters give the gaps between tones (space 75 ms, "-" 45 ms, "," 125 ms, ". ; :" 170 ms); tones written directly after each other (##) are seamless. A line that ends while a tone command is still open is ignored as a whole.
- The output is muted when idle, fades in before speech and fades out at the end (no clicks); pauses are exactly silent. BUSY stays set until the fade-out has finished.

### A.4 Speech engine

- Letter-to-sound: context rules in the style of the NRL scheme with an exception list; spelling of lone letters and words without vowels; digits one by one; a run of vowels sounds as one vowel.
- Prosody: word accents, statement fall, question rise, comma, emphasis, vowel reduction of unstressed vowels and function words, smooth pitch contour.
- Synthesizer at 16 kHz: glottal pulse source with jitter, shimmer and aspiration; five cascaded formant resonators; fricative and burst noise through two resonators with per-sound centre and bandwidth. Sound durations are the phoneme table values times 1.15, rounded to 5 ms frames. Consonants such as r, x, z, s and final stops must stay audible.
- Processor TTSCPU: a small custom 32-bit core with a serial multiplier, firmware written with an own assembler, program ROM, tables and 1024-word data RAM in block RAM (phoneme list capped at 520 entries; extreme lines are cut to a correct prefix without overrun), and a sample FIFO to the PWM unit. Real time at 1500 clocks per sample without FIFO underrun.

### A.5 Verification

- Float and fixed-point Python reference models. The TTSCPU firmware and the RTL must reproduce the fixed-point model bit for bit.
- Instruction-set simulator test (tools/test_firmware.py): letter-to-sound texts including 128-character lines and tone-gap cases with 0 mismatches, plus end-to-end texts bit-exact.
- Questa testbenches: tb_ttscpu (processor RTL bit-exact against the model) and tb_tts_apb (register-level test: ID and reset, one line, queue and drop, 128-character lines and bursts, CR LF and empty lines, tone commands, unterminated tone command, CLEAR and reset during speech).

### A.6 Driver

- driver/tts_regs.h: register offsets, bit definitions, TTS_ID_VALUE and TTS_LINE_MAX (128).
- driver/tts.h and driver/tts.c, using reg_access.h of the RISC-V BSP: struct tts_instance; unsigned char tts_init(struct tts_instance *, uint32_t base_addr) (binds the base address and checks the ID; returns 0, 1 for a null pointer, 2 for a wrong ID); uint32_t read_tts_status(struct tts_instance *); void write_tts_control(struct tts_instance *, uint32_t value); uint32_t write_tts_phrase(struct tts_instance *, const char *buf, uint32_t length).
- driver/tts_apb.xml: Propel driver descriptor (IP-XACT) with vendor custom, library module, name tts_apb, version 1.5.0, structName tts_instance, initName tts_init, headerFile tts.h, so that Propel generates the driver into the BSP.

### A.7 Library block and packaging

- Identity: custom:module:tts_apb:1.5.0, display name TTS_APB, category Custom IP, folder custom_module_tts_apb_1.5.0. metadata.xml keeps platform_version, min_radiant_version and min_esi_version at 2025.2 (the block also works in 2026.1).
- Contents: metadata.xml, bus_interface.xml (APB_S0), memory_map.xml (the four registers), rtl/ (tts_apb.v top, ttscpu.v, tts_engine.v, line_buf.v, sample_fifo.v, pwm10.v, tts_roms.v), driver/ (tts_regs.h, tts.c, tts.h, tts_apb.xml), doc/introduction.html.
- doc/introduction.html in the layout of the existing custom blocks: name TTS_APB; devices supported ALL; description with a summary of the function, the register table, the list of driver functions with a description, and a code example of initialisation and use; revision history with revision 1.5.
- Package with the IP Packager engine on the command line: C:\lscc\propel\2026.1.1\builder\rtf\ispfpga\bin\nt64\ippkg.exe -metadata_files <list of metadata.xml, bus_interface.xml, memory_map.xml> -rtl_files <list of rtl/*.v> -driver_files <list of driver/*> -help_file <block>\doc\introduction.html -o custom_module_tts_apb_1.5.0.ipk (set FOUNDRY to C:\lscc\propel\2026.1.1\builder\rtf\ispfpga). Check the contents of the .ipk.
- Install the .ipk through the Propel IP Catalog (it lands in PropelIPLocal). A new version gets its own folder; never edit an installed version in place.

## Part B: SoC system for the final application


### B.1 Objective


Build a Propel RISC-V SoC that receives text over a UART and speaks it through the TTS_APB library block, with status messages, LEDs, a 7-segment display, three phrase buttons and a periodic jingle.


### B.2 SoC components (Propel 2026.1.1 catalog versions)


| Block | IP and version | Settings |
|---|---|---|
| PLL | pll 3.0.1 | 12 MHz in (pin B10), 24 MHz system clock |
| CPU | riscv_sm 1.9.0 | RV32I, no multiplier; DEBUG_ENABLE false (its JTAG debug port blocks placement at site D10) |
| System memory | system_memory 2.6.0 | ADDR_DEPTH 4096 (16 KB) at 0x0000, initialised with the firmware image |
| Bus | ahb_lite_interconnect 1.5.0, ahb_lite_to_apb_bridge 1.2.0, apb_interconnect 1.4.0 | AHB-Lite for CPU and memory, APB for the peripherals |
| UART | uart 1.6.0 at 0x8000 | RXRDY_ENA false (no RX ready pin), interrupt to the CPU |
| LED GPIO | gpio 1.8.1 at 0x8400 | 7 lines: 3..0 LED outputs, 6..4 inputs SW3, SW4, SW5 |
| 7-segment GPIO | gpio 1.8.1 at 0x8800 | 8 outputs |
| TTS | custom:module:tts_apb:1.5.0 at 0x8C00 | Library block from Part A; pwm_o to the top level |


### B.3 Top level and pins

- tts_top.v wraps the SoC: SW2 debounce (about 22 ms at 12 MHz) and reset, the "SW2 pressed" LED driven in hardware, and the switch inputs mapped as sw_i = {SW5, SW4, SW3, 4'b0000}.

| Signal | Pin | Function |
|---|---|---|
| clk12_i | B10 | 12 MHz board clock |
| sw2_n_i | D3 | SW2, active low, restarts the design |
| sw_n_i[2:0] | G7, F6, D4 | SW5, SW4, SW3, active low, internal pull-up |
| rxd_i / txd_o | P4 / N5 | UART 19200 baud 8N1, no pull-ups |
| pwm_o | G12 | Audio PWM to a BC547 amplifier and speaker |
| dled_n_o[0..3] | J1, J2, H1, H2 | Low = lit: heartbeat, SW2 pressed, speech active, character received |
| seg_o[0..7] | G2, D1, C1, D2, E2, E3, E1, F1 | Last received character as ASCII bits, lit when low |


### B.4 Firmware behaviour (C, rv32i)

- main() uses only driver functions (UART, GPIO, TTS, timer); no direct register access. The TTS driver comes from the library block (BSP driver tts_apb).
- After reset: send "TTS OK." and speak the boot phrase "Lattice? Mag X-O for is running!"; characters received during the phrase are discarded and the queue is cleared afterwards.
- Each received character goes to the TTS block (line length up to 128). When a line is complete: send "TTS EXE", write START, wait until BUSY clears, send "TTS RDY". Every message ends with CR LF.
- Buttons, debounced in firmware: SW3 speaks "hellow? , is it me you r looking for?"; SW4 speaks "Lattice!, harder!-better!-faster-stronger"; SW5 speaks the boot phrase. A press while speaking waits; the latest press wins.
- Jingle every 10 minutes (RISC-V timer, assumed 24 MHz tick), when the TTS block is idle and no button phrase is pending, two tone phrases one after the other: 1: #1318,125# #1174,125# #881,100#,#1046,188# #991,188# #657,188# #881,125# #1046,125# #1174,125# #989,125#  2: #1318,125#-#1174,125#-#881,100#-#1046,125# #991,125# #657,125# #881,125# #1046,125#-#1174,125# #991,313#
- LEDs: heartbeat, speech active, character received; the 7-segment display shows the last received character.

### B.5 Build flow (two passes)

- Pass 1: create the SoC in Propel from a script, generate it and generate the system environment (SGE).
- Firmware: compile with -march=rv32i -mabi=ilp32, convert the binary to a memory image (bin2mem, plain ASCII without BOM), configure the system memory with INIT_MEM true, INIT_CHK hex and INIT_FILE_IN set to the image, generate again and confirm with check_memory_init_applied that the firmware is in the generated memory. Then generate the SGE again.
- Radiant: LSE, loop limit 20000, LSE memory initialisation search path set to the CPU testbench folder of the generated SoC. The Radiant project lists the .ipx file of every generated instance; update it when an instance is replaced.
- Gates: 0 failing endpoints in all corners at 24 MHz; resources within the device; constraint coverage reviewed (the asynchronous ports rxd_i, sw2_n_i, sw_n_i and seg_o are unconstrained and need a recorded waiver).
- Reference result (SM_v1.5 with TTS_APB 1.5.0): Fmax 27.7 MHz, 2057 registers (20 percent), 6077 LUT4 (65 percent), 44 of 48 block RAMs (92 percent), firmware 9.3 KB of 16 KB.

### B.6 Programming

- Program the JEDEC file into the internal flash (Erase, Program, Verify) over JTAG, cable USB2, port FTUSB-0, TCK delay 3. Use a chain file in the format saved by the Radiant Programmer GUI: I/O state HighZ and IOVectorData all ones, so that the outputs stay tri-stated during programming; set the JedecChecksum and FileTime of the new JEDEC file.
- The batch programmer prints "failed" because it misses its completion line; the programmer log line "Operation: successful" is the result. Check that no other program holds the FTDI cable.

### B.7 Audio hardware

- G12 through 680 ohm to the base of a BC547, 100 kohm from base to ground; the collector drives an 8 ohm speaker in parallel with a 1N4148, then 47 ohm 0.5 W to +5 V, with 100 uF across the supply. The board I/O is 3.3 V only.

Credits

JayV
32 projects • 34 followers
Silicon crazy for profession, silicon do-it-yourselves at Home.

Comments