- Um projeto de hardware que adiciona transmissão e recepção Ethernet 10BASE-T a um computador de lógica discreta feito sem CPU comercial nem chip de rede dedicado
- Sobre um adaptador de camada física 10BASE-T↔SPI criado anteriormente, foi adicionado um módulo de camada MAC para conectá-lo ao homebrew computer, com transmissor e receptor em uma arquitetura full-duplex independente
- O receptor converte dados SPI em bytes e os armazena em uma SRAM de 2 kB, verificando em hardware os 6 primeiros bytes para aceitar apenas
FE:FA:F6:F2:EE:EAou MAC de broadcast - Para simplificar o circuito, o transmissor deixa a geração do FCS e a preparação do preâmbulo para o software e suporta apenas quadros de tamanho fixo de 1024 bytes
- Foi criado até um compilador C capaz de compilar o uIP 1.0 para executar apps de rede; o resultado foi ping médio de 85 ms e download HTTP de arquivos estáticos em torno de 2.6 kB/s
Adicionando Ethernet a um computador de lógica discreta
- Como continuação do trabalho de construir um sistema computacional completo com componentes de lógica discreta, foi implementado um adaptador Ethernet capaz de executar aplicações de rede
- Antes, foi criado um adaptador de camada física que converte sinais 10BASE-T Ethernet para SPI e de volta, e na época um microcontrolador STM32 foi usado para testar o funcionamento
- O ponto central deste trabalho é um módulo de camada MAC para conectar esse adaptador ao homebrew computer
- O adaptador tem arquitetura full-duplex, e as partes de transmissão e recepção operam independentemente entre si
Receptor: armazenando dados SPI no buffer de quadros
- O receptor converte dados seriais SPI em dados paralelos por byte e extrai o clock de byte
- O endereço MAC de destino é verificado nos 6 primeiros bytes, e quadros que não atendem ao critério são rejeitados
- Os bytes recebidos são gravados em um buffer SRAM 6116 de 2 kB
- Quando o quadro termina, o receptor é desativado e não recebe quadros adicionais até ser reativado
- O contador de bytes mantém o valor mesmo após parar, para que a CPU possa ler o tamanho recebido
- O FCS não é verificado em hardware
-
Coleta de dados e acesso ao buffer
- Os dados seriais SPI entram no registrador de deslocamento
U32, enquantoU30eU31contam bits e bytes, respectivamente - O flip-flop D
U29Bgera o sinal de escrita na SRAMrecv_buf_we, que vai brevemente para nível baixo a cada 8 bits de entrada - Os bytes recebidos são gravados na SRAM 6116
U20 U13,U16eU18formam um multiplexador de endereços que seleciona para a entrada de endereço da SRAM o contador de bytes ou o barramento de endereços do sistemaU21atua como um buffer tri-state para enviar os bytes recebidos à RAM- A RAM e o contador de bytes são conectados ao barramento de dados do sistema para que a CPU possa acessar os dados recebidos e seu comprimento
U25conecta a RAM de recepção ao barramento de dados do sistema- Após a conclusão do quadro, o valor do contador de bytes é mantido no barramento
recv_byte_cnt U26eU27enviam esse valor ao barramento de dados do sistema quando há uma solicitação de leitura de endereço específico- A metade restante de
U27forma um registrador de status somente leitura de 2 bits para consultar o estado do receptor e do transmissor
- Os dados seriais SPI entram no registrador de deslocamento
Filtragem de endereço MAC em hardware
- Ao analisar o tráfego Ethernet, foi observado que os quadros normalmente chegavam em pequenos grupos de 3 a 4, separados por curtos atrasos, e mesmo dentro do mesmo grupo muitas vezes tinham endereços MAC de destino diferentes
- Como o computador talvez não fosse rápido o bastante para fazer a filtragem MAC por software e reativar o receptor, foi necessária filtragem MAC em hardware
- A ideia de armazenar um endereço MAC personalizado e compará-lo com os 6 primeiros bytes foi descartada por ser complexa demais
- Um MAC com repetição de um único byte também era possível, mas no fim o endereço MAC foi construído como função do índice do byte
- bit 0 é fixado em 0
- bit 1 é fixado em 1
- bits 2 a 4 são o valor invertido do índice do byte
- bits 5 a 7 são fixados em 1
- O endereço MAC gerado por essa regra é
FE:FA:F6:F2:EE:EA - Para o funcionamento do ARP, também é aceito o MAC de broadcast
FF:FF:FF:FF:FF:FF U33compara se o bit 0 e os bits 2 a 4 dos dados correspondem aos valores esperados, e a saída deU34Afica alta quando esses bits coincidemU35Aimplementa a verificação do MAC de broadcast, e sua saída fica alta quando o bit 0 e os bits 2 a 4 são todos 1- Os dois sinais são combinados com um OR por diodos usando
D7eR6, eU35Bverifica se todos os bits restantes também são 1 - O resultado de validade de um único byte é acumulado em
U10A- Quando nenhum quadro está sendo recebido, o sinal incoming SPI slave select
ssfica baixo eU10Aé definido como 1 - Durante a recepção do quadro, o valor é atualizado a cada byte recebido
- Se o endereço MAC de destino atender ao critério, o valor de
U10Apermanece alto - Quando o endereço de byte chega a 5, o valor final é armazenado em
U36B, e a recepção do quadro é bloqueada se o endereço de destino não corresponder
- Quando nenhum quadro está sendo recebido, o sinal incoming SPI slave select
Transmissor: simplificando o circuito com quadros de tamanho fixo
- Assim como no receptor, o transmissor também não implementa em hardware a geração do FCS e deixa isso para o software
- Para reduzir o circuito, o transmissor suporta apenas quadros de tamanho fixo
- O comprimento do quadro foi definido como 1024 bytes, um valor próximo ao MTU comum de 1500 bytes
- O preâmbulo exigido pelo 10BASE-T é composto por vários
0x55e um0xD5no final, e o software deve carregá-lo junto dentro desses 1024 bytes - O comprimento fixo do quadro não afeta os protocolos de nível superior
- Os protocolos superiores codificam o tamanho do pacote no cabeçalho
- Eles não dependem do tamanho real do quadro Ethernet
-
Fluxo de dados de transmissão
- Os dados de transmissão são armazenados em SRAM
- Um clock de 20 MHz entra em um contador de 4 bits, e a saída de overflow é usada como clock de byte
- Ao escrever qualquer valor em uma posição específica de memória write-only, o contador é ativado e a transmissão do quadro começa
- Os dados paralelos em bytes são serializados por meio de um registrador de deslocamento
- Como no receptor,
U12conta bits eU14conta bytes - O clock de 20 MHz vem de um oscilador integrado e é usado apenas após pelo menos uma divisão por 2, sem uso direto
- Isso evita que o duty cycle do oscilador afete o sinal de saída
-
RAM, registrador de deslocamento e temporização
- Para selecionar a entrada de endereço da RAM
U22, são usados três multiplexadores 74HC157, como no receptor U23é usado para carregar dados na RAMU24atua como um armazenamento intermediário para o byte atualmente em transmissão- O contador de bytes 74HC4040 é um ripple counter, então estabiliza lentamente
U24fornece uma saída estável enquanto a saída da RAM ainda não é válida- Os dados entram no registrador de deslocamento
U28e são deslocados bit a bit - Houve um bug de hardware em que a ordem dos bits conectados da RAM para o registrador de deslocamento estava errada, então foi preciso contornar isso embaralhando os bits no software
MOSIeSCKprecisam estar sincronizados com precisão para gerar um bom sinal 10BASE-TU11AeU8Bcuidam dessa sincronizaçãotx_cnt0é o bit 0 do contador de bits e usa um sinal de clock dividido por 2 a partir de 20 MHzU11Aaltera a saída de acordo com esse sinalU8Batrasa o clock para acompanhar o atraso introduzido porU11A- Um latch D é mais complexo que uma simples porta AND e adiciona cerca de 5 ns de atraso, então foi usado um 74LV74A mais rápido
- O 74LV74A é o único chip de família rápida nesta placa
- Para selecionar a entrada de endereço da RAM
Interface da CPU e mapeamento de memória
- Do ponto de vista do programador, o adaptador Ethernet aparece como uma interface mapeada em memória
- Os dois buffers de quadros são mapeados em
0xF000 - Há dois registradores somente leitura
- O registrador de status de 8 bits em
0xFB00tem os flagsRX_FULLeTX_BUSY RX_FULLindica que a recepção de um quadro foi concluídaTX_BUSYindica que um quadro está sendo transmitido- O registrador de 16 bits em
0xFB02contém o comprimento dos dados recebidos
- O registrador de status de 8 bits em
- As operações de escrita são usadas como comandos de controle
- Escrever qualquer valor em
0xFB00reativa o receptor - Escrever qualquer valor em
0xFB01inicia a transmissão
- Escrever qualquer valor em
- Como a CPU não suporta interrupções, não há interrupções
- Todos os endereços relacionados começam com
F, com os 4 bits mais altos em 1, e essa condição é verificada porU2A - Os endereços de buffer exigem que o bit 11 seja 0, o que é verificado por
U1D,D2,R2eU1E - Os endereços de registrador exigem que o segundo dígito hexadecimal seja
B, isto é,1011, verificado porU1BeU2B - Os decodificadores
U4AeU4Bsão usados para selecionar funções individuais - Dois LEDs indicam acesso ao buffer ou aos registradores
Programação e desempenho
- Havia interesse em suporte de rede, mas não em implementar uma pilha TCP/IP do zero, e programar em assembly era incômodo, então foi criado um compilador C
- Esse compilador amadureceu a ponto de conseguir compilar a pequena biblioteca TCP/IP uIP 1.0
- A densidade de código da CPU é muito baixa, mas o uIP cabe na RAM e ainda sobra espaço para aplicações reais
- O desempenho de rede é baixo, mas este é um resultado obtido sem CPU comercial nem chips especiais
- média de ida e volta do ping: 85 ms
- velocidade de download do servidor HTTP: 2.6 kB/s
- o servidor HTTP serve arquivos estáticos de um cartão SD
- Modelos, arquivos de esquemático e desenhos de PCB estão no repositório do GitHub
1 comentários
Comentários do Hacker News
Belo trabalho por compartilhar isso. Gostei especialmente do stack trace do processo de raciocínio, e é excelente do ponto de vista educacional como muita coisa foi resolvida a partir de primeiros princípios ou explicada sob a ótica de um iniciante
Mesmo que seja pouco prático para redes reais, não acho que seja só uma brincadeira. Numa era em que backdoors são encontrados em chips de rede excessivamente complexos, talvez no futuro isso acabe atraindo um público mais sério ou motivações de projeto mais fortes
Isso é para um computador totalmente customizado, o que por si só já é muito mais impressionante, e nem precisa falar da parte do “então eu fiz um compilador C”. Ainda assim, fiquei curioso sobre qual seria a implementação mínima de uma placa Ethernet para um PC “comum”
Parece que boa parte seria parecida, e talvez os checksums pudessem ser processados pela CPU do PC. A conexão teria de ser serial bruta ou, de forma mais prática, USB, e no fim provavelmente teria de usar um driver “de verdade” ou passar isso para o espaço de usuário
Ao ver coisas parecidas, pensei se não daria para fazer o dispositivo implementar https://en.wikipedia.org/wiki/USB_communications_device_clas... e assim “simplesmente funcionar” sem driver próprio, mas isso não parece combinar muito bem com deixar todos os checksums para o host
Pesquisando, também encontrei https://en.wikipedia.org/wiki/Ethernet_over_USB e talvez isso signifique que seria possível fazer um adaptador que só converte a conexão física para USB e deixa o resto para o computador cuidar
Talvez seja possível convencer um dispositivo USB no estilo FTDI a fazer bit banging de Ethernet 10base2. A ideia seria implementar só o lado “PHY”, convertendo o tráfego da linha em um fluxo de bits limpo e alinhando o início dos quadros, e deixar o PC fazer todo o resto em software
Do lado USB, o CDC-NCM em si não é difícil de implementar em qualquer MCU, mas implementar um PHY USB HS na prática exige hardware ASIC
Com um PHY ULPI USB HS de US$ 0,30, daria para implementar USB CDC-NCM em FPGA com bastante facilidade
No fim há um link para o compilador C criado para este projeto: https://github.com/imihajlow/ccpu-cc
Parece que também tem linker e libc. Não entendo de verdade o quão complexo é o projeto de hardware, mas é impressionante que ele também tenha colocado um compilador C nisso quase como algo casual
Muito impressionante. Dá vontade de fazer um projeto assim eu mesmo, e fico admirado com a paixão e as incontáveis horas necessárias para entender o sistema e depois construir tudo
Não estou exatamente ansioso para me aposentar, mas talvez seja nessa fase que eu acabe dedicando tempo a esse tipo de projeto de hardware e software
Então isso é melhor ou pior que uma Etherlink 3c501? :-D
https://mirror.math.princeton.edu/pub/oldlinux/Linux.old/net...
Se bem me lembro, era algo como pacotes novos vindos da rede sobrescreverem o buffer que a CPU estava tentando ler. Usei isso no Linux por um tempo e o desempenho era realmente péssimo
Impressiona o quanto drivers e atualizações de firmware escondem esse tipo de coisa da nossa vista
A minha é melhor porque tem dois buffers :) Mesmo assim, só um quadro recebido é armazenado
Achei interessante a parte que diz que “fixar o comprimento do quadro não afeta os protocolos de camada superior, porque eles codificam o tamanho do pacote no cabeçalho e não dependem do comprimento real do quadro Ethernet”
Recentemente fiz um decodificador de pacotes e validei explicitamente em cada camada se o comprimento da camada inferior batia. No caso de IP, no meu decodificador o comprimento do datagrama IP precisa corresponder exatamente ao comprimento do quadro Ethernet e ao tamanho do cabeçalho da camada de enlace
Não foi por capricho; eu queria detectar quadros curtos, e depois decidi tratar quadros longos também como erro. O autor usa uIP, mas fiquei curioso sobre como Linux e outros sistemas modernos lidam com isso. Também fiquei pensando se ele fez testes de interoperabilidade
Se a aplicação não olha para os dados de L2, a pilha IP do Linux simplesmente ignora isso
Fisicamente, é muito menor que o primeiro conjunto de placas Ethernet SSI da DEC: https://i.ebayimg.com/images/g/NEYAAOSw-mZlg0lZ/s-l1600.jpg
As placas DEC DEUNA têm mais de 30 cm de comprimento, mas também oferecem muito mais funcionalidades. A DEUNA é uma NIC “de verdade”: tem filas de transmissão e recepção, lida com isso de forma autônoma e também faz DMA. Claro, ela ainda roda seu próprio PDP-11 na placa para fazer tudo isso
Muito legal. Fico curioso para saber quanto tempo levou
Isso mostra como é fácil colocar um backdoor dentro de um chip conectado à porta de rede
Em um curso de Communication Systems Engineering implementei o processamento de sinais de Ethernet e depois implementei uma pilha TCP/IP com ARP e switching em assembly QUIC para Motorola 68k
Foram os 18 meses mais longos da minha vida