- Ein Hardware-Baubericht über die Erweiterung eines Computers aus diskreter Logik um 10BASE-T-Ethernet-Sende- und Empfangsfunktionen – ohne kommerzielle CPU oder dedizierten Netzwerkchip
- Auf einem zuvor gebauten 10BASE-T↔SPI-Physical-Layer-Adapter wurde ein MAC-Layer-Modul ergänzt, um ihn mit einem Homebrew-Computer zu verbinden; Sender und Empfänger sind als unabhängige Full-Duplex-Struktur ausgelegt
- Der Empfänger wandelt SPI-Daten in Bytes um, speichert sie in 2 kB SRAM und prüft die ersten 6 Bytes in Hardware, sodass nur
FE:FA:F6:F2:EE:EAoder Broadcast-MACs akzeptiert werden - Um die Schaltung zu vereinfachen, überlässt der Sender FCS-Erzeugung und Präambel-Vorbereitung der Software und unterstützt nur Frames fester Länge von 1024 Bytes
- Es wurde sogar ein C-Compiler gebaut, der uIP 1.0 kompilieren kann, um Netzwerk-Apps auszuführen; das Ergebnis liegt bei durchschnittlich 85 ms Ping und 2,6 kB/s beim Download statischer HTTP-Dateien
Ethernet an einen Computer aus diskreter Logik anschließen
- Als Fortsetzung des Projekts, ein vollständiges Computersystem aus diskreten Logikbausteinen zu bauen, wurde ein Ethernet-Adapter implementiert, der Netzwerk-Anwendungen ausführen kann
- Zuvor wurde ein Physical-Layer-Adapter gebaut, der 10BASE-T-Ethernet-Signale in SPI umwandelt und zurück; für die damaligen Funktionstests wurde ein STM32-Mikrocontroller verwendet
- Der Kern dieser Arbeit ist ein MAC-Layer-Modul, um diesen Adapter mit dem Homebrew-Computer zu verbinden
- Der Adapter ist als Full-Duplex-Struktur aufgebaut; Sende- und Empfangsteil arbeiten unabhängig voneinander
Empfänger: SPI-Daten im Frame-Buffer speichern
- Der Empfänger wandelt serielle SPI-Daten in byteweise parallele Daten um und extrahiert den Byte-Takt
- Die Ziel-MAC-Adresse wird in den ersten 6 Bytes geprüft; Frames, die nicht den Kriterien entsprechen, werden verworfen
- Empfangene Bytes werden in einen 6116-2-kB-SRAM-Puffer geschrieben
- Wenn ein Frame endet, wird der Empfänger deaktiviert und nimmt keine weiteren Frames an, bis er wieder aktiviert wird
- Der Byte-Zähler behält seinen Wert auch nach dem Anhalten bei, sodass die CPU die Empfangslänge auslesen kann
- FCS wird nicht in Hardware geprüft
-
Datenerfassung und Buffer-Zugriff
- Serielle SPI-Daten gelangen in das Schieberegister
U32;U30undU31zählen jeweils Bits und Bytes - Das D-Flipflop
U29Berzeugt das SRAM-Schreibsignalrecv_buf_we; dieses Signal wird bei jeweils 8 Bit Eingangsdaten kurz auf Low gezogen - Empfangene Bytes werden in den 6116-SRAM
U20geschrieben U13,U16undU18bilden den Adressmultiplexer und wählen als SRAM-Adresseingang entweder den Byte-Zähler oder den System-AdressbusU21dient als Tri-State-Buffer, der empfangene Bytes an den RAM weitergibt- Damit die CPU auf Empfangsdaten und Länge zugreifen kann, sind RAM und Byte-Zähler mit dem System-Datenbus verbunden
U25verbindet den Empfangs-RAM mit dem System-Datenbus- Nach Abschluss eines Frames bleibt der Wert des Byte-Zählers auf dem Bus
recv_byte_cnterhalten U26undU27legen diesen Wert auf den System-Datenbus, wenn eine Leseanforderung an eine bestimmte Adresse eingeht- Die übrige Hälfte von
U27bildet ein 2-Bit-Read-only-Statusregister zum Abfragen des Empfänger- und Senderstatus
- Serielle SPI-Daten gelangen in das Schieberegister
Hardware-MAC-Adressfilterung
- Bei der Analyse des Ethernet-Traffics zeigte sich, dass Frames meist in kleinen Gruppen von 3 bis 4 mit kurzen Verzögerungen dazwischen eintrafen und dass selbst innerhalb derselben Gruppe oft unterschiedliche Ziel-MAC-Adressen vorkamen
- Da der Computer möglicherweise nicht schnell genug wäre, um MAC-Filterung in Software auszuführen und den Empfänger wieder zu aktivieren, war Hardware-MAC-Filterung nötig
- Ein Ansatz, eine benutzerdefinierte MAC-Adresse zu speichern und mit den ersten 6 Bytes zu vergleichen, wurde als zu komplex verworfen
- Eine MAC-Adresse aus einem einzelnen wiederholten Byte wäre ebenfalls möglich gewesen, letztlich wurde die MAC-Adresse jedoch als Funktion des Byte-Index erzeugt
- Bit 0 ist fest 0
- Bit 1 ist fest 1
- Bit 2–4 sind der invertierte Wert des Byte-Index
- Bit 5–7 sind fest 1
- Die nach dieser Regel erzeugte MAC-Adresse lautet
FE:FA:F6:F2:EE:EA - Für ARP wird auch die Broadcast-MAC
FF:FF:FF:FF:FF:FFakzeptiert U33vergleicht Datenbit 0 und Bit 2–4 mit den gewünschten Werten; der Ausgang vonU34Awird High, wenn diese Bits übereinstimmenU35Aimplementiert die Broadcast-MAC-Prüfung; der Ausgang wird High, wenn Bit 0 und Bit 2–4 alle 1 sind- Die beiden Signale werden per Dioden-OR mit
D7undR6kombiniert, undU35Bprüft, ob die übrigen Bits alle 1 sind - Das Gültigkeitsergebnis eines einzelnen Bytes wird in
U10Aakkumuliert- Wenn kein Frame empfangen wird, ist das eingehende SPI-Slave-Select-Signal
ssLow undU10Awird auf 1 gesetzt - Während des Frame-Empfangs wird der Wert bei jedem empfangenen Byte aktualisiert
- Wenn die Ziel-MAC-Adresse den Kriterien entspricht, bleibt der Wert von
U10AHigh - Sobald die Byte-Adresse 5 erreicht, wird der Endwert in
U36Bgelatcht; stimmt die Zieladresse nicht, wird der Frame-Empfang blockiert
- Wenn kein Frame empfangen wird, ist das eingehende SPI-Slave-Select-Signal
Sender: Schaltung durch Frames fester Länge vereinfachen
- Wie beim Empfänger wird auch beim Sender die FCS-Erzeugung nicht in Hardware implementiert, sondern in Software erledigt
- Um die Schaltung zu verkleinern, unterstützt der Sender nur Frames fester Länge
- Als Frame-Länge wurden 1024 Bytes gewählt, ein Wert nahe der üblichen MTU von 1500 Bytes
- Die für 10BASE-T nötige Präambel besteht aus mehreren
0x55und einem abschließenden0xD5; die Software muss sie zusammen mit dem Frame in diese 1024 Bytes laden - Die feste Frame-Länge beeinflusst Protokolle höherer Schichten nicht
- Protokolle höherer Schichten kodieren die Paketgröße im Header
- Sie hängen nicht von der tatsächlichen Ethernet-Frame-Länge ab
-
Sendedatenfluss
- Sendedaten werden im SRAM gespeichert
- Ein 20-MHz-Takt wird einem 4-Bit-Zähler zugeführt, dessen Overflow-Ausgang als Byte-Takt verwendet wird
- Das Schreiben eines Werts an eine bestimmte nur schreibbare Speicherposition aktiviert den Zähler und startet die Frame-Übertragung
- Parallele Byte-Daten werden über ein Schieberegister serialisiert
- Wie beim Empfänger zählt
U12die Bits undU14die Bytes - Der 20-MHz-Takt kommt von einem integrierten Oszillator und wird nicht direkt verwendet, sondern mindestens durch 2 geteilt
- Dadurch beeinflusst das Tastverhältnis des Oszillators das Ausgangssignal nicht
-
RAM, Schieberegister und Timing
- Für die Auswahl des Adresseingangs von RAM
U22werden wie beim Empfänger drei 74HC157-Multiplexer verwendet U23wird genutzt, um Daten in den RAM zu ladenU24dient als Zwischenspeicher für das aktuell gesendete Byte- Der Byte-Zähler 74HC4040 ist ein Ripple-Counter und stabilisiert sich daher langsam
- Solange der RAM-Ausgang noch nicht gültig ist, liefert
U24einen stabilen Ausgang - Die Daten gelangen in das Schieberegister
U28und werden bitweise verschoben - Wegen eines Hardware-Bugs, bei dem die Bitreihenfolge vom RAM zum Schieberegister falsch verdrahtet war, musste die Software die Bits vertauschen, um das zu umgehen
MOSIundSCKmüssen exakt synchronisiert sein, um ein gutes 10BASE-T-Signal zu erzeugenU11AundU8Bübernehmen diese Synchronisierungtx_cnt0ist Bit 0 des Bitzählers und wird als durch 2 geteilter 20-MHz-Takt für den Clock verwendetU11Aändert seinen Ausgang passend zu diesem SignalU8Bverzögert den Takt, um die vonU11Aerzeugte Verzögerung auszugleichen- D-Latches sind komplexer als einfache AND-Gatter und haben etwa 5 ns mehr Verzögerung, daher wird der schnellere 74LV74A verwendet
- Der 74LV74A ist der einzige Chip einer schnellen Familie auf diesem Board
- Für die Auswahl des Adresseingangs von RAM
CPU-Interface und Memory-Mapping
- Aus Sicht des Programmierers erscheint der Ethernet-Adapter als memory-mapped Interface
- Die beiden Frame-Buffer sind auf
0xF000gemappt - Es gibt zwei Read-only-Register
- Das 8-Bit-Statusregister bei
0xFB00enthält die FlagsRX_FULLundTX_BUSY RX_FULLzeigt an, dass ein Frame vollständig empfangen wurdeTX_BUSYzeigt an, dass gerade ein Frame gesendet wird- Das 16-Bit-Register bei
0xFB02enthält die Länge der Empfangsdaten
- Das 8-Bit-Statusregister bei
- Schreiboperationen werden als Steuerbefehle verwendet
- Das Schreiben eines beliebigen Werts nach
0xFB00reaktiviert den Empfänger - Das Schreiben eines beliebigen Werts nach
0xFB01startet die Übertragung
- Das Schreiben eines beliebigen Werts nach
- Da die CPU keine Interrupts unterstützt, gibt es keine Interrupts
- Alle zugehörigen Adressen beginnen mit
F, also haben die oberen 4 Bits den Wert 1; diese Bedingung prüftU2A - Für Buffer-Adressen muss Bit 11 den Wert 0 haben;
U1D,D2,R2undU1Eprüfen dies - Für Registeradressen muss die zweite Hexadezimalstelle
Bsein, also1011;U1BundU2Bprüfen dies - Die Decoder
U4AundU4Bdienen zur Auswahl einzelner Funktionen - Zwei LEDs zeigen Zugriffe auf Buffer oder Register an
Programmierung und Performance
- Netzwerkunterstützung war gewünscht, aber weder ein eigener TCP/IP-Stack noch umständliche Assemblerprogrammierung; deshalb wurde ein C-Compiler gebaut
- Dieser Compiler ist ausgereift genug, um die kleine TCP/IP-Bibliothek uIP 1.0 zu kompilieren
- Die Code-Dichte der CPU ist sehr niedrig, aber uIP passt in den RAM und lässt auch noch Platz für echte Anwendungen
- Die Netzwerkleistung ist gering, aber sie wurde ohne kommerzielle CPU oder Spezialchip erreicht
- Durchschnittliche Ping-Roundtrip-Zeit: 85 ms
- Download-Geschwindigkeit des HTTP-Servers: 2,6 kB/s
- Der HTTP-Server stellt statische Dateien von einer SD-Karte bereit
- Modelle, Schaltplandateien und PCB-Layouts befinden sich im GitHub-Repository
1 Kommentare
Hacker-News-Kommentare
Coole Arbeit, danke fürs Teilen. Besonders gut gefiel mir der Stacktrace des Denkprozesses, und dass vieles aus First Principles hergeleitet oder aus der Perspektive von Einsteigern erklärt wurde, ist didaktisch hervorragend.
Auch wenn es für echtes Networking unpraktisch ist, sehe ich es nicht nur als Spielerei. In einer Zeit, in der Backdoors in übermäßig komplexen Netzwerkchips gefunden werden, könnte es künftig auch eine ernsthaftere Leserschaft oder Projektmotivation dafür geben.
Das ist für einen komplett maßgeschneiderten Computer, was es an sich schon viel beeindruckender macht — ganz zu schweigen von der Stelle „also habe ich einen C-Compiler gebaut“. Trotzdem frage ich mich, wie eine Minimalimplementierung einer Ethernet-Karte für einen „normalen“ PC aussehen würde.
Vieles dürfte ähnlich sein, und Checksummen könnte man vielleicht von der PC-CPU erledigen lassen. Für die Anbindung bräuchte man entweder rohes Seriell oder, praktikabler, USB; am Ende müsste man wohl einen „echten“ Treiber verwenden oder die Verarbeitung in den Userspace weiterreichen.
Beim Ansehen ähnlicher Dinge dachte ich, wenn das Gerät https://en.wikipedia.org/wiki/USB_communications_device_clas... implementiert, könnte es ohne eigenen Treiber „einfach funktionieren“; das passt aber wohl nicht gut dazu, alle Checksummen auf der Host-Seite zu berechnen.
Beim Suchen bin ich auch auf https://en.wikipedia.org/wiki/Ethernet_over_USB gestoßen; vielleicht heißt das, man könnte einen Adapter bauen, der nur die physische Verbindung auf USB umsetzt und den Rest dem Computer überlässt.
Vielleicht ließe sich ein USB-Gerät im FTDI-Stil dazu überreden, 10base2 Ethernet per Bit-Banging zu machen. Man würde nur die „PHY“-Seite implementieren, die den Leitungsverkehr in einen sauberen Bitstream umsetzt und den Frame-Anfang ausrichtet, und den PC den gesamten Rest in Software erledigen lassen.
Auf der USB-Seite ist CDC-NCM selbst auf praktisch jedem MCU nicht schwer zu implementieren, aber eine USB-HS-PHY-Implementierung braucht faktisch ASIC-Hardware.
Mit einem USB-HS-ULPI-PHY für 0,30 Dollar ließe sich USB CDC-NCM in einem FPGA wohl ziemlich einfach umsetzen.
Am Ende gibt es einen Link zu dem für dieses Projekt gebauten C-Compiler: https://github.com/imihajlow/ccpu-cc
Es scheint auch einen Linker und libc zu geben. Ich kann nicht wirklich beurteilen, wie komplex das Hardwaredesign ist, aber dass er mal eben einen C-Compiler drangebaut hat, ist beeindruckend.
Wirklich beeindruckend. Solche Projekte würde ich gern selbst ausprobieren, und ich habe großen Respekt vor der Leidenschaft und den unzähligen Stunden, die hineingeflossen sind, um das System zu verstehen und dann zu bauen.
Ich wünsche mir nicht unbedingt den Ruhestand, aber vielleicht werde ich um diese Zeit herum irgendwann solche Hardware- und Softwareprojekte angehen.
Ist es also besser oder schlechter als eine Etherlink 3c501? :-D
https://mirror.math.princeton.edu/pub/oldlinux/Linux.old/net...
Wenn ich mich richtig erinnere, konnte ein neues Paket aus dem Netzwerk den Puffer überschreiben, den die CPU gerade lesen wollte. Ich habe sie eine Zeit lang unter Linux benutzt, und die Performance war wirklich schlecht.
Es ist erstaunlich, wie viel Treiber- und Firmware-Updates aus dem Blickfeld verschwinden lassen.
Meines ist besser, weil es zwei Puffer hat :) Trotzdem wird nur ein Empfangs-Frame gespeichert.
Interessant fand ich die Stelle: „Eine feste Frame-Länge hat keinen Einfluss auf höhere Protokolle. Höhere Protokolle kodieren die Paketgröße im Header und verlassen sich nicht auf die tatsächliche Ethernet-Frame-Länge.“
Ich habe vor Kurzem einen Paketdecoder gebaut und in jeder Schicht explizit geprüft, ob die Länge der darunterliegenden Schicht passt. Bei IP muss in meinem Decoder die IP-Datagrammlänge exakt zur Ethernet-Frame-Länge und zur Länge des Link-Layer-Headers passen.
Das war nicht als Pedanterie gedacht, sondern um kurze Frames zu erkennen; später habe ich beschlossen, auch zu lange Frames als Fehler zu behandeln. Der Autor verwendet uIP, aber ich frage mich, wie Linux oder andere moderne OS damit umgehen. Auch würde mich interessieren, ob er Interoperabilitätstests gemacht hat.
Wenn die Anwendung keine L2-Daten sieht, ignoriert der Linux-IP-Stack das einfach.
Physisch ist sie viel kleiner als DECs erstes SSI-Ethernet-Kartenset: https://i.ebayimg.com/images/g/NEYAAOSw-mZlg0lZ/s-l1600.jpg
Die DEC-DEUNA-Boards sind über einen Fuß lang, können aber auch viel mehr. DEUNA ist eine „echte“ NIC: Sie hat Sende- und Empfangsqueues, verarbeitet sie autonom und macht auch DMA. Natürlich hat sie auch ihren eigenen PDP-11 auf der Karte, der das ausführt.
Wirklich cool. Ich frage mich, wie lange das gedauert hat.
Das zeigt, wie einfach es ist, eine Backdoor in einen Chip einzubauen, der an einen Netzwerkport angeschlossen ist.
In einem Kurs zu Communication Systems Engineering habe ich die Ethernet-Signalverarbeitung implementiert und anschließend einen TCP/IP-Stack einschließlich ARP und Switching in Motorola-68k-QUIC-Assembler umgesetzt.
Das waren die längsten 18 Monate meines Lebens.