Files
hdl-projects/roadmap.md
Jose Luis Montañes Ojados 42811f868b add rom
2026-03-01 03:44:57 +01:00

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 generate y parameter
  • 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.v final 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 assign ternario vs case vs generate
  • Concepto Verilog: operador ternario ? :, seleccion por bits

1.5 Decoder / Encoder

  • Carpeta: decoder_encoder/
  • Objetivo: Decoder 3:8, encoder con prioridad
  • Practica: Usar casez para 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, $readmemh para 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: localparam para 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 grupo
    • opcode[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 grupo
    • param1[15:0] — registro o inmediato segun modo
    • param2[15:0] — registro o inmediato segun modo
    • target[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.v 12 ops (ADD-NEG), entradas 16bit, flags Z/C/N
    Register File register_file.v 16 regs x 16bit, 2 read + 1 write, regs especiales
    Program Counter pc.v Increment por 4 words, load para branch/CALL
    Instruction Memory imem.v ROM de 64 bits de ancho (o 4 lecturas de 16 bits)
    Data Memory (RAM) dmem.v RAM 16 bit, interfaz via REG12/REG13
    Stack stack.v PUSH/POP/RSTR, para datos y CALL/RET
    Control Unit control_unit.v Decode opcode[7:0] -> senales de control
    Mux de operandos operand_mux.v Selecciona reg o inmediato segun opcode[7:6]
    Comparador comparator.v EQ/NEQ/LS/LSE/GR/GRE para branches
    I/O Controller io_controller.v Interfaz 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):
    1. FETCH: Leer 4 words consecutivos de IMEM -> registro de instruccion de 64 bits
    2. DECODE: Extraer opcode, modo, param1, param2, target. Generar senales de control
    3. EXECUTE: ALU o comparacion. Resolver operandos (reg/imm mux)
    4. MEMORY: Si opcode accede a RAM (via REG12/REG13), hacer read/write
    5. 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)
  • 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 $readmemh en 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)

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

  1. Blocking vs non-blocking: = en combinacional, <= en secuencial. Siempre.
  2. Latch inferido: Si usas always @(*) con if sin else, Verilog infiere un latch. Siempre pon else o valores por defecto.
  3. Sensitivity list incompleta: Usa always @(*) para combinacional, nunca listes senales manualmente (es error-prone).
  4. Senales no inicializadas en testbench: Todo reg empieza en x si no lo inicializas.
  5. Confusion wire vs reg: wire para assign y conexiones, reg para 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