3 puntos por GN⁺ 2024-04-10 | 1 comentarios | Compartir por WhatsApp
  • Es un relato de construcción de hardware que agrega funciones de transmisión y recepción Ethernet 10BASE-T a una computadora de lógica discreta, hecha sin CPU comercial ni chip de red dedicado.
  • Sobre un adaptador de capa física 10BASE-T↔SPI creado anteriormente, se añadió un módulo de capa MAC para conectarlo con una homebrew computer, y el transmisor y el receptor se configuraron como una estructura full-duplex independiente.
  • El receptor convierte datos SPI en bytes, los almacena en 2 kB de SRAM y revisa en hardware los primeros 6 bytes para aceptar solo FE:FA:F6:F2:EE:EA o la MAC de broadcast.
  • Para simplificar el circuito, el transmisor deja la generación de FCS y la preparación del preámbulo al software, y solo admite tramas de longitud fija de 1024 bytes.
  • También se creó un compilador de C capaz de compilar uIP 1.0 para ejecutar apps de red; el resultado fue un ping promedio de 85 ms y descargas de archivos estáticos por HTTP de 2.6 kB/s.

Agregar Ethernet a una computadora de lógica discreta

  • Como extensión del trabajo de crear un sistema de computadora completo con componentes de lógica discreta, se implementó un adaptador Ethernet capaz de ejecutar aplicaciones de red.
  • Anteriormente se había creado un adaptador de capa física que convierte señales Ethernet 10BASE-T a SPI y viceversa, y en ese momento se usó un microcontrolador STM32 para las pruebas de funcionamiento.
  • El núcleo de este trabajo es el módulo de capa MAC para conectar ese adaptador a la homebrew computer.
  • El adaptador tiene una estructura full-duplex, y las partes de transmisión y recepción funcionan de manera independiente.

Receptor: guardar datos SPI en un búfer de tramas

  • El receptor convierte los datos seriales SPI en datos paralelos por byte y extrae el reloj de bytes.
  • La dirección MAC de destino se revisa en los primeros 6 bytes, y las tramas que no cumplen el criterio se rechazan.
  • Los bytes recibidos se escriben en un búfer SRAM 6116 de 2 kB.
  • Cuando termina la trama, el receptor se desactiva y no recibe tramas adicionales hasta que se vuelva a activar.
  • El contador de bytes mantiene su valor incluso después de detenerse, para que la CPU pueda leer la longitud recibida.
  • El FCS no se verifica en hardware.
  • Captura de datos y acceso al búfer

    • Los datos seriales SPI entran al registro de desplazamiento U32, y U30 y U31 cuentan bits y bytes, respectivamente.
    • El flip-flop D U29B genera la señal de escritura de SRAM recv_buf_we, y esta señal baja brevemente cada 8 bits de datos de entrada.
    • Los bytes recibidos se escriben en la SRAM 6116 U20.
    • U13, U16 y U18 forman un multiplexor de direcciones que elige como entrada de dirección de la SRAM el contador de bytes o el bus de direcciones del sistema.
    • U21 actúa como búfer de tres estados que entrega los bytes recibidos a la RAM.
    • La RAM y el contador de bytes se conectan al bus de datos del sistema para que la CPU pueda acceder a los datos recibidos y a su longitud.
    • U25 conecta la RAM de recepción al bus de datos del sistema.
    • Después de completarse la trama, el valor del contador de bytes se mantiene en el bus recv_byte_cnt.
    • U26 y U27 entregan este valor al bus de datos del sistema cuando llega una solicitud de lectura a una dirección específica.
    • La otra mitad de U27 crea un registro de estado de solo lectura de 2 bits para consultar el estado del receptor y del transmisor.

Filtrado de direcciones MAC en hardware

  • Al analizar el tráfico Ethernet, se observó que las tramas generalmente llegaban en pequeños grupos de 3 a 4 unidades, separados por retardos breves, y que incluso dentro de un mismo grupo a menudo tenían direcciones MAC de destino distintas.
  • Como la computadora podría no ser lo bastante rápida para hacer filtrado MAC por software y reactivar el receptor, se necesitaba filtrado MAC en hardware.
  • Se descartó el método de guardar una dirección MAC personalizada y compararla con los primeros 6 bytes porque era demasiado complejo.
  • También era posible usar una dirección MAC formada por un único byte repetido, pero finalmente se creó la dirección MAC como función del índice de byte:
    • el bit 0 queda fijo en 0
    • el bit 1 queda fijo en 1
    • los bits 2~4 son el valor invertido del índice de byte
    • los bits 5~7 quedan fijos en 1
  • La dirección MAC generada con esta regla es FE:FA:F6:F2:EE:EA.
  • Para que funcione ARP, también se acepta la MAC de broadcast FF:FF:FF:FF:FF:FF.
  • U33 compara si el bit 0 y los bits 2~4 de los datos coinciden con los valores deseados, y la salida de U34A se pone en alto cuando esos bits coinciden.
  • U35A implementa la comprobación de la MAC de broadcast, y su salida se pone en alto cuando el bit 0 y los bits 2~4 son todos 1.
  • Las dos señales se combinan con un OR de diodos usando D7 y R6, y U35B verifica que todos los bits restantes sean 1.
  • El resultado de validez de un byte individual se acumula en U10A:
    • cuando no se está recibiendo una trama, la señal incoming SPI slave select ss está baja y U10A se fija en 1
    • durante la recepción de una trama, el valor se actualiza con cada byte recibido
    • si la dirección MAC de destino cumple el criterio, el valor de U10A se mantiene en alto
    • cuando la dirección de byte llega a 5, el valor final se enclava en U36B, y si la dirección de destino no coincide se bloquea la recepción de la trama

Transmisor: simplificar el circuito con tramas de longitud fija

  • Al igual que el receptor, el transmisor no implementa la generación de FCS en hardware, sino que la maneja por software.
  • Para reducir el circuito, el transmisor solo admite tramas de longitud fija.
  • La longitud de trama elegida fue de 1024 bytes, un valor cercano al MTU típico de 1500 bytes.
  • El preámbulo necesario para 10BASE-T está compuesto por varios 0x55 y un 0xD5 final, y el software debe cargarlo también dentro de esos 1024 bytes.
  • La longitud fija de trama no afecta a los protocolos superiores:
    • los protocolos superiores codifican el tamaño del paquete en la cabecera
    • no dependen de la longitud real de la trama Ethernet
  • Flujo de datos de transmisión

    • Los datos de transmisión se almacenan en SRAM.
    • Un reloj de 20 MHz entra a un contador de 4 bits, y la salida de overflow se usa como reloj de bytes.
    • Al escribir un valor en una ubicación de memoria específica de solo escritura, el contador se activa y comienza la transmisión de la trama.
    • Los datos paralelos por byte se serializan mediante un registro de desplazamiento.
    • Igual que en el receptor, U12 cuenta los bits y U14 cuenta los bytes.
    • El reloj de 20 MHz proviene de un oscilador integrado, y no se usa directamente, sino como mínimo dividido por 2.
    • Este método evita que el ciclo de trabajo del oscilador afecte a la señal de salida.
  • RAM, registro de desplazamiento y temporización

    • Para seleccionar la entrada de dirección de la RAM U22, se usan tres multiplexores 74HC157 como en el receptor.
    • U23 se usa para cargar datos en la RAM.
    • U24 actúa como almacenamiento intermedio del byte que se está transmitiendo actualmente.
    • El contador de bytes 74HC4040 es un contador ripple, por lo que tarda en estabilizarse.
    • Mientras la salida de la RAM aún no es válida, U24 entrega una salida estable.
    • Los datos entran al registro de desplazamiento U28 y se desplazan bit a bit.
    • Hubo un bug de hardware por conectar mal el orden de bits que va de la RAM al registro de desplazamiento, por lo que hubo que sortearlo mezclando bits en software.
    • MOSI y SCK deben estar sincronizados con precisión para generar una buena señal 10BASE-T.
    • U11A y U8B se encargan de esta sincronización.
    • tx_cnt0 es el bit 0 del contador de bits, y se usa como reloj con una señal de 20 MHz dividida por 2.
    • U11A cambia su salida de acuerdo con esta señal.
    • U8B retrasa el reloj para ajustarlo al retardo generado por U11A.
    • Un latch D es más complejo que una compuerta AND simple y tiene unos 5 ns más de retardo, por lo que se usa el 74LV74A, más rápido.
    • El 74LV74A es el único chip de una familia rápida en esta placa.

Interfaz de CPU y mapeo de memoria

  • Desde el punto de vista del programador, el adaptador Ethernet se ve como una interfaz mapeada en memoria.
  • Los dos búferes de tramas se mapean en 0xF000.
  • Hay dos registros de solo lectura:
    • el registro de estado de 8 bits en 0xFB00 tiene las banderas RX_FULL y TX_BUSY
    • RX_FULL indica que la recepción de una trama se completó
    • TX_BUSY indica que se está transmitiendo una trama
    • el registro de 16 bits en 0xFB02 contiene la longitud de los datos recibidos
  • Las operaciones de escritura se usan como comandos de control:
    • escribir cualquier valor en 0xFB00 vuelve a activar el receptor
    • escribir cualquier valor en 0xFB01 inicia la transmisión
  • Como la CPU no soporta interrupciones, no hay interrupciones.
  • Todas las direcciones relacionadas comienzan con F, donde los 4 bits superiores son 1; esta condición la verifica U2A.
  • La dirección del búfer requiere que el bit 11 sea 0, y U1D, D2, R2 y U1E lo verifican.
  • La dirección de los registros requiere que el segundo dígito hexadecimal sea B, es decir 1011, y U1B y U2B lo comprueban.
  • Los decodificadores U4A y U4B se usan para seleccionar funciones individuales.
  • Dos LED indican accesos al búfer o a los registros.

Programación y rendimiento

  • Aunque se quería soporte de red, no se quería implementar directamente un stack TCP/IP, y programar en ensamblador era incómodo, así que se creó un compilador de C.
  • Este compilador es lo bastante maduro como para compilar uIP 1.0, una pequeña biblioteca TCP/IP.
  • La densidad de código de la CPU es muy baja, pero uIP cabe en la RAM y todavía queda espacio para aplicaciones reales.
  • El rendimiento de red es bajo, pero es un resultado logrado sin CPU comercial ni chips especiales:
    • promedio de ida y vuelta de ping: 85 ms
    • velocidad de descarga del servidor HTTP: 2.6 kB/s
    • el servidor HTTP ofrece archivos estáticos desde una tarjeta SD
  • Los modelos, archivos de esquemáticos y diseños de PCB están en el repositorio de GitHub.

1 comentarios

 
GN⁺ 2024-04-10
Opiniones en Hacker News
  • Excelente trabajo, gracias por compartirlo. Me gustó especialmente el stack trace del proceso de razonamiento, y es muy educativo que haya resuelto muchas cosas desde primeros principios o haya intentado explicarlas desde la perspectiva de alguien principiante.
    Aunque no sea práctico para redes reales, no creo que sea solo un juguete. En una época en la que se descubren backdoors en chips de red excesivamente complejos, en el futuro podría atraer a un público más serio o dar lugar a motivaciones de proyecto más serias.

    • Me pregunto cuántas vulnerabilidades ocultas dentro del silicio moderno habrá. Si casi todos los días aparecen vulnerabilidades incluso en código de unos pocos miles de líneas, dentro del silicio cableado hay microchips que, en la práctica, equivalen a miles de millones de líneas de código.
  • Esto es para una computadora totalmente personalizada, así que ya de por sí es mucho más impresionante, y ni hablar de la parte de “entonces hice un compilador de C”. Aun así, me da curiosidad cuál sería la implementación mínima de una tarjeta Ethernet para una PC “normal”.
    Supongo que buena parte sería parecida, y el checksum podría dejarse en manos del CPU de la PC. La conexión tendría que ser serial cruda o, de forma más práctica, USB, y al final habría que usar un driver “real” o pasarlo al espacio de usuario para procesarlo.
    Al ver cosas similares pensé que, si el dispositivo implementara https://en.wikipedia.org/wiki/USB_communications_device_clas..., tal vez podría “simplemente funcionar” sin un driver propio, pero eso no parece encajar muy bien con hacer todos los checksums del lado del host.
    Buscando también encontré https://en.wikipedia.org/wiki/Ethernet_over_USB, aunque no sé si eso significa que se podría hacer un adaptador que solo convierta la conexión física a USB y deje que la computadora se encargue del resto.

    • USB puede considerarse mucho más complejo que el Ethernet 10base2 de hace muchos años. Si intentas conectarte a una red Ethernet 10base2 mediante PCIe o USB, ambos implican mucho más trabajo que la parte Ethernet.
      Tal vez se pueda convencer a un dispositivo USB estilo FTDI de hacer bit-banging de Ethernet 10base2. Sería implementar solo la parte “PHY” que convierte el tráfico de la línea en un flujo de bits limpio y alinea el inicio de la trama, y que la PC haga todo lo demás por software.
    • Si las PC normales todavía tuvieran un bus ISA como hace 30 años, mi tarjeta de red podría conectarse ahí con solo pequeñas modificaciones.
    • Implementar una NIC en FPGA es bastante común, normalmente junto con una conexión PCIe.
      En el caso de USB, CDC-NCM en sí no es difícil de implementar en cualquier MCU, pero implementar un PHY USB HS en la práctica requiere hardware ASIC.
      Usando un PHY USB HS ULPI de 0.30 dólares, debería ser bastante fácil implementar USB CDC-NCM en una FPGA.
  • Al final hay un enlace al compilador de C creado para este proyecto: https://github.com/imihajlow/ccpu-cc
    Parece que también tiene linker y libc. No sé bien qué tan complejo es el diseño de hardware, pero es impresionante que haya hecho y conectado “ligeramente” un compilador de C.

    • Es un compilador de C escrito en Rust, y usa el crate lang_c para el parsing del lenguaje.
  • Realmente impresionante. Me dan ganas de intentar hacer un proyecto así, y admiro la pasión y las incontables horas invertidas en entender el sistema y luego construirlo.
    No es que esté esperando jubilarme, pero tal vez para entonces termine dedicando tiempo a este tipo de proyectos de hardware y software.

  • Entonces, ¿es mejor o peor que una Etherlink 3c501? :-D
    https://mirror.math.princeton.edu/pub/oldlinux/Linux.old/net...
    Si mal no recuerdo, un paquete nuevo que entraba por la red sobrescribía el buffer que el CPU estaba intentando leer. La usé durante un tiempo en Linux y el rendimiento era realmente malo.

  • Me pareció interesante la parte que dice: “Fijar la longitud de la trama no afecta a los protocolos superiores, porque estos codifican el tamaño del paquete en sus headers y no dependen de la longitud real de la trama Ethernet”.
    Hace poco hice un decodificador de paquetes, y en cada capa validé explícitamente que la longitud de la capa inferior coincidiera. En el caso de IP, en mi decodificador la longitud del datagrama IP tiene que coincidir exactamente con la longitud de la trama Ethernet y la longitud del header de la capa de enlace.
    No fue por ser meticuloso, sino para detectar tramas cortas, y después decidí tratar también las tramas largas como error. El autor usa uIP, pero me da curiosidad cómo lo manejan Linux u otros sistemas operativos modernos. También me pregunto si hizo pruebas de interoperabilidad.

    • Estoy enviando tramas largas a la red y ninguno de los sistemas operativos que tengo mostró problemas. Leí en algún lado que algunos routers usan tramas largas en la práctica para guardar metadatos después del paquete.
    • Los timestamps y otras formas de telemetría de red in-band a veces se insertan en las tramas como trailers. En ese caso se adjunta un FCS nuevo.
      Si la aplicación no ve los datos L2, el stack IP de Linux simplemente los ignora.
  • Es físicamente mucho más pequeña que el primer conjunto de tarjetas Ethernet SSI de DEC: https://i.ebayimg.com/images/g/NEYAAOSw-mZlg0lZ/s-l1600.jpg
    Las placas DEC DEUNA miden más de un pie de largo, pero también tienen muchas más funciones. DEUNA es una NIC “real”: tiene colas de transmisión y recepción, las maneja de forma autónoma y también hace DMA. Claro que además tiene su propio PDP-11 en la tarjeta para ejecutar eso.

  • Muy bueno. Me pregunto cuánto tiempo le tomó.

    • Hacer el módulo de red tomó alrededor de un mes, pero escribir el compilador llevó mucho más tiempo.
  • Muestra lo fácil que es meter un backdoor dentro de un chip conectado al puerto de red.

  • En el curso de Communication Systems Engineering implementé procesamiento de señales Ethernet y luego implementé un stack TCP/IP, incluyendo ARP y switching, en ensamblador Motorola 68k QUIC.
    Fueron los 18 meses más largos de mi vida.