16 KiB
Verilog Roadmap: De Turing Complete a FPGA
Objetivo: traducir tu CPU custom de 16 bits (Turing Complete) a Verilog sintetizable y montarla en la Tang Primer 20K (Gowin GW2A).
Prerequisitos que ya tienes:
- Logica digital (puertas, muxes, flip-flops, FSMs) — via Turing Complete
- Arquitectura de CPU de 16 bits — tu CPU custom
- Lenguaje j-lang + compilador para tu CPU
- Toolchain: iverilog + vvp + gtkwave funcionando
- Primer ejercicio (half_adder) completado en 3 estilos
Tu CPU (mycpu_v2):
- 16 registros de 16 bits (REG0-REG11 generales, REG12=RAM_VAL, REG13=RAM_ADDR, REG14=PC, REG15=IN/OUT)
- Instrucciones de 64 bits: [OPCODE:16][PARAM1:16][PARAM2:16][TARGET:16]
- Modo de direccionamiento en bits 7-6 del opcode (reg/reg, imm/reg, reg/imm, imm/imm)
- 12 ops ALU (ADD,SUB,MUL,DIV,AND,OR,NOT,NAND,NOR,XOR,XNOR,NEG)
- 6 condicionales (EQ,NEQ,LS,LSE,GR,GRE) — branch si condicion true
- Stack: PUSH, POP, RSTR
- Funciones: CALL, RET, HALT
- ISA completo en: D:\Proyectos\c-labs\j-lang\mycpu_v2.md
FPGA target: Tang Primer 20K (Gowin GW2A-LV18PG256C8/I7)
- 20736 LUT4 | 15552 FF | 828K BSRAM | 48 multiplicadores 18x18
- 128MB DDR3 | 32Mbit Flash | JTAG+UART debug | 117 IO
Fase 1: Bloques combinacionales fundamentales
Cosas que ya conoces del juego, ahora las escribes en Verilog. Cada ejercicio: modulo + testbench + simulacion con gtkwave.
1.1 Full Adder
- Carpeta:
full_adder/ - Objetivo: Combinar dos half adders para hacer un full adder (con carry-in)
- Practica: Hacerlo structural (instanciando tu half_adder) y dataflow
- Concepto Verilog: instanciacion de modulos, jerarquia
1.2 Adder de N bits (ripple carry)
- Carpeta:
adder_nbit/ - Objetivo: Sumador parametrizable de N bits usando
generateyparameter - Practica: Parametrizarlo a 4, 8 y 16 bits desde el testbench
- Concepto Verilog:
parameter,generate for, buses[N-1:0]
1.3 ALU basica
- Carpeta:
alu_basic/ - Objetivo: ALU de 16 bits con las 12 operaciones de tu CPU: ADD, SUB, MUL, DIV, AND, OR, NOT, NAND, NOR, XOR, XNOR, NEG
- Practica: Selector de operacion de 4 bits (opcode[3:0]), flags (zero, carry, negative)
- Concepto Verilog:
case, operadores aritmeticos, concatenacion{} - Conexion directa: Este modulo es tu
alu.vfinal casi tal cual
1.4 Multiplexor y Demultiplexor
- Carpeta:
mux_demux/ - Objetivo: Mux 2:1, 4:1 y 8:1 parametrizables. Demux 1:4
- Practica: Mux con
assignternario vscasevsgenerate - Concepto Verilog: operador ternario
? :, seleccion por bits
1.5 Decoder / Encoder
- Carpeta:
decoder_encoder/ - Objetivo: Decoder 3:8, encoder con prioridad
- Practica: Usar
casezpara don't cares - Concepto Verilog:
casez,casex, wildcards
Fase 2: Bloques secuenciales
Aqui entran los flip-flops y el clock. Es el salto mas importante desde la logica combinacional.
2.1 Flip-Flop D y registro
- Carpeta:
flip_flop/ - Objetivo: FF-D con reset sincrono y asincrono, enable
- Practica: Registro de N bits parametrizable
- Concepto Verilog:
always @(posedge clk),posedge/negedge, blocking vs non-blocking (=vs<=)
CRITICO: En logica secuencial SIEMPRE usa
<=(non-blocking). En combinacional usa=(blocking). Mezclarlos es el error #1 de Verilog.
2.2 Contador
- Carpeta:
counter/ - Objetivo: Contador up/down de N bits con enable, load y reset
- Practica: Hacerlo parametrizable, probarlo a distintas frecuencias
- Concepto Verilog: Aritmetica en always secuencial, overflow
2.3 Shift Register
- Carpeta:
shift_register/ - Objetivo: Registro de desplazamiento con carga paralela, shift left/right
- Practica: SISO, SIPO, PISO, PIPO
- Concepto Verilog: Operadores de shift en contexto secuencial
2.4 Register File
- Carpeta:
register_file/ - Objetivo: Banco de 16 registros de 16 bits (como tu CPU: REG0-REG15), 2 puertos de lectura simultanea, 1 de escritura
- Practica: Lectura combinacional, escritura sincrona. Registros especiales (REG12=RAM_VAL, REG13=RAM_ADDR, REG14=PC, REG15=IO) pueden tener logica extra
- Concepto Verilog: Arreglos
reg [15:0] regs [0:15], indexado por variable de 4 bits
Fase 3: Memorias
3.1 RAM sincrona
- Carpeta:
ram/ - Objetivo: RAM de 256x16 (256 posiciones de 16 bits), lectura/escritura sincrona
- Practica: Single port y dual port
- Concepto Verilog: Inferencia de block RAM,
$readmemhpara cargar datos iniciales
3.2 ROM
- Carpeta:
rom/ - Objetivo: ROM de 256x16 con contenido inicializado desde archivo .hex
- Practica: Cargar un programa simple en hex y leerlo secuencialmente
- Concepto Verilog:
$readmemh,initial begin - Conexion con tu proyecto: Aqui eventualmente cargaras el binario que genera tu compilador j-lang
3.3 Stack (LIFO)
- Carpeta:
stack/ - Objetivo: Stack de 256 posiciones de 16 bits con PUSH, POP y RSTR (reset)
- Practica: Puntero de stack, deteccion de overflow/underflow
- Conexion directa: Tu CPU usa PUSH (0x19/0x59), POP (0x1A), RSTR (0x18) y CALL/RET que tambien usan el stack para guardar/restaurar el PC
Fase 4: Maquinas de estados finitos (FSM)
Ya las conoces de Turing Complete. Ahora las codificas formalmente.
4.1 FSM basica — Semaforo
- Carpeta:
fsm_traffic/ - Objetivo: Semaforo con estados RED, GREEN, YELLOW y temporizador
- Practica: FSM con patron de 2 always (estado_actual + logica_siguiente)
- Concepto Verilog:
localparampara estados, el patron clasico de FSM
4.2 FSM — Controlador UART TX (simplificado)
- Carpeta:
uart_tx/ - Objetivo: Transmitir un byte por UART (start bit, 8 data, stop bit)
- Practica: Baud rate configurable con counter, FSM de transmision
- Concepto: FSM real que usaras para debug en FPGA
Fase 5: Pipeline y CPU simples
Aqui empiezas a armar las piezas en subsistemas tipo CPU.
5.1 Fetch unit
- Carpeta:
fetch_unit/ - Objetivo: Program Counter + ROM = fetch de instrucciones
- Practica: PC con increment, load (para jumps), reset
- Conexion: Este es el primer pedazo real de tu CPU
5.2 Decoder de instrucciones
- Carpeta:
instruction_decoder/ - Objetivo: Tomar una instruccion de 64 bits y extraer:
opcode[7:0]— operacion + tipo de grupoopcode[7:6]— modo de direccionamiento (00=R/R, 01=I/R, 10=R/I, 11=I/I)opcode[5:4]— grupo (00=ALU, 01=COND, 01=CTRL, 10=FUNC)opcode[3:0]— operacion dentro del grupoparam1[15:0]— registro o inmediato segun modoparam2[15:0]— registro o inmediato segun modotarget[15:0]— registro destino o direccion de salto
- Practica: Generar senales de control: alu_op, reg_write, mem_read, mem_write, branch, stack_op, is_immediate1, is_immediate2
- Conexion: Mapeo directo del ISA de mycpu_v2
5.3 CPU single-cycle minima
- Carpeta:
cpu_minimal/ - Objetivo: CPU simplificada que ejecuta un subconjunto de tu ISA: ADD (reg,imm), SUB, AND, OR — EQ, NEQ — PUSH, POP — CALL, RET, HALT
- Practica: Conectar fetch + decode + ALU + register file + RAM + stack
- Concepto: Datapath + Control Unit, todo single-cycle
- Nota: Tu instruccion es de 64 bits (4 words), asi que el fetch necesita 4 ciclos de lectura de ROM o una ROM con palabra de 64 bits
5.4 Programa de prueba
- Objetivo: Escribir a mano un programa en hex que sume numeros del 1 al 10
- Practica: Cargarlo en la ROM con
$readmemh, simular, verificar resultado - Conexion: Eventualmente esto lo hara tu compilador j-lang automaticamente
Fase 6: TU CPU de Turing Complete en Verilog
Aqui es donde todo converge.
6.1 Documentar tu ISA (ya tienes mycpu_v2.md — completar)
- Carpeta:
tc_cpu/docs/ - Base: Ya tienes mycpu_v2.md con registros, opcodes y modos
- Que falta agregar para Verilog:
- Diagrama de bloques del datapath (que se conecta con que)
- Ciclo de ejecucion: fetch (4 words) -> decode -> execute -> writeback
- Como funciona el acceso a RAM (escribir REG13=addr, leer/escribir REG12=data)
- Comportamiento exacto de CALL (push PC al stack, jump) y RET (pop PC del stack)
- Comportamiento de RSTR (reset del stack pointer?)
- Que pasa con flags/condiciones: se compara en el mismo ciclo?
- Senales de I/O via REG15: protocolo?
6.2 Implementar modulos individuales
- Carpeta:
tc_cpu/rtl/ - Objetivo: Cada bloque testeado independientemente
- Modulos concretos de tu CPU:
Modulo Archivo Descripcion ALU alu.v12 ops (ADD-NEG), entradas 16bit, flags Z/C/N Register File register_file.v16 regs x 16bit, 2 read + 1 write, regs especiales Program Counter pc.vIncrement por 4 words, load para branch/CALL Instruction Memory imem.vROM de 64 bits de ancho (o 4 lecturas de 16 bits) Data Memory (RAM) dmem.vRAM 16 bit, interfaz via REG12/REG13 Stack stack.vPUSH/POP/RSTR, para datos y CALL/RET Control Unit control_unit.vDecode opcode[7:0] -> senales de control Mux de operandos operand_mux.vSelecciona reg o inmediato segun opcode[7:6] Comparador comparator.vEQ/NEQ/LS/LSE/GR/GRE para branches I/O Controller io_controller.vInterfaz REG15 con el mundo exterior
6.3 Integrar el top-level
- Archivo:
tc_cpu/rtl/cpu_top.v - Objetivo: Conectar todos los modulos
- Ciclo de ejecucion probable (multi-ciclo o FSM):
- FETCH: Leer 4 words consecutivos de IMEM -> registro de instruccion de 64 bits
- DECODE: Extraer opcode, modo, param1, param2, target. Generar senales de control
- EXECUTE: ALU o comparacion. Resolver operandos (reg/imm mux)
- MEMORY: Si opcode accede a RAM (via REG12/REG13), hacer read/write
- WRITEBACK: Escribir resultado en registro target
- Nota: Como tus instrucciones son de 64 bits, probablemente sea multi-ciclo (4 ciclos de fetch + 1-2 de execute). Alternativa: ROM de 64 bits de ancho
6.4 Compilar j-lang -> hex -> simular en tu CPU Verilog
- Objetivo: Cerrar el loop completo
- Flujo:
codigo.j-> compilador j-lang ->.hex->$readmemh-> simulacion iverilog - Test sugerido: Un programa j-lang simple (fibonacci, factorial, algo con loops)
- Verificacion: Comparar traza de ejecucion en gtkwave vs ejecucion en Turing Complete
- Esto es el momento "wow": el mismo programa que corria en Turing Complete ahora corre en tu CPU Verilog
Fase 7: FPGA — Tang Primer 20K
Tu placa: Tang Primer 20K con Gowin GW2A-LV18PG256C8/I7.
Recursos de tu FPGA vs lo que necesita tu CPU
Recurso | Disponible | Tu CPU necesita (estimado)
-----------------|---------------|---------------------------
LUT4 | 20,736 | ~2,000-4,000 (sobra mucho)
Flip-Flops | 15,552 | ~500-1,000
BSRAM (828Kb) | 46 bloques | ~2-4 bloques (IMEM + DMEM)
Multiplicador | 48 (18x18) | 1 (para MUL de 16 bits)
PLL | 4 | 1 (para generar tu clock)
IO | 117 | ~20-30 (UART, LEDs, botones)
Te sobra FPGA para tu CPU. Podrias meter varias instancias si quisieras.
7.1 Instalar Gowin EDA y primer proyecto
- Toolchain: Gowin EDA (Education Edition es gratuita)
- Descarga: https://www.gowinsemi.com (necesitas registro)
- Incluye: Gowin Synthesizer, Place & Route, Programmer
- Primer proyecto: Blink LED
- Aprender: crear proyecto, seleccionar chip GW2A-LV18PG256C8/I7
- Constraints (.cst): asignar pines fisicos a senales
- Clock: la placa tiene un oscilador de 27 MHz, usar PLL para tu frecuencia
- Flujo: Synthesis -> Place & Route -> Generate Bitstream -> Program
- Dock vs Lite: Dependiendo de que placa de expansion uses, los pines cambian. El Dock trae mas LEDs, botones y conectores
7.2 Adaptar tu CPU para sintesis
- Cambios necesarios:
- Quitar todo
$display,$dumpfile,$dumpvars,#delays(solo simulacion) - IMEM: Usar inicializacion de BSRAM de Gowin (soporta
$readmemhen sintesis para block RAM, pero verificar con Gowin Synthesizer) - DMEM: Inferir BSRAM (el synthesizer lo detecta si sigues el patron correcto)
- MUL: Gowin puede mapear
*a los multiplicadores DSP hardware automaticamente - Clock: Usar la PLL Gowin (PLLVR primitive) para generar un clock limpio
- Reset: Usar el boton de la placa como reset global (con debouncing)
- Quitar todo
7.3 Agregar I/O real
- Basico (debug):
- LEDs de la placa -> mostrar estado/registro/flags
- Botones -> reset, step (ejecutar instruccion a instruccion), run
- 7-segmentos (si hay en el dock) -> mostrar PC o valor de registro
- UART (esencial para debug):
- Ya la habras hecho en Fase 4.2, conectarla al REG15 (IN/OUT)
- Permite enviar/recibir datos entre PC y tu CPU via terminal serial
- Debug: imprimir valor de registros, estado del PC, etc.
- Avanzado (futuro):
- LCD via el conector de 8 pines
- Tarjeta SD para cargar programas sin resintesis
- DDR3 para expandir RAM (requiere controlador DDR3, mas complejo)
7.4 Meta final
- Tu CPU ejecutando un programa compilado con j-lang en hardware real
- Poder cargar programas y ver resultados via UART desde tu PC
- El loop completo:
codigo.j-> compilador ->.hex-> FPGA -> resultados reales
Consejos generales
Estructura de cada ejercicio
nombre_ejercicio/
nombre.v # modulo principal
nombre_tb.v # testbench
readme.md # notas, comandos, que aprendiste
Comandos (ya los conoces)
iverilog -o modulo_tb.vvp modulo_tb.v
vvp modulo_tb.vvp
gtkwave modulo.vcd
Errores comunes a evitar
- Blocking vs non-blocking:
=en combinacional,<=en secuencial. Siempre. - Latch inferido: Si usas
always @(*)conifsinelse, Verilog infiere un latch. Siempre ponelseo valores por defecto. - Sensitivity list incompleta: Usa
always @(*)para combinacional, nunca listes senales manualmente (es error-prone). - Senales no inicializadas en testbench: Todo
regempieza enxsi no lo inicializas. - Confusion wire vs reg:
wirepara assign y conexiones,regpara always blocks.
Recursos recomendados
- HDLBits (hdlbits.01xz.net) — Ejercicios interactivos de Verilog online. Excelente complemento para practicar sintaxis.
- "Digital Design and Computer Architecture" (Harris & Harris) — El libro de referencia para ir de compuertas a CPU. Tiene ejemplos en Verilog.
- Nand2Tetris — Ya hiciste algo similar con Turing Complete, pero el libro tiene buena formalizacion del ISA y el compilador.
Progreso
- Fase 1.0 — Half Adder (3 estilos + testbench)
- Fase 1.1 — Full Adder
- Fase 1.2 — Adder N bits
- Fase 1.3 — ALU basica
- Fase 1.4 — Mux / Demux
- Fase 1.5 — Decoder / Encoder
- Fase 2.1 — Flip-Flop D y registro
- Fase 2.2 — Contador
- Fase 2.3 — Shift Register
- Fase 2.4 — Register File
- Fase 3.1 — RAM sincrona
- Fase 3.2 — ROM
- Fase 3.3 — Stack
- Fase 4.1 — FSM Semaforo
- Fase 4.2 — UART TX
- Fase 5.1 — Fetch Unit
- Fase 5.2 — Decoder de instrucciones
- Fase 5.3 — CPU single-cycle minima
- Fase 5.4 — Programa de prueba
- Fase 6.1 — Documentar ISA de tu CPU
- Fase 6.2 — Modulos individuales de tu CPU
- Fase 6.3 — Top-level integrado
- Fase 6.4 — j-lang -> hex -> simulacion
- Fase 7.1 — Gowin EDA + blink LED en Tang Primer 20K
- Fase 7.2 — CPU sintetizable (quitar sim-only, inferir BSRAM, PLL)
- Fase 7.3 — I/O real (LEDs, botones, UART)
- Fase 7.4 — j-lang corriendo en hardware real