Objetivo y caso de uso
Qué construirás: Implementar un eco UART a 9600 bps en la placa Basys 3 FPGA utilizando Verilog.
Para qué sirve
- Comunicación serial entre la FPGA y un PC para depuración de datos.
- Interacción con sensores que envían datos en formato UART.
- Pruebas de transmisión de datos en aplicaciones de IoT usando LoRa.
- Desarrollo de prototipos de sistemas embebidos con comunicación UART.
Resultado esperado
- Latencia de respuesta menor a 10 ms en la comunicación serial.
- Transmisión de datos a 9600 bps sin pérdida de paquetes.
- Capacidad de recibir y enviar hasta 256 bytes en un ciclo de prueba.
- Mensajes de eco confirmando la recepción correcta de datos.
Público objetivo: Ingenieros y estudiantes de electrónica; Nivel: Básico
Arquitectura/flujo: Comunicación UART entre la FPGA Basys 3 y un terminal serie en PC.
Nivel: Básico
Prerrequisitos
Sistema operativo recomendado (y verificado)
- Windows 10/11 64-bit con drivers de puertos COM instalados (FTDI).
- o Ubuntu 22.04 LTS 64-bit con soporte para puertos serie USB (módulos ftdi_sio/usbserial).
Ambos entornos funcionan correctamente con la toolchain indicada.
Toolchain exacta (versiones y enlaces)
- Vivado Design Suite 2023.2 (WebPACK), 64-bit
- Versión probada: 2023.2
- Descarga: https://www.xilinx.com/support/download.html (seleccionar Vivado WebPACK)
- Nota: WebPACK es la edición gratuita y soporta la Basys 3.
- Cableado y programación: integrado en Vivado (no se requiere herramienta adicional).
- Terminal serie:
- Windows: PuTTY 0.78 o superior (https://www.putty.org/)
- Linux: screen (4.08.00) o miniterm (pyserial 3.5)
Verificación de instalación (línea de comandos)
- Windows PowerShell o CMD:
- vivado -version
- Linux:
- vivado -version
- lsusb | grep -i ftdi (con la placa conectada)
- dmesg | tail (para ver /dev/ttyUSBx asignado)
Salida esperada de Vivado (similar):
– Vivado v2023.2 (64-bit) …
Materiales
- 1× FPGA Basys 3 (Xilinx Artix‑7, XC7A35T-1CPG236C).
- 1× Cable micro‑USB (de datos) para la Basys 3.
- 1× PC con Windows 10/11 o Ubuntu 22.04 LTS.
- Software:
- Vivado WebPACK 2023.2 instalado.
- PuTTY (Windows) o screen/miniterm (Linux).
- Opcional:
- Multímetro o analizador lógico sólo si se quiere inspección adicional (no requerido en este caso básico).
Consideraciones:
– La Basys 3 incorpora un puente USB (FTDI) que ofrece JTAG y un puerto USB‑UART. No se necesita adaptador USB‑UART externo.
Preparación y conexión
Conexión física
- Conecta la Basys 3 al PC mediante el cable micro‑USB.
- Asegúrate de que el selector de alimentación (si aplica) permite la alimentación por USB.
- No conectes nada a los pines PMOD para este caso; usaremos el USB‑UART integrado.
En Windows:
– Abre el Administrador de dispositivos y localiza “Puertos (COM y LPT)”.
– Verás típicamente dos interfaces FTDI (una JTAG y otra UART). El puerto COM asignado a UART suele ser COMx (el mayor número).
– Anota ese COMx para usarlo en PuTTY.
En Linux:
– Ejecuta dmesg | grep -i ttyUSB tras conectar la placa. Deberías ver dos dispositivos: /dev/ttyUSB0 y /dev/ttyUSB1. El UART suele ser el segundo (p. ej., /dev/ttyUSB1).
– Confírmalo con ls -l /dev/ttyUSB* o por descarte si uno está ocupado por JTAG.
Tabla de señales y puertos (Basys 3)
Usaremos el reloj de 100 MHz integrado, el USB‑UART (RX/TX) y un LED para indicar recepción. En la práctica, estas asignaciones vienen del archivo de constraints (XDC). La siguiente tabla resume las conexiones lógicas que emplearemos:
| Señal funcional | Puerto HDL | Pin FPGA (paquete) | IOSTANDARD | Comentario |
|---|---|---|---|---|
| Reloj 100 MHz | clk | W5 | LVCMOS33 | Oscilador de 100 MHz de la Basys 3 |
| UART RX (hacia FPGA) | uart_rx | B18 | LVCMOS33 | Desde FTDI USB‑UART (PC -> FPGA) |
| UART TX (desde FPGA) | uart_tx | A18 | LVCMOS33 | Hacia FTDI USB‑UART (FPGA -> PC) |
| LED 0 | led0 | U16 | LVCMOS33 | Encendido breve al recibir un byte (eco) |
Notas:
– El mapeo de pines proviene de los constraints típicos para Basys 3. Si usas el “Basys-3-Master.xdc” oficial de Digilent, asegúrate de alinear exactamente los nombres de puertos HDL con los de ese archivo si decides reutilizarlo.
– El reloj en W5 (100 MHz) es estándar para Basys 3.
Código completo (Verilog) y explicación
Objetivo: Implementar un eco UART a 9600 bps, 8‑N‑1. Todo lo que llegue por uart_rx se reenvía por uart_tx. Además, se activa el LED0 brevemente al recibir un byte.
Puntos clave:
– Reloj del sistema: 100 MHz.
– Baud rate: 9600 bps.
– Parámetro divisor: CLKS_PER_BIT = 100_000_000 / 9600 ≈ 10417 (redondeo con error < 0,01%).
– Protocolo 8‑N‑1: 1 bit de inicio (0), 8 bits de datos, sin paridad, 1 bit de stop (1).
Módulos UART RX/TX y top
El siguiente código incluye:
– uart_rx: receptor UART con muestreo simple en el centro de bit.
– uart_tx: transmisor UART con temporización por contador.
– uart_echo_basys3_top: top‑level que enlaza RX->TX y gestiona el LED.
// File: src/uart_echo_basys3_top.v
// Tool: Vivado WebPACK 2023.2
// Board: Basys 3 (Xilinx Artix-7, XC7A35T-1CPG236C)
// UART: 9600 bps, 8-N-1, eco inmediato
`timescale 1ns/1ps
module uart_rx #(
parameter integer CLKS_PER_BIT = 10417
)(
input wire clk, // 100 MHz system clock
input wire rx, // UART receive pin
output reg [7:0] rx_byte, // Received byte
output reg rx_dv // Data valid (1 clk pulse)
);
localparam [2:0] IDLE=0, START=1, DATA=2, STOP=3, CLEANUP=4;
reg [2:0] state = IDLE;
reg [13:0] clk_cnt = 0; // enough bits for CLKS_PER_BIT up to ~16383
reg [2:0] bit_idx = 0;
reg rx_sync1 = 1'b1, rx_sync2 = 1'b1;
reg [7:0] rx_shift = 8'h00;
// Synchronize RX to clk
always @(posedge clk) begin
rx_sync1 <= rx;
rx_sync2 <= rx_sync1;
end
always @(posedge clk) begin
case (state)
IDLE: begin
rx_dv <= 1'b0;
clk_cnt <= 0;
bit_idx <= 0;
if (rx_sync2 == 1'b0) begin // start bit detected
state <= START;
end
end
START: begin
if (clk_cnt == (CLKS_PER_BIT/2)-1) begin
// sample in middle of start bit
if (rx_sync2 == 1'b0) begin
clk_cnt <= 0;
state <= DATA;
end else begin
state <= IDLE; // false start
end
end else begin
clk_cnt <= clk_cnt + 1;
end
end
DATA: begin
if (clk_cnt == CLKS_PER_BIT-1) begin
clk_cnt <= 0;
rx_shift[bit_idx] <= rx_sync2;
if (bit_idx == 3'd7) begin
bit_idx <= 0;
state <= STOP;
end else begin
bit_idx <= bit_idx + 1;
end
end else begin
clk_cnt <= clk_cnt + 1;
end
end
STOP: begin
if (clk_cnt == CLKS_PER_BIT-1) begin
rx_byte <= rx_shift;
rx_dv <= 1'b1; // pulse data valid
clk_cnt <= 0;
state <= CLEANUP;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
CLEANUP: begin
rx_dv <= 1'b0;
state <= IDLE;
end
default: state <= IDLE;
endcase
end
endmodule
module uart_tx #(
parameter integer CLKS_PER_BIT = 10417
)(
input wire clk, // 100 MHz
input wire [7:0] tx_byte,
input wire tx_dv, // data valid strobe (1 clk)
output reg tx, // UART transmit pin
output reg tx_active, // high while transmitting
output reg tx_done // 1 clk pulse when done
);
localparam [2:0] IDLE=0, START=1, DATA=2, STOP=3, CLEANUP=4;
reg [2:0] state = IDLE;
reg [13:0] clk_cnt = 0;
reg [2:0] bit_idx = 0;
reg [7:0] tx_shift = 8'h00;
initial begin
tx = 1'b1; // idle line high
end
always @(posedge clk) begin
case (state)
IDLE: begin
tx <= 1'b1;
tx_done <= 1'b0;
tx_active <= 1'b0;
clk_cnt <= 0;
bit_idx <= 0;
if (tx_dv) begin
tx_active <= 1'b1;
tx_shift <= tx_byte;
state <= START;
end
end
START: begin
tx <= 1'b0; // start bit
if (clk_cnt == CLKS_PER_BIT-1) begin
clk_cnt <= 0;
state <= DATA;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
DATA: begin
tx <= tx_shift[bit_idx];
if (clk_cnt == CLKS_PER_BIT-1) begin
clk_cnt <= 0;
if (bit_idx == 3'd7) begin
bit_idx <= 0;
state <= STOP;
end else begin
bit_idx <= bit_idx + 1;
end
end else begin
clk_cnt <= clk_cnt + 1;
end
end
STOP: begin
tx <= 1'b1; // stop bit
if (clk_cnt == CLKS_PER_BIT-1) begin
clk_cnt <= 0;
state <= CLEANUP;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
CLEANUP: begin
tx_done <= 1'b1;
tx_active <= 1'b0;
state <= IDLE;
end
default: state <= IDLE;
endcase
end
endmodule
module uart_echo_basys3_top(
input wire clk, // 100 MHz
input wire uart_rx, // from FTDI to FPGA
output wire uart_tx, // from FPGA to FTDI
output reg led0
);
localparam integer CLKS_PER_BIT = 10417;
// RX
wire [7:0] rx_byte;
wire rx_dv;
uart_rx #(.CLKS_PER_BIT(CLKS_PER_BIT)) u_rx (
.clk (clk),
.rx (uart_rx),
.rx_byte(rx_byte),
.rx_dv (rx_dv)
);
// TX
reg tx_dv = 1'b0;
reg [7:0] tx_byte = 8'h00;
wire tx_active;
wire tx_done;
reg [15:0] led_cnt = 0;
uart_tx #(.CLKS_PER_BIT(CLKS_PER_BIT)) u_tx (
.clk (clk),
.tx_byte (tx_byte),
.tx_dv (tx_dv),
.tx (uart_tx),
.tx_active(tx_active),
.tx_done (tx_done)
);
// Echo simple: cuando llega un byte y el TX está libre, lo enviamos
always @(posedge clk) begin
tx_dv <= 1'b0; // default
if (rx_dv && !tx_active) begin
tx_byte <= rx_byte;
tx_dv <= 1'b1; // un ciclo
led_cnt <= 16'd50000; // ~0.5 ms a 100 MHz
end
// LED activity breve
if (led_cnt != 0) begin
led_cnt <= led_cnt - 1;
led0 <= 1'b1;
end else begin
led0 <= 1'b0;
end
end
endmodule
Explicación breve de las partes clave:
– uart_rx: sincroniza la línea RX, detecta flanco de inicio, muestrea a mitad del bit de start y cada bit subsiguiente con un contador de CLKS_PER_BIT. Entrega rx_dv como pulso de validez y rx_byte como el octeto recibido.
– uart_tx: al recibir tx_dv, envía start, 8 bits de datos (LSB primero), y stop; expone tx_active durante la transmisión y pulso tx_done al terminar.
– uart_echo_basys3_top: encadena RX a TX para eco; cuando rx_dv = 1 y tx no está ocupado, lanza transmisión del mismo byte; enciende led0 brevemente como indicador visual de recepción.
Constraints (XDC)
Creamos un archivo XDC mínimo para Basys 3, con reloj, UART y LED0. Puedes nombrarlo constraints/basys3_uart.xdc:
## File: constraints/basys3_uart.xdc
## Board: Basys 3 (Artix-7 XC7A35T-1CPG236C)
## Clock 100 MHz
set_property PACKAGE_PIN W5 [get_ports {clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {clk}]
create_clock -period 10.000 -name sys_clk -waveform {0 5} [get_ports {clk}]
## USB-UART
set_property PACKAGE_PIN B18 [get_ports {uart_rx}]
set_property IOSTANDARD LVCMOS33 [get_ports {uart_rx}]
set_property PACKAGE_PIN A18 [get_ports {uart_tx}]
set_property IOSTANDARD LVCMOS33 [get_ports {uart_tx}]
## LED0
set_property PACKAGE_PIN U16 [get_ports {led0}]
set_property IOSTANDARD LVCMOS33 [get_ports {led0}]
Nota: El nombre de puertos HDL (clk, uart_rx, uart_tx, led0) debe coincidir exactamente con los del módulo top.
Compilación, programación y ejecución (CLI con Vivado 2023.2)
Organiza tu proyecto con esta estructura:
– proyecto/
– src/uart_echo_basys3_top.v
– constraints/basys3_uart.xdc
– scripts/build.tcl
Script de build (Tcl para Vivado)
Crea scripts/build.tcl con:
# File: scripts/build.tcl
# Vivado WebPACK 2023.2 - CLI flow for Basys 3 UART echo
set proj_name "uart_echo_basys3"
set proj_dir [file normalize "./$proj_name"]
file mkdir $proj_dir
# Crear proyecto y definir parte de la FPGA (Basys 3)
create_project $proj_name $proj_dir -part xc7a35tcpg236-1 -force
# Añadir fuentes HDL
add_files -fileset sources_1 [file normalize "./src/uart_echo_basys3_top.v"]
# Añadir constraints
add_files -fileset constrs_1 [file normalize "./constraints/basys3_uart.xdc"]
# Establecer top
set_property top uart_echo_basys3_top [current_fileset]
# Guardar y lanzar síntesis/implementación/bitstream
save_project_as $proj_name $proj_dir
launch_runs synth_1
wait_on_run synth_1
launch_runs impl_1 -to_step write_bitstream
wait_on_run impl_1
# Programación (requiere placa conectada por USB)
open_hw
connect_hw_server
open_hw_target
current_hw_device [lindex [get_hw_devices] 0]
refresh_hw_device -update_hw_probes false
set bitfile [file normalize "$proj_dir/$proj_name.runs/impl_1/uart_echo_basys3_top.bit"]
set_property PROGRAM.FILE $bitfile [current_hw_device]
program_hw_devices [current_hw_device]
# Mensaje final
puts "Programación completada. Bitstream: $bitfile"
Comandos exactos a ejecutar
Desde la carpeta proyecto/:
- Linux:
- vivado -mode batch -source scripts/build.tcl
- Windows (PowerShell o CMD):
- vivado -mode batch -source scripts\build.tcl
Salida esperada:
– Vivado ejecuta síntesis, implementación y genera el bitstream.
– La programación finaliza sin errores (“Programación completada…”).
Validación paso a paso
Objetivo: al teclear en el terminal serie a 9600‑8‑N‑1, cada carácter debe volver (eco). El LED0 debe parpadear brevemente por cada byte recibido.
1) Verifica el puerto serie:
– Windows:
– Abrir Administrador de dispositivos → Puertos (COM & LPT).
– Identifica “USB Serial Port (COMx)” correspondiente al UART de la Basys 3.
– Linux:
– dmesg | grep -i ttyUSB
– Escoge /dev/ttyUSB1 si hay dos; si falla, prueba /dev/ttyUSB0.
2) Configura el terminal:
– Windows (PuTTY):
– Connection type: Serial
– Serial line: COMx (identificado antes)
– Speed: 9600
– Data bits: 8
– Stop bits: 1
– Parity: None
– Flow control: None
– Open
– Linux:
– screen /dev/ttyUSB1 9600
– o: python -m serial.tools.miniterm /dev/ttyUSB1 9600 –eol LF
3) Prueba de eco:
– Teclea caracteres (por ejemplo, “hola”).
– Debes ver que aparece “hola” inmediatamente (eco 1:1).
– Observa LED0: debe encenderse un parpadeo muy breve por cada carácter recibido.
4) Pruebas con Enter y símbolos:
– Pulsa Enter: deberías ver retorno de carro/line feed según el terminal.
– Teclea símbolos como “123 ABC !?”: deben volver exactamente.
5) Estabilidad:
– Mantén presionada una tecla para enviar ráfagas. El eco seguirá el ritmo del enlace (9600 bps). Si saturas el TX (por ejemplo, pegando un bloque de texto muy largo), algunos bytes podrían perderse porque el diseño básico no implementa FIFO. Esto es esperado a este nivel y se comenta en “Mejoras”.
Si el eco aparece y el LED parpadea, el objetivo está cumplido.
Troubleshooting
Listado de problemas típicos y cómo resolverlos:
1) No aparece el puerto COM/ttyUSB:
– Windows:
– Reinstala drivers FTDI (Windows Update o desde FTDI).
– Prueba otro cable USB (asegúrate de que es de datos, no solo carga).
– Cambia de puerto USB.
– Linux:
– Verifica permisos: sudo usermod -a -G dialout $USER y reinicia sesión.
– Comprueba dmesg | tail al conectar la placa.
– Asegúrate de que no hay otro proceso usando /dev/ttyUSBx (p. ej., JTAG).
2) Vivado no detecta hardware en CLI (programación falla):
– Asegúrate de tener el cable USB conectado y la placa alimentada.
– Ejecuta vivado_hwsd -query (desde Vivado Tcl Console) o reintenta open_hw_target.
– Cierra otras instancias de Vivado que pudieran bloquear el cable.
3) Eco no funciona, terminal en blanco:
– Verifica la configuración del terminal: 9600 bps, 8‑N‑1, sin flow control.
– Confirmar constraints: pines correctos para uart_rx y uart_tx; que no estén invertidos en el XDC.
– Asegura que el bitstream se programó con éxito (sin errores en consola).
– Verifica que el reloj esté mapeado a W5 y el create_clock esté en el XDC.
4) Caracteres corruptos (símbolos extraños):
– Baud rate incorrecto: asegúrate CLKS_PER_BIT=10417 para 100 MHz y 9600 bps.
– Terminal a otra velocidad (por ejemplo 115200): cambia a 9600.
– Reloj distinto al esperado: la Basys 3 usa 100 MHz; si usas otro archivo XDC con create_clock diferente, corrígelo.
5) LED0 no parpadea pero el eco funciona:
– Revisa mapping del LED0 en el XDC (U16).
– Comprueba que el puerto led0 está en el toplevel y en el XDC.
6) “Implementation failed” por conflictos de pines:
– Asegúrate de no reutilizar el mismo pin en dos puertos distintos.
– Abre el “Implemented Design” en GUI para revisar I/O Planning si es necesario.
7) Error al abrir /dev/ttyUSBx (Permission denied) en Linux:
– Añade tu usuario al grupo dialout: sudo usermod -a -G dialout $USER
– Cierra sesión y vuelve a entrar (o reinicia).
8) No hay eco en ráfagas largas:
– El diseño carece de FIFO; si el RX recibe un nuevo byte mientras el TX aún transmite, el ejemplo puede perder bytes. Ver “Mejoras” para una cola de transmisión.
Mejoras/variantes
- Añadir un buffer FIFO:
- Implementa una FIFO (p. ej., 16/32 bytes) entre RX y TX para evitar pérdida en ráfagas.
-
Estrategia: usar BRAM pequeña o registros con punteros head/tail.
-
Detección de overrun y conteo de errores:
- Señales de “overrun” si llega un byte cuando TX está ocupado y no hay espacio.
-
Contador de bytes recibidos/enviados para diagnóstico.
-
Configuración de baud rate por interruptores:
-
Por ejemplo, usar los switches SW0–SW3 para seleccionar entre 9600/19200/57600/115200. Ajustar CLKS_PER_BIT en tiempo de síntesis o, mejor, implementar un generador de baud programable con un divisor.
-
Eco con modificaciones:
-
Convertir a mayúsculas/minúsculas en tiempo real (simple LUT) para demostrar procesamiento.
-
Timeout y latido:
-
Parpadear LED a 1 Hz como “heartbeat” e indicar estado de enlace.
-
Verificación con testbench:
-
Crea un testbench Verilog que inyecte una trama UART y verifique la respuesta del TX con CLKS_PER_BIT simulado.
-
Migración a AXI UARTLite (IP):
- A nivel básico usas UART HDL simple; como variante, usa el IP “AXI UARTLite” con un MicroBlaze softcore y verifica eco en software (más complejo, no cubierto aquí).
Checklist de verificación
Marca cada punto cuando lo completes:
- [ ] Vivado 2023.2 (WebPACK) instalado y ejecuta “vivado -version”.
- [ ] Basys 3 conectada por USB y visible (Windows: COMx; Linux: /dev/ttyUSBx aparece en dmesg).
- [ ] Estructura de proyecto creada: src/ y constraints/ con los archivos provistos.
- [ ] Código Verilog copiado sin modificaciones tipográficas.
- [ ] Archivo XDC creado con pines: clk=W5, uart_rx=B18, uart_tx=A18, led0=U16; y create_clock de 10 ns.
- [ ] Ejecución de “vivado -mode batch -source scripts/build.tcl” finaliza sin errores.
- [ ] Programación exitosa (mensaje de programación completada).
- [ ] Terminal configurado a 9600‑8‑N‑1, sin control de flujo.
- [ ] Eco funcional: lo que tecleo vuelve inmediatamente.
- [ ] LED0 parpadea brevemente en cada carácter recibido.
- [ ] Opcional: Probaste ráfagas largas y anotaste si hay pérdidas (comportamiento entendido).
- [ ] Guardaste el proyecto y bitstream para reutilización.
Apéndice: Notas de precisión y coherencia con Basys 3
- Dispositivo exacto: Basys 3 (Artix‑7 XC7A35T‑1CPG236C).
- Toolchain: Vivado WebPACK 2023.2, flujo 100% por CLI (Tcl).
- Conectividad: USB‑UART integrado en la Basys 3 vía FTDI; no se requieren adaptadores externos.
- Objetivo cumplido: uart-echo-9600bps con Verilog conciso, constraints adecuados, y validación por terminal serie.
Con este caso práctico, has construido y desplegado un diseño HDL sencillo pero completo, fundamentado en la plataforma Basys 3 y la toolchain oficial de Xilinx, con instrucciones reproducibles y verificables end‑to‑end.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.



