บันทึกการสร้างการ์ดเครือข่ายลอจิกแบบดิสครีต
บทความนี้เป็นส่วนหนึ่งของชุดบทความที่ว่าด้วยกระบวนการสร้างระบบคอมพิวเตอร์ที่สมบูรณ์โดยใช้วงจรลอจิกแบบดิสครีต จนถึงตอนนี้ได้สร้างคอมพิวเตอร์ที่สามารถรันแอปพลิเคชันเครือข่ายอย่าง HTTP server หรือเกมผ่าน LAN ได้แล้ว
ภาพรวม
- เมื่อปีที่แล้วได้สร้างอะแดปเตอร์ชั้นกายภาพที่แปลงสัญญาณอีเธอร์เน็ต 10BASE-T ไปเป็น SPI และแปลงกลับได้ด้วย ตอนนั้นทดสอบการทำงานด้วยไมโครคอนโทรลเลอร์ STM32 และตอนนี้กำลังพัฒนาโมดูลชั้น MAC เพื่อเชื่อมต่อเข้ากับคอมพิวเตอร์ที่สร้างขึ้นเอง
- อะแดปเตอร์ทั้งสองเป็นแบบฟูลดูเพล็กซ์และมีส่วนส่งกับส่วนรับที่แยกจากกันอย่างอิสระ
ตัวรับ
สรุปการทำงานของตัวรับ:
- ข้อมูลอนุกรม SPI ถูกแปลงเป็นข้อมูลขนานระดับไบต์ และดึงสัญญาณนาฬิกาไบต์ออกมา
- 6 ไบต์แรกจะถูกนำไปเทียบกับค่าอ้างอิงของที่อยู่ MAC ปลายทาง และเฟรมที่ไม่ตรงกันจะถูกปฏิเสธ
- ไบต์จะถูกเขียนลงในบัฟเฟอร์ static RAM
- เมื่อเฟรมสิ้นสุด ตัวรับจะถูกปิดการทำงาน และจะปฏิเสธเฟรมเพิ่มเติมจนกว่าผู้ใช้จะสตาร์ตตัวรับใหม่ ตัวนับไบต์จะหยุดและค่าดังกล่าวจะถูกส่งให้ผู้ใช้
การเก็บข้อมูล
- ต้องแปลงข้อมูล SPI แบบอนุกรมให้เป็นสตรีมข้อมูลระดับไบต์
- ข้อมูลอนุกรมจะถูกเลื่อนเข้า shift register (
U32)U30และU31ใช้นับบิตและไบต์ตามลำดับ - สัญญาณเขียน static RAM
recv_buf_weถูกสร้างโดยใช้ D flip-flopU29Bสัญญาณนี้จะลดลงเป็นช่วงสั้น ๆ หลังจากข้อมูลอินพุตแต่ละชุดครบ 8 บิต - ไบต์ที่รับมาได้จะถูกเขียนลงในบัฟเฟอร์ static RAM ขนาด 2kB ของ 6116 (
U20) U13,U16,U18สร้าง address multiplexer เพื่อเลือกให้ใช้อย่างใดอย่างหนึ่งระหว่างตัวนับไบต์หรือบัสที่อยู่ของระบบเป็นอินพุตที่อยู่ของ SRAM (U20)- tri-state buffer
U21ใช้ส่งไบต์ที่รับมาเข้า RAM
การกรองที่อยู่ MAC
- ระหว่างการวิเคราะห์ทราฟฟิกอีเธอร์เน็ต พบว่าเฟรมมักจะมาเป็นกลุ่มเล็ก ๆ (3-4 เฟรมที่คั่นกันด้วยดีเลย์สั้น ๆ) โดยเฟรมในกลุ่มเดียวกันมักมีที่อยู่ MAC ปลายทางต่างกัน
- สิ่งนี้ทำให้คิดว่าคอมพิวเตอร์ของตนจะไม่สามารถกรองเฟรมที่รับมาตาม MAC และรีสตาร์ตตัวรับได้เร็วพอที่จะจับเฟรมที่มีไว้สำหรับตัวเอง จึงจำเป็นต้องมีการกรอง MAC ด้วยฮาร์ดแวร์
- การเก็บที่อยู่ MAC แบบกำหนดเองไว้ที่ไหนสักแห่งแล้วนำมาเทียบกับ 6 ไบต์แรกที่รับเข้ามานั้นซับซ้อนเกินไป จะทำเป็นไบต์เดียวซ้ำ ๆ (เช่น FE:FE:FE:FE:FE:FE) ก็ได้แต่ไม่น่าสนใจ
- เพื่อให้ MAC ของตัวเองมีความเปลี่ยนแปลงเล็กน้อย จึงกำหนดให้เป็นฟังก์ชันของดัชนีไบต์:
- บิต 0 ตรึงไว้เป็น 0
- บิต 1 ตรึงไว้เป็น 1
- บิต 2-4 เป็นค่ากลับบิตของดัชนีไบต์
- บิต 5-7 ตรึงไว้เป็น 1
- เมื่อใช้กติกานี้ ที่อยู่ MAC จะเป็น
FE:FA:F6:F2:EE:EAและเพื่อให้ทำงานกับ ARP ได้ ยังต้องยอมรับ broadcast MACFF:FF:FF:FF:FF:FFด้วย
ตัวส่ง
- เช่นเดียวกับตัวรับ ตัวส่งไม่ได้สร้าง FCS ในฮาร์ดแวร์ แต่ทำในซอฟต์แวร์
- เพื่อให้ตัวส่งง่ายขึ้น จึงตัดสินใจรองรับเฉพาะเฟรมความยาวคงที่ วิธีนี้ทำให้ไม่ต้องใช้ digital comparator ที่ซับซ้อน และลอจิกการส่งเฟรมจะอาศัยเพียงบิตเดียวของตัวนับไบต์
- เลือกความยาวเฟรมเป็น 1024 ไบต์ ซึ่งใกล้เคียงกับ MTU ทั่วไปที่ 1500 ไบต์
- preamble ของเฟรมที่ 10BASE-T ต้องใช้ (ลำดับของ 0x55 ตามด้วย 0xD5) ก็รวมอยู่ใน 1024 ไบต์นี้ และต้องโหลดมาจากซอฟต์แวร์ด้วย
ตัวนับ
- เช่นเดียวกับตัวรับ ใช้ตัวนับสองตัวในการนับบิต (
U12) และไบต์ (U14) - ตัวนับตัวแรกป้อนด้วยสัญญาณนาฬิกา 20MHz จาก oscillator ในตัว
- ไม่ได้ใช้ 20MHz โดยตรง แต่หารอย่างน้อย 2 ก่อน เพื่อไม่ให้ duty cycle ของ oscillator ส่งผลต่อสัญญาณเอาต์พุต
การไหลของข้อมูล
- ใช้ 74HC157 multiplexer สามตัวชุดเดียวกับฝั่งตัวรับ (ไม่ได้แสดงไว้ที่นี่) เพื่อเลือกอินพุตที่อยู่ของ RAM (
U22) U23ใช้สำหรับโหลดข้อมูลเข้า RAMU24ทำหน้าที่เป็นที่เก็บชั่วคราวสำหรับไบต์ที่กำลังส่งอยู่ แนวคิดนี้คล้ายกับ VGA pipeline ของผู้เขียน- ตัวนับไบต์ 74HC4040 เป็น ripple counter และต้องใช้เวลาสักพักกว่าจะนิ่ง โดย
U24จะให้เอาต์พุตที่คงที่ในระหว่างที่เอาต์พุตของ RAM ยังไม่ valid - จากนั้นข้อมูลนี้จะถูกป้อนไปยัง shift register
U28และเลื่อนออกทีละบิต
อินเทอร์เฟซกับ CPU
จากมุมมองของโปรแกรมเมอร์ อินเทอร์เฟซของอะแดปเตอร์อีเธอร์เน็ตนี้เป็นดังนี้:
- frame buffer ทั้งสองถูกแมปไว้ที่
0xF000 - มีรีจิสเตอร์แบบอ่านอย่างเดียวสองตัว:
- รีจิสเตอร์สถานะ 8 บิตที่
0xFB00มีแฟล็กสองตัว:RX_FULL- ได้รับเฟรมแล้วTX_BUSY- กำลังส่งเฟรมอยู่
- รีจิสเตอร์ความยาวข้อมูลที่รับมาแบบ 16 บิตที่
0xFB02
- รีจิสเตอร์สถานะ 8 บิตที่
- การเขียนค่าใด ๆ ไปที่
0xFB00จะเป็นการรีสตาร์ตตัวรับ - การเขียนค่าใด ๆ ไปที่
0xFB01จะเริ่มการส่ง - ไม่มี interrupt เพราะ CPU ของผู้เขียนไม่รองรับ interrupt
การเขียนโปรแกรม
ต้องการการรองรับเครือข่าย แต่ไม่อยากลงมือเขียน TCP/IP stack เอง อีกทั้งคอมไพเลอร์ตัวแรกก็แย่มาก และการเขียนโปรแกรมด้วยแอสเซมบลีก็น่ารำคาญ จึงอยากได้ C compiler ที่ดีพอ เลยสร้าง C compiler ขึ้นมาเอง ตอนนี้มันพัฒนาไปมากพอที่จะคอมไพล์ uIP 1.0 (ไลบรารี TCP/IP ขนาดเล็ก) ได้ แม้ว่าความหนาแน่นของโค้ดบน CPU นี้จะต่ำมาก แต่ uIP ก็ยังเล็กพอที่จะใส่ลงใน RAM ได้ และยังเหลือพื้นที่สำหรับแอปพลิเคชันจริง
ประสิทธิภาพเครือข่ายนั้นต่ำมาก แต่เมื่อคำนึงว่าไม่ได้ใช้ทั้ง CPU เชิงพาณิชย์หรือชิปเฉพาะทาง ก็ยังน่าพอใจมาก:
- ping แบบไปกลับเฉลี่ย 85ms
- ความเร็วดาวน์โหลดจาก HTTP server 2.6kB/s (ให้บริการไฟล์สถิตจาก SD card)
ที่เก็บโครงการ
โมเดล ไฟล์ schematic และแบบ PCB อยู่บน GitHub
ความเห็นของ GN⁺
- โครงการนี้แสดงให้เห็นถึงความเข้าใจด้านฮาร์ดแวร์อย่างลึกซึ้งและความหลงใหลของผู้พัฒนา ความพยายามที่จะสร้างทุกอย่างขึ้นมาเองนั้นน่าประทับใจอย่างมาก แต่ในแง่ความใช้งานจริงก็ยังชวนให้ตั้งคำถาม
- ระบบคอมพิวเตอร์สมัยใหม่มีความซับซ้อนและเฉพาะทางสูงมาก การสร้างทุกอย่างตั้งแต่ศูนย์จึงไม่มีประสิทธิภาพอย่างยิ่ง โดยเฉพาะในส่วนที่มีการวางรากฐานและปรับแต่งมาอย่างดีแล้ว เช่น network protocol stack การใช้ implementation ที่มีอยู่จึงเป็นทางเลือกที่ฉลาดกว่า
- ถึงอย่างนั้น โครงการแบบนี้ก็มีคุณค่าทางการศึกษาอย่างมาก เพราะทำให้ได้สัมผัสโดยตรงว่าฮาร์ดแวร์ระดับล่างและซอฟต์แวร์ทำงานร่วมกันอย่างไร และโปรโตคอลต่าง ๆ ถูกนำไปสร้างจริงอย่างไร
- อีกทั้งในช่วงที่นักพัฒนายุคนี้มีความเข้าใจเรื่องฮาร์ดแวร์ลดลง โครงการแบบนี้ก็อาจเป็นตัวอย่างอันมีค่าที่ช่วยย้ำเตือนถึงรากฐานของระบบคอมพิวเตอร์
- จุดที่น่าเสียดายคือประสิทธิภาพยังต่ำมาก หากต้องการให้นำไปใช้งานได้จริงมากขึ้นก็น่าจะต้องมีการปรับแต่งเพิ่มเติม แต่ดูเหมือนว่านั่นไม่ใช่เป้าหมายหลักของโครงการนี้
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News