สร้างเกมทำงานของ Tetris ใน Game of Life ของ Conway


993

นี่คือคำถามเชิงทฤษฎี - คำถามหนึ่งที่ไม่สามารถหาคำตอบได้ง่ายไม่ว่าในกรณีใด ๆ แม้แต่คำถามเล็กน้อยก็ตาม

ใน Game of Life ของ Conway มีการสร้างเช่นmetapixelซึ่งอนุญาตให้ Game of Life จำลองระบบกฎ Game-of-Life อื่น ๆ ได้เช่นกัน นอกจากนี้ยังเป็นที่รู้จักกันว่าเกมแห่งชีวิตนั้นทัวริงสมบูรณ์

งานของคุณคือการสร้างหุ่นยนต์อัตโนมัติโดยใช้กฎของเกมชีวิตของคอนเวย์ที่จะอนุญาตให้เล่นเกม Tetris

โปรแกรมของคุณจะได้รับการป้อนข้อมูลโดยการเปลี่ยนสถานะของหุ่นยนต์ด้วยตนเองในรุ่นที่เฉพาะเจาะจงเพื่อเป็นตัวแทนของการขัดจังหวะ (เช่นการย้ายชิ้นส่วนทางซ้ายหรือขวาวางมันหมุนหมุนหรือสร้างชิ้นใหม่เพื่อวางลงบนตะแกรง) จำนวนรุ่นที่เฉพาะเจาะจงเป็นเวลารอคอยและแสดงผลที่ใดที่หนึ่งบนหุ่นยนต์ ผลลัพธ์ที่แสดงต้องคล้ายกับตาราง Tetris จริงอย่างเห็นได้ชัด

โปรแกรมของคุณจะได้รับคะแนนตามสิ่งต่าง ๆ ดังต่อไปนี้ตามลำดับ (ด้วยเกณฑ์ที่ต่ำกว่าที่ทำหน้าที่เป็นตัวแบ่งสำหรับเกณฑ์ที่สูงกว่า):

  • ขนาดกล่องที่ถูกผูกไว้ - กล่องสี่เหลี่ยมที่มีพื้นที่ขนาดเล็กที่สุดซึ่งบรรจุโซลูชันที่ให้ไว้ได้รับรางวัลทั้งหมด

  • การเปลี่ยนแปลงเล็ก ๆ น้อย ๆ ในการป้อนข้อมูล - เซลล์ที่น้อยที่สุด (สำหรับกรณีที่แย่ที่สุดในหุ่นยนต์ของคุณ) ที่จะต้องมีการปรับด้วยตนเองสำหรับการชนะการขัดจังหวะ

  • การประมวลผลที่เร็วที่สุด - ชั่วอายุที่น้อยที่สุดเพื่อก้าวไปหนึ่งขีดในการจำลองการชนะ

  • จำนวนเซลล์เริ่มต้นสด - จำนวนที่น้อยลงชนะ

  • คนแรกที่โพสต์ - โพสต์ก่อนหน้านี้ชนะ


95
"ตัวอย่างที่เห็นได้ชัดในการทำงาน" หมายถึงบางสิ่งบางอย่างที่ทำงานในเวลาไม่กี่ชั่วโมงหรือบางสิ่งที่สามารถพิสูจน์ได้ว่าถูกต้องแม้ว่ามันจะต้องใช้เวลาจนกว่าความร้อนจากเอกภพจะหมดไป
Peter Taylor

34
ฉันค่อนข้างแน่ใจว่าสิ่งนี้เป็นไปได้และเล่นได้ มีเพียงไม่กี่คนที่มีความเชี่ยวชาญในการเขียนสิ่งที่อาจเป็นหนึ่งใน "ภาษาแอสเซมบลี" ที่ลึกลับที่สุดในโลก
Justin L.

58
ความท้าทายนี้กำลังดำเนินการอยู่! ห้องสนทนา | ความคืบหน้า | บล็อก
mbomb007

49
ตั้งแต่ 5:10 เช้านี้ (9:10 UTC) คำถามนี้เป็นคำถามแรกในประวัติ PPCG ที่จะได้คะแนน 100 คะแนนโดยไม่ได้รับคำตอบ! ทำได้ดีทุกคน
Joe Z.

76
ฉันกำลังพยายามแก้ไขปัญหานี้ ... ตอนนี้เมื่อฉันเข้านอนฉันเห็นเครื่องร่อนทุกหนทุกแห่งพุ่งชนกันอย่างยุ่งเหยิง การนอนหลับของฉันเต็มไปด้วยฝันร้ายที่เพนทาคาเทนนอลส์เต้นเป็นจังหวะขวางทางของฉัน กรุณาจอห์นคอนเวย์, อธิษฐานสำหรับฉัน ...
สลัว

คำตอบ:


937

สิ่งนี้เริ่มเป็นภารกิจ แต่จบลงด้วยการเป็นโอดิสซีย์

Quest for Tetris Processor, 2,940,928 x 10,295,296

ไฟล์รูปแบบในทุกสิริของสามารถพบได้ที่นี่ , สามารถดูได้ในเบราว์เซอร์ที่นี่

โครงการนี้เป็นสุดยอดของความพยายามของผู้ใช้จำนวนมากตลอดระยะเวลา 1 และ 1/2 ปีที่ผ่านมา แม้ว่าองค์ประกอบของทีมจะแตกต่างกันไปตามเวลา แต่ผู้เข้าร่วมในการเขียนมีดังนี้:

เราขอขอบคุณ 7H3_H4CK3R, Conor O'Brienและผู้ใช้อื่น ๆ อีกมากมายที่พยายามแก้ไขปัญหานี้

เนื่องจากขอบเขตที่ไม่เคยมีมาก่อนของการทำงานร่วมกันนี้คำตอบนี้แบ่งออกเป็นส่วน ๆ ตามคำตอบหลาย ๆ คำที่เขียนโดยสมาชิกของทีมนี้ สมาชิกแต่ละคนจะเขียนเกี่ยวกับหัวข้อย่อยที่เฉพาะเจาะจงซึ่งสัมพันธ์กับพื้นที่ของโครงการที่พวกเขาเกี่ยวข้องมากที่สุด

โปรดแจกจ่าย upvotes หรือรางวัลใด ๆ ให้กับสมาชิกทุกคนในทีม

สารบัญ

  1. ภาพรวม
  2. Metapixels และ VarLife
  3. ฮาร์ดแวร์
  4. QFTASM และ Cogol
  5. การประกอบการแปลและอนาคต
  6. ใหม่ภาษาและคอมไพเลอร์

ลองพิจารณาการตรวจสอบองค์กร GitHubของเราซึ่งเราได้ใส่รหัสทั้งหมดที่เราเขียนไว้เป็นส่วนหนึ่งของโซลูชันของเรา คำถามที่จะถูกนำไปเราเนตพัฒนา


ส่วนที่ 1: ภาพรวม

ความคิดพื้นฐานของโครงการนี้คือสิ่งที่เป็นนามธรรม แทนที่จะพัฒนาเกม Tetris ใน Life โดยตรงเราค่อย ๆ หมุนสิ่งที่เป็นนามธรรมในหลายขั้นตอน ในแต่ละชั้นเราจะอยู่ห่างไกลจากความยากลำบากของชีวิตและใกล้กับการสร้างคอมพิวเตอร์ที่ง่ายต่อการเขียนโปรแกรมเหมือนกัน

อันดับแรกเราใช้OTCA metapixelsเป็นรากฐานของคอมพิวเตอร์ของเรา metapixels เหล่านี้สามารถเลียนแบบกฎ "เหมือนชีวิต" ได้ Wireworldและคอมพิวเตอร์ Wireworldทำหน้าที่เป็นแรงบันดาลใจที่สำคัญสำหรับโครงการนี้ดังนั้นเราจึงพยายามสร้างกลุ่มที่คล้ายกันกับ metapixels แม้ว่ามันจะเป็นไปไม่ได้ที่จะเลียนแบบ Wireworld ด้วย OTCA metapixels แต่ก็เป็นไปได้ที่จะกำหนดกฎเกณฑ์ที่แตกต่างกันของ metapixels และสร้างการจัดเรียงของพิกเซลที่ทำงานคล้ายกับสาย

ขั้นตอนต่อไปคือการสร้างความหลากหลายของประตูตรรกะพื้นฐานเพื่อใช้เป็นพื้นฐานสำหรับคอมพิวเตอร์ ในขั้นตอนนี้เรากำลังเผชิญกับแนวคิดที่คล้ายกับการออกแบบโปรเซสเซอร์จริง นี่คือตัวอย่างของเกต OR แต่ละเซลล์ในภาพนี้เป็นจริง OTCA metapixel ทั้งหมด คุณสามารถเห็น "อิเล็กตรอน" (แต่ละอันแทนข้อมูลบิตเดียว) เข้าและออกจากเกต คุณยังสามารถดูประเภท metapixel ที่แตกต่างกันทั้งหมดที่เราใช้ในคอมพิวเตอร์ของเรา: B / S เป็นพื้นหลังสีดำ, B1 / S เป็นสีน้ำเงิน, B2 / S ในสีเขียวและ B12 / S1 เป็นสีแดง

ภาพ

จากที่นี่เราได้พัฒนาสถาปัตยกรรมสำหรับโปรเซสเซอร์ของเรา เราใช้ความพยายามอย่างมากในการออกแบบสถาปัตยกรรมที่ไม่ลึกลับและสามารถนำไปใช้งานได้ง่ายที่สุด ในขณะที่คอมพิวเตอร์ Wireworld ใช้สถาปัตยกรรมที่กระตุ้นการขนส่งขั้นพื้นฐานโครงการนี้ใช้สถาปัตยกรรม RISC ที่ยืดหยุ่นมากขึ้นพร้อมด้วย opcodes และโหมดการกำหนดแอดเดรสที่หลากหลาย เราสร้างภาษาแอสเซมบลีที่รู้จักในชื่อ QFTASM (Quest for Tetris Assembly) ซึ่งเป็นแนวทางในการสร้างโปรเซสเซอร์ของเรา

คอมพิวเตอร์ของเรามีแบบอะซิงโครนัสซึ่งหมายความว่าไม่มีการควบคุมนาฬิกาทั่วโลกในคอมพิวเตอร์ แต่ข้อมูลจะมาพร้อมกับสัญญาณนาฬิกาขณะที่มันไหลไปรอบ ๆ คอมพิวเตอร์ซึ่งหมายความว่าเราจะต้องให้ความสำคัญกับการกำหนดเวลาท้องถิ่น แต่ไม่ใช่การกำหนดเวลาทั่วโลกของคอมพิวเตอร์

นี่คือภาพประกอบของสถาปัตยกรรมโปรเซสเซอร์ของเรา:

ภาพ

จากที่นี่เป็นเพียงเรื่องของการนำ Tetris ไปใช้งานบนคอมพิวเตอร์ เพื่อช่วยให้บรรลุผลดังกล่าวเราได้ทำงานหลายวิธีในการรวบรวมภาษาระดับสูงกว่าให้กับ QFTASM เรามีภาษาพื้นฐานที่เรียกว่า Cogol ซึ่งเป็นภาษาที่สองและมีความก้าวหน้ามากขึ้นภายใต้การพัฒนาและในที่สุดเราก็มีแบ็คเอนด์ GCC ที่อยู่ระหว่างการปรับปรุง โปรแกรม Tetris ปัจจุบันเขียนขึ้นใน / รวบรวมจาก Cogol

เมื่อสร้างรหัส Tetris QFTASM สุดท้ายขั้นตอนสุดท้ายคือการรวบรวมจากรหัสนี้ไปยัง ROM ที่สอดคล้องกันจากนั้นจาก metapixels ไปจนถึง Game of Life พื้นฐานทำให้การก่อสร้างของเราเสร็จสมบูรณ์

เล่นเตตริส

สำหรับผู้ที่ต้องการที่จะเล่นเกม Tetris โดยไม่ต้อง messing รอบด้วยคอมพิวเตอร์คุณสามารถเรียกใช้รหัสที่มา Tetrisบนล่าม QFTASM ตั้งค่าที่อยู่จอแสดงผล RAM เป็น 3-32 เพื่อดูเกมทั้งหมด นี่คือความคิดเห็นเพื่อความสะดวก: Tetris ใน QFTASM

คุณสมบัติของเกม:

  • 7 tetrominoes ทั้งหมด
  • การเคลื่อนไหวหมุนเบา ๆ
  • เส้นที่ชัดเจนและให้คะแนน
  • ดูตัวอย่างชิ้นส่วน
  • ผู้เล่นอินพุตฉีดแบบสุ่ม

แสดง

คอมพิวเตอร์ของเราแสดงถึงบอร์ด Tetris เป็นตารางภายในหน่วยความจำ ที่อยู่ 10-31 แสดงบอร์ด, ที่อยู่ 5-8 แสดงชิ้นตัวอย่างและที่อยู่ 3 ประกอบด้วยคะแนน

อินพุต

อินพุตไปยังเกมดำเนินการโดยการแก้ไขเนื้อหาของ RAM address 1. ด้วยตนเองใช้ล่าม QFTASM ซึ่งหมายถึงทำการเขียนโดยตรงไปยังที่อยู่ 1. ค้นหา "Direct write to RAM" บนหน้าของล่าม การย้ายแต่ละครั้งต้องใช้การแก้ไข RAM หนึ่งบิตเท่านั้นและการลงทะเบียนอินพุตนี้จะถูกล้างโดยอัตโนมัติหลังจากเหตุการณ์อินพุตถูกอ่าน

value     motion
   1      counterclockwise rotation
   2      left
   4      down (soft drop)
   8      right
  16      clockwise rotation

ระบบการให้คะแนน

คุณจะได้รับโบนัสสำหรับการเคลียร์หลายบรรทัดในรอบเดียว

1 row    =  1 point
2 rows   =  2 points
3 rows   =  4 points
4 rows   =  8 points

14
@ Christopher2EZ4RTZ ภาพรวมโพสต์นี้แสดงรายละเอียดงานของสมาชิกในโครงการหลายคน (รวมถึงการเขียนโพสต์ภาพรวมจริง) ดังนั้นจึงเหมาะสมที่จะเป็น CW นอกจากนี้เรายังพยายามหลีกเลี่ยงคนคนหนึ่งที่มีสองโพสต์เพราะนั่นจะทำให้พวกเขาได้รับจำนวนตัวแทนที่ไม่ยุติธรรมเนื่องจากเราพยายามที่จะรักษาตัวแทน
Mego

28
ประการแรก +1 ทั้งหมดเพราะนี่คือความสำเร็จที่ยอดเยี่ยมอย่างเหลือเชื่อ (โดยเฉพาะเมื่อคุณสร้างคอมพิวเตอร์ในเกมแห่งชีวิตแทนที่จะเป็นเกมเตตริส) ประการที่สองคอมพิวเตอร์นั้นเร็วแค่ไหนและเกม tetris เร็วแค่ไหน? มันสามารถเล่นจากระยะไกลได้หรือไม่? (อีกครั้ง: นี่มันยอดเยี่ยม)
Socratic Phoenix

18
นี่ ... นี่มันบ้ามาก +1 กับคำตอบทั้งหมดทันที
กอตติเนต์

28
คำเตือนสำหรับทุกคนที่ต้องการแจกจ่ายรางวัลเล็ก ๆ น้อย ๆ เหนือคำตอบ: คุณต้องเพิ่มจำนวนเงินรางวัลสองเท่าในแต่ละครั้ง (จนกว่าคุณจะตี 500) ดังนั้นบุคคลเดียวจึงไม่สามารถให้จำนวนเท่ากันทุกคำตอบยกเว้นจำนวนเงินนั้นคือ 500 ตัวแทน
Martin Ender

23
นี่คือสิ่งที่ยิ่งใหญ่ที่สุดเดียวที่ฉันเคยเลื่อนดูในขณะที่เข้าใจน้อยมาก
Engineer Toast

678

ส่วนที่ 2: OTCA Metapixel และ VarLife

OTCA Metapixel

OTCA metapixel
( ที่มา )

OTCA Metapixelเป็นโครงสร้างในเกมคอนเวย์ของชีวิตที่สามารถนำมาใช้ในการจำลองชีวิตเหมือน automata ถือใด ๆ ตามที่ LifeWiki (ลิงก์ด้านบน) กล่าวว่า

OTCA metapixel เป็นเซลล์หน่วย 35488 ระยะเวลา 35328 ที่ถูกสร้างขึ้นโดย Brice Due ... มันมีข้อดีมากมาย ... รวมถึงความสามารถในการเลียนแบบหุ่นยนต์เซลลูล่าร์ที่มีชีวิตเหมือนจริงและความจริงที่ว่าเมื่อซูมออก และเซลล์ OFF นั้นแยกแยะได้ง่าย ...

ออโตมาตาเซลลูล่าร์ที่มีชีวิตเหมือนจริงหมายถึงที่นี่เป็นหลักว่าเซลล์เกิดและเซลล์อยู่รอดตามจำนวนเพื่อนบ้านแปดเซลล์ของพวกเขาที่ยังมีชีวิตอยู่ ไวยากรณ์สำหรับกฎเหล่านี้มีดังต่อไปนี้: a ตามด้วยหมายเลขของเพื่อนบ้านที่ยังมีชีวิตอยู่ซึ่งจะทำให้เกิด, จากนั้นสแลช, แล้ว S ตามด้วยหมายเลขของเพื่อนบ้านที่มีชีวิตที่จะทำให้เซลล์มีชีวิตอยู่ คำพูดเล็กน้อยดังนั้นฉันคิดว่าตัวอย่างจะช่วยได้ เกมบัญญัติแห่งชีวิตสามารถแสดงโดยกฎ B3 / S23 ซึ่งบอกว่าเซลล์ตายใด ๆ ที่มีเพื่อนบ้านสามคนจะมีชีวิตอยู่และเซลล์ใด ๆ ที่มีชีวิตด้วยเพื่อนบ้านสองหรือสามคนจะยังมีชีวิตอยู่ มิฉะนั้นเซลล์จะตาย

แม้จะเป็นเซลล์ 2048 x 2048 แต่ตัวจริง OTCA metapixel นั้นก็มีกล่องที่มีขอบเขตเท่ากับ 2058 x 2058 เซลล์เหตุผลที่ทำให้มันซ้อนทับกันห้าเซลล์ในทุกทิศทางกับเพื่อนบ้านในแนวทแยง เซลล์ที่ทับซ้อนกันทำหน้าที่สกัดกั้นเครื่องร่อนซึ่งถูกปล่อยออกมาเพื่อส่งสัญญาณเพื่อนบ้านของเซลล์เมตาเซลล์ที่เปิดอยู่เพื่อที่จะได้ไม่รบกวนการทำงานของ metapixels อื่น ๆ หรือบินออกไปอย่างไม่มีกำหนด กฎการเกิดและการเอาชีวิตรอดจะได้รับการเข้ารหัสในส่วนพิเศษของเซลล์ที่ด้านซ้ายของเมตาพิกเซลโดยการมีหรือไม่มีผู้กินในตำแหน่งที่เฉพาะเจาะจงตามสองคอลัมน์ (หนึ่งสำหรับการเกิดและอื่น ๆ เพื่อความอยู่รอด) สำหรับการตรวจสอบสถานะของเซลล์ข้างเคียงนี่คือวิธีการที่เกิดขึ้น:

สตรีม 9-LWSS จากนั้นหมุนตามเข็มนาฬิกาไปรอบ ๆ เซลล์สูญเสีย LWSS สำหรับแต่ละเซลล์ 'on' ที่อยู่ติดกันซึ่งก่อให้เกิดปฏิกิริยา honeybit จำนวนของ LWSSes ที่ขาดหายไปจะถูกนับโดยการตรวจจับตำแหน่งของ LWSS ด้านหน้าโดยการชนกับ LWSS อื่นเข้าไปในทิศทางตรงกันข้าม การชนกันนี้จะปล่อยเครื่องร่อนซึ่งจะกระตุ้นปฏิกิริยาของ honeybit หนึ่งหรือสองตัวหากผู้กินที่ระบุว่าสภาพการเกิด / การอยู่รอดไม่อยู่

แผนภาพรายละเอียดเพิ่มเติมของแต่ละแง่มุมของ OTCA metapixel สามารถพบได้ที่เว็บไซต์ดั้งเดิม: มันทำงานอย่างไร? .

VarLife

ฉันสร้างตัวจำลองออนไลน์ของกฎเหมือนมีชีวิตซึ่งคุณสามารถทำให้เซลล์ใด ๆ ทำงานตามกฎของชีวิตและเรียกมันว่า "การเปลี่ยนแปลงของชีวิต" ชื่อนี้ย่อมาจาก "VarLife" เพื่อให้กระชับยิ่งขึ้น นี่คือภาพหน้าจอของมัน (ลิงก์ไปที่นี่: http://play.starmaninnovations.com/varlife/BeeHkfCpNR ):

ภาพหน้าจอของ VarLife

คุณสมบัติที่โดดเด่น:

  • สลับเซลล์ระหว่างชีวิต / ตายและทาสีกระดานด้วยกฎที่แตกต่างกัน
  • ความสามารถในการเริ่มและหยุดการจำลองและทำทีละขั้นตอน นอกจากนี้ยังเป็นไปได้ที่จะทำตามจำนวนขั้นตอนที่กำหนดให้เร็วที่สุดหรือช้ากว่าตามอัตราที่กำหนดไว้ในกล่อง ticks-per-second และ milliseconds-per-tick
  • ล้างเซลล์ที่มีชีวิตทั้งหมดหรือเพื่อรีเซ็ตบอร์ดเป็นสถานะว่างเปล่า
  • สามารถเปลี่ยนขนาดของเซลล์และบอร์ดรวมถึงการเปิดใช้งานการห่อวงแหวนในแนวนอนและ / หรือแนวตั้ง
  • ลิงก์ถาวร (ซึ่งเข้ารหัสข้อมูลทั้งหมดใน URL) และ URL แบบสั้น (เพราะบางครั้งมีข้อมูลมากเกินไป แต่ก็ดีอยู่ดี)
  • ชุดกฎพร้อมข้อกำหนด B / S สีและการเลือกแบบสุ่ม
  • และสุดท้าย แต่ไม่ท้ายสุดการเรนเดอร์ gif!

คุณสมบัติ render-to-gif เป็นที่ชื่นชอบของฉันทั้งคู่เพราะต้องใช้งานหลายอย่างเพื่อนำไปใช้งานดังนั้นจึงเป็นที่น่าพอใจอย่างมากเมื่อฉันแตกมันที่ 7 ในตอนเช้าและเพราะมันทำให้การแบ่งปัน VarLife สร้างกับผู้อื่นได้ง่ายมาก .

วงจรพื้นฐานของ VarLife

สรุปแล้วคอมพิวเตอร์ VarLife ต้องการเพียงเซลล์สี่ประเภทเท่านั้น! แปดสถานะในทุกการนับรัฐที่ตาย / มีชีวิตอยู่ พวกเขาคือ:

  • B / S (ดำ / ขาว) ซึ่งทำหน้าที่เป็นบัฟเฟอร์ระหว่างส่วนประกอบทั้งหมดเนื่องจากเซลล์ B / S ไม่สามารถมีชีวิตอยู่ได้
  • B1 / S (สีน้ำเงิน / สีฟ้า) ซึ่งเป็นเซลล์หลักชนิดที่ใช้ในการเผยแพร่สัญญาณ
  • B2 / S (เขียว / เหลือง) ซึ่งส่วนใหญ่ใช้สำหรับการควบคุมสัญญาณทำให้มั่นใจได้ว่ามันจะไม่แพร่กระจาย
  • B12 / S1 (แดง / ส้ม) ซึ่งใช้ในสถานการณ์พิเศษบางอย่างเช่นสัญญาณข้ามและการจัดเก็บข้อมูลเล็กน้อย

ใช้ URL สั้น ๆ นี้จะเปิดขึ้น VarLife กับกฎเหล่านี้เข้ารหัสแล้ว: http://play.starmaninnovations.com/varlife/BeeHkfCpNR

สายไฟ

มีการออกแบบลวดที่แตกต่างกันเล็กน้อยที่มีลักษณะแตกต่างกัน

นี่เป็นสายที่ง่ายที่สุดและพื้นฐานที่สุดใน VarLife แถบสีน้ำเงินที่มีขอบเป็นแถบสีเขียว

ลวดพื้นฐาน
URL สั้น ๆ : http://play.starmaninnovations.com/varlife/WcsGmjLiBF

เส้นลวดนี้เป็นทิศทางเดียว นั่นคือมันจะฆ่าสัญญาณใด ๆ ที่พยายามเดินทางไปในทิศทางตรงกันข้าม นอกจากนี้ยังมีเซลล์หนึ่งที่แคบกว่าสายพื้นฐาน

สายไฟทางเดียว
URL สั้น ๆ : http://play.starmaninnovations.com/varlife/ARWgUgPTEJ

สายไฟแนวทแยงนั้นมีอยู่ แต่ก็ไม่ได้ใช้งานมากนัก

ลวดเส้นทแยงมุม
URL สั้น ๆ : http://play.starmaninnovations.com/varlife/kJotsdSXIj

เกตส์

มีหลายวิธีในการสร้างประตูแต่ละบานดังนั้นฉันจะแสดงเพียงตัวอย่างเดียวของแต่ละประเภท gif แรกนี้แสดงให้เห็นถึง AND, XOR และ OR ตามลำดับ แนวคิดพื้นฐานที่นี่ก็คือเซลล์สีเขียวทำหน้าที่คล้ายกับและเซลล์สีน้ำเงินทำหน้าที่เหมือน XOR และเซลล์สีแดงทำหน้าที่เหมือน OR และเซลล์อื่น ๆ ที่อยู่รอบ ๆ พวกมันอยู่ที่นั่นเพื่อควบคุมการไหลอย่างเหมาะสม

และ, ประตู XOR หรือตรรกะ
URL สั้น ๆ : http://play.starmaninnovations.com/varlife/EGTlKktmeI

ประตู AND-NOT ย่อมาจาก "ANT gate" กลายเป็นองค์ประกอบสำคัญ มันเป็นประตูที่ส่งสัญญาณจาก A หากและถ้าไม่มีสัญญาณจาก B ดังนั้น "A AND NOT B"

และไม่ใช่ประตู
URL สั้น ๆ : http://play.starmaninnovations.com/varlife/RsZBiNqIUy

แม้ว่าจะไม่ใช่ประตูแต่กระเบื้องข้ามสายก็ยังคงสำคัญและมีประโยชน์

ข้ามสาย
URL สั้น ๆ : http://play.starmaninnovations.com/varlife/OXMsPyaNTC

บังเอิญไม่มีประตูไม่ได้ที่นี่ เนื่องจากไม่มีสัญญาณขาเข้าจึงต้องสร้างเอาต์พุตคงที่ซึ่งทำงานได้ไม่ดีกับความหลากหลายในการกำหนดเวลาที่ฮาร์ดแวร์คอมพิวเตอร์ปัจจุบันต้องการ เราเข้ากันได้ดีโดยที่ไม่มีมันอยู่ดี

นอกจากนี้องค์ประกอบหลายอย่างได้รับการออกแบบโดยเจตนาเพื่อให้พอดีกับกล่องขอบ 11 ถึง 11 ( กระเบื้อง ) ที่ใช้สัญญาณ 11 เห็บจากการเข้ากระเบื้องเพื่อออกจากแผ่นกระเบื้อง ทำให้ส่วนประกอบแบบแยกส่วนและตบง่ายกว่ากันตามต้องการโดยไม่ต้องกังวลกับการปรับสายไฟสำหรับการเว้นระยะหรือระยะเวลา

หากต้องการดูประตูอื่น ๆ ที่ถูกค้นพบ / สร้างในขั้นตอนของการสำรวจส่วนประกอบวงจรให้ตรวจสอบบล็อกโพสต์นี้โดย PhiNotPi: Building Blocks: จิกเกต

ส่วนประกอบล่าช้า

ในกระบวนการออกแบบฮาร์ดแวร์ของคอมพิวเตอร์ KZhang ได้ออกแบบส่วนประกอบการหน่วงเวลาหลายช่วงดังที่แสดงด้านล่าง

ความล่าช้า 4 ขีด: URL สั้น: http://play.starmaninnovations.com/varlife/gebOMIXxdh
ความล่าช้า 4 เห็บ

ความล่าช้า 5 ขีด: URL สั้น: http://play.starmaninnovations.com/varlife/JItNjJvnUB
ความล่าช้า 5 เห็บ

8-tick delay (สามจุดเข้าใช้งานที่แตกต่างกัน): URL สั้น: http://play.starmaninnovations.com/varlife/nSTRaVEDvA
8 ความล่าช้าติ๊ก

การล่าช้าแบบ 11 ขีด: URL สั้น: http://play.starmaninnovations.com/varlife/kfoADussXA
11 ติ๊กล่าช้า

ความล่าช้า 12 ขีด: URL สั้น: http://play.starmaninnovations.com/varlife/bkamAfUfud
12 ติ๊กล่าช้า

การล่าช้า 14 ขีด: URL สั้น: http://play.starmaninnovations.com/varlife/TkwzYIBWln
14 ติ๊กล่าช้า

ความล่าช้า 15 ขีด (ตรวจสอบโดยเปรียบเทียบกับสิ่งนี้ ): URL สั้น ๆ : http://play.starmaninnovations.com/varlife/jmgpehYlpT
15 ติ๊กล่าช้า

นั่นเป็นเพียงส่วนประกอบวงจรพื้นฐานใน VarLife! ดูโพสต์ฮาร์ดแวร์ของ KZhangสำหรับวงจรหลักของคอมพิวเตอร์!


4
VarLife เป็นหนึ่งในส่วนที่น่าประทับใจที่สุดของโครงการนี้ มันเป็นความเก่งกาจและความเรียบง่ายเมื่อเทียบกับตัวอย่างเช่นWireworldเป็นฟีโนมินัล OTCA Metapixel นั้นมีขนาดใหญ่เกินความจำเป็นแม้ว่าจะเคยมีความพยายามที่จะตีมันลงหรือไม่?
โม่

@primo: Dave Greene เป็นคนที่ทำงานเกี่ยวกับเรื่องนี้ดูเหมือนว่า chat.stackexchange.com/transcript/message/40106098#40106098
El'endia Starman

6
ใช่ทำให้จำนวนของความคืบหน้าในวันหยุดสุดสัปดาห์นี้ในหัวใจของ metacell ที่เป็นมิตรกับ HashLife 512x512 ( conwaylife.com/forums/viewtopic.php?f=&p=51287#p51287 ) เมตาเซลล์อาจมีขนาดเล็กลงขึ้นอยู่กับพื้นที่ "พิกเซล" ขนาดใหญ่ที่ต้องการส่งสัญญาณสถานะของเซลล์เมื่อคุณซูมออก ดูเหมือนจะคุ้มค่าที่จะหยุดที่ไทล์ขนาด 2 ^ N แน่นอนเนื่องจากอัลกอริทึม HashLife ของ Golly จะทำให้คอมพิวเตอร์ทำงานเร็วขึ้นมาก
เดฟกรีน

2
ไม่สามารถใช้สายไฟและประตูในลักษณะที่ "สิ้นเปลือง" น้อยลงหรือไม่ อิเล็กตรอนจะถูกแทนด้วยเครื่องร่อนหรือยานอวกาศ (ขึ้นอยู่กับทิศทาง) ฉันเห็นการจัดเรียงที่เปลี่ยนเส้นทางพวกเขา (และเปลี่ยนจากที่หนึ่งไปยังอีกถ้าจำเป็น) และบางประตูทำงานกับเครื่องร่อน ใช่พวกเขาใช้พื้นที่มากขึ้นการออกแบบมีความซับซ้อนและกำหนดเวลาต้องแม่นยำ แต่เมื่อคุณมีหน่วยการสร้างพื้นฐานเหล่านั้นพวกเขาควรจะง่ายพอที่จะรวมตัวกันและพวกเขาจะใช้พื้นที่น้อยกว่า VarLife ที่ดำเนินการโดยใช้ OTCA มันก็จะวิ่งเร็วขึ้นเช่นกัน
Heimdall

@Heimdall ถึงแม้ว่ามันจะทำงานได้ดี แต่ก็ไม่ได้แสดงผลที่ดีในขณะที่เล่น Tetris
MilkyWay90

649

ส่วนที่ 3: ฮาร์ดแวร์

ด้วยความรู้ของประตูตรรกะและโครงสร้างทั่วไปของโปรเซสเซอร์เราจึงสามารถเริ่มออกแบบส่วนประกอบทั้งหมดของคอมพิวเตอร์

Demultiplexer

อุปกรณ์แยกส่งสัญญาณหรือ demux เป็นส่วนประกอบที่สำคัญของ ROM, RAM และ ALU มันกำหนดเส้นทางสัญญาณอินพุตไปยังหนึ่งในสัญญาณเอาต์พุตจำนวนมากตามข้อมูลตัวเลือกที่กำหนด ประกอบด้วย 3 ส่วนหลักคือตัวแปลงอนุกรมเป็นขนานตัวตรวจสอบสัญญาณและตัวแยกสัญญาณนาฬิกา

เราเริ่มต้นด้วยการแปลงข้อมูลตัวเลือกซีเรียลเป็น "ขนาน" สิ่งนี้ทำได้โดยการแยกและชะลอข้อมูลเพื่อให้บิตข้อมูลซ้ายสุดตัดสัญญาณนาฬิกาที่ 11x11 สแควร์ด้านซ้ายบิตถัดไปของข้อมูลจะตัดสัญญาณนาฬิกาที่จัตุรัส 11x11 ถัดไปเป็นต้น แม้ว่าข้อมูลทุกบิตจะถูกส่งออกในทุกตาราง 11x11 แต่ข้อมูลทุกบิตจะตัดกับสัญญาณนาฬิกาเพียงครั้งเดียว

ตัวแปลงอนุกรมเป็นขนาน

ต่อไปเราจะตรวจสอบเพื่อดูว่าข้อมูลแบบขนานตรงกับที่อยู่ที่ตั้งไว้ เราทำสิ่งนี้โดยใช้ประตู AND และ ANT บนนาฬิกาและข้อมูลแบบขนาน อย่างไรก็ตามเราจำเป็นต้องตรวจสอบให้แน่ใจว่ามีการส่งออกข้อมูลแบบขนานเพื่อให้สามารถเปรียบเทียบได้อีกครั้ง ประตูเหล่านี้เป็นประตูที่ข้าขึ้นมา

การตรวจสอบสัญญาณประตู

ในที่สุดเราเพิ่งแยกสัญญาณนาฬิกาซ้อนตัวตรวจสอบสัญญาณ (หนึ่งสำหรับแต่ละที่อยู่ / เอาต์พุต) และเรามีมัลติเพล็กเซอร์!

เครื่องมัลทิเปล็คเสอร์

รอม

ROM ควรใช้ที่อยู่เป็นอินพุตและส่งคำสั่งที่แอดเดรสนั้นเป็นเอาต์พุต เราเริ่มต้นด้วยการใช้มัลติเพล็กเซอร์เพื่อส่งสัญญาณนาฬิกาไปยังหนึ่งในคำแนะนำ ต่อไปเราต้องสร้างสัญญาณโดยใช้การข้ามลวดและประตู การโยงลวดช่วยให้สัญญาณนาฬิกาเดินทางลงทั้ง 58 บิตของคำสั่งและยังอนุญาตให้สร้างสัญญาณ (ปัจจุบันเป็นแบบขนาน) เพื่อเลื่อนลงผ่าน ROM เพื่อส่งออก

บิตรอม

ต่อไปเราต้องแปลงสัญญาณขนานเป็นข้อมูลอนุกรมและ ROM เสร็จสมบูรณ์

ตัวแปลงขนานกับอนุกรม

รอม

ขณะนี้ ROM สร้างขึ้นโดยการเรียกใช้สคริปต์ใน Golly ที่จะแปลรหัสประกอบจากคลิปบอร์ดของคุณเป็น ROM

SRL, SL, SRA

ประตูลอจิกทั้งสามนี้ใช้สำหรับการเลื่อนบิตและมีความซับซ้อนมากกว่าปกติของคุณและ AND, OR, XOR เป็นต้นเพื่อให้ประตูเหล่านี้ทำงาน ในข้อมูล อาร์กิวเมนต์ที่สองที่กำหนดให้กับประตูเหล่านี้กำหนดจำนวนบิตที่จะเลื่อน

สำหรับ SL และ SRL เราต้องทำ

  1. ตรวจสอบให้แน่ใจว่า 12 บิตที่สำคัญที่สุดไม่ได้เปิดอยู่ (มิฉะนั้นเอาต์พุตจะเป็น 0 เท่านั้น) และ
  2. ทำให้ข้อมูลในปริมาณที่ถูกต้องล่าช้าตามบิตที่มีนัยสำคัญน้อยที่สุด 4 บิต

สิ่งนี้สามารถทำได้ด้วยพวงของ AND / ANT และมัลติเพล็กเซอร์

SRL

SRA นั้นแตกต่างกันเล็กน้อยเนื่องจากเราจำเป็นต้องคัดลอกบิตระหว่างการเปลี่ยน เราทำสิ่งนี้โดยการกดสัญญาณนาฬิกาด้วยสัญญาณบิตจากนั้นก็คัดลอกเอาท์พุทหลายครั้งด้วยตัวแยกลวดและหรือประตู

SRA

สลักรีเซ็ต (SR)

ฟังก์ชั่นส่วนใหญ่ของโปรเซสเซอร์นั้นขึ้นอยู่กับความสามารถในการจัดเก็บข้อมูล การใช้ B12 / S1 2 เซลล์สีแดงเราสามารถทำได้ เซลล์ทั้งสองสามารถต่อกันและยังสามารถอยู่ด้วยกัน การใช้ชุดพิเศษรีเซ็ตและอ่านวงจรเราสามารถสร้างสลัก SR แบบง่ายได้

สลัก SR

Synchronizer

โดยการแปลงข้อมูลอนุกรมเป็นข้อมูลแบบขนานจากนั้นตั้งค่าสลักแลทช์จำนวนมากเราสามารถจัดเก็บข้อมูลทั้งหมดได้ จากนั้นเพื่อให้ข้อมูลออกมาอีกครั้งเราสามารถอ่านและรีเซ็ตสลักทั้งหมดและชะลอข้อมูลตามนั้น สิ่งนี้ช่วยให้เราสามารถเก็บข้อมูลหนึ่งคำ (หรือมากกว่า) ของข้อมูลในขณะที่รออีกคำหนึ่งทำให้สามารถรับข้อมูลสองคำที่มาถึงในเวลาต่างกันได้

Synchronizer

อ่านเคาน์เตอร์

อุปกรณ์นี้ติดตามจำนวนครั้งที่ต้องใช้ที่อยู่จาก RAM มันทำเช่นนี้โดยใช้อุปกรณ์ที่คล้ายกับสลัก SR: ฟลิปฟล็อป T ทุกครั้งที่ฟลิปฟล็อป T รับอินพุตจะเปลี่ยนสถานะ: ถ้าเปิดอยู่จะปิดและในทางกลับกัน เมื่อฟลิปฟล็อป T พลิกจากการเปิดปิดมันจะส่งพัลส์เอาต์พุตซึ่งสามารถป้อนเข้าในฟลิปฟล็อป T ตัวอื่นเพื่อสร้างตัวนับ 2 บิต

เคาน์เตอร์สองบิต

ในการสร้าง Read Counter เราจำเป็นต้องตั้งตัวนับเป็นโหมดการกำหนดที่เหมาะสมด้วย ANT สองประตูและใช้สัญญาณเอาต์พุตของตัวนับเพื่อตัดสินใจว่าจะนำสัญญาณนาฬิกาไปที่ ALU หรือ RAM

อ่านเคาน์เตอร์

อ่านคิว

คิวการอ่านจำเป็นต้องติดตามว่าตัวนับการอ่านส่งอินพุตไปยัง RAM เพื่อให้สามารถส่งเอาต์พุตของ RAM ไปยังตำแหน่งที่ถูกต้อง ในการทำเช่นนั้นเราใช้สลัก SR บางอัน: หนึ่งสลักสำหรับแต่ละอินพุต เมื่อสัญญาณถูกส่งไปที่ RAM จากตัวนับการอ่านสัญญาณนาฬิกาจะถูกแยกและตั้งค่าสลัก SR ของตัวนับ เอาท์พุตของแรมคือ ANDed ด้วย SR latch และสัญญาณนาฬิกาจาก RAM จะรีเซ็ต SR latch

อ่านคิว

ALU

ฟังก์ชั่น ALU คล้ายกับคิวการอ่านซึ่งมันใช้ SR latch เพื่อติดตามตำแหน่งที่จะส่งสัญญาณ อันดับแรกสลัก SR ของวงจรลอจิกที่สอดคล้องกับ opcode ของคำสั่งนั้นถูกตั้งค่าโดยใช้มัลติเพล็กเซอร์ ถัดไปค่าที่หนึ่งและที่สองของอาร์กิวเมนต์คือ ANDed ด้วย SR latch แล้วส่งผ่านไปยังวงจรลอจิก สัญญาณนาฬิกาจะรีเซ็ตสลักในขณะที่ผ่านไปเพื่อให้สามารถใช้ ALU ได้อีกครั้ง (วงจรส่วนใหญ่เล่นกอล์ฟลงและมีการจัดการการหน่วงเวลาเป็นจำนวนมากดังนั้นจึงดูเหมือนเป็นระเบียบเล็กน้อย)

ALU

แกะ

RAM เป็นส่วนที่ซับซ้อนที่สุดของโครงการนี้ มันต้องการการควบคุมที่เฉพาะเจาะจงสำหรับสลัก SR แต่ละตัวที่จัดเก็บข้อมูล สำหรับการอ่านที่อยู่จะถูกส่งไปที่ตัวมัลติเพล็กเซอร์และส่งไปยังหน่วย RAM หน่วย RAM ส่งออกข้อมูลที่เก็บในแบบขนานซึ่งถูกแปลงเป็นอนุกรมและเอาท์พุท สำหรับการเขียนที่อยู่จะถูกส่งไปยังเครื่องมัลติเพล็กเซอร์ที่แตกต่างกันข้อมูลที่จะเขียนจะถูกแปลงจากอนุกรมเป็นขนานและหน่วย RAM จะส่งสัญญาณไปทั่ว RAM

หน่วย RAM ขนาด 22x22 แต่ละพิกเซลมีโครงสร้างพื้นฐานนี้:

หน่วยแรม

การรวมแรมทั้งหมดเข้าด้วยกันเราได้สิ่งที่มีลักษณะดังนี้:

แกะ

วางทุกอย่างเข้าด้วยกัน

ใช้ส่วนประกอบเหล่านี้ทั้งหมดและสถาปัตยกรรมคอมพิวเตอร์ทั่วไปที่อธิบายไว้ในภาพรวมเราสามารถสร้างคอมพิวเตอร์ที่ใช้งานได้!

การดาวน์โหลด: - คอมพิวเตอร์ Tetris สำเร็จรูป - สคริปต์สร้าง ROM, คอมพิวเตอร์ว่างเปล่าและคอมพิวเตอร์ค้นหาเฉพาะ

คอมพิวเตอร์


49
ฉันแค่อยากจะบอกว่าภาพในโพสต์นี้มีความสวยงามในความคิดของฉันด้วยเหตุผลใดก็ตาม : P +1
HyperNeutrino

7
นี่คือสิ่งที่น่าอัศจรรย์ที่สุดที่ฉันเคยเห็น .... ฉันจะ +20 ถ้าฉันทำได้
FantaC

3
@tfbninja คุณสามารถเรียกได้ว่าเป็นรางวัลและคุณสามารถให้ชื่อเสียง 200
Fabian Röling

10
โปรเซสเซอร์นี้มีความเสี่ยงต่อการโจมตีจาก Spectre และ Meltdown หรือไม่ :)
Ferrybig

5
@Ferrybig ไม่มีการทำนายสาขาดังนั้นฉันจึงสงสัย
JAD

621

ส่วนที่ 4: QFTASM และ Cogol

ภาพรวมสถาปัตยกรรม

ในระยะสั้นคอมพิวเตอร์ของเรามีสถาปัตยกรรม RISC Harvard แบบอะซิงโครนัส 16 บิต เมื่อสร้างตัวประมวลผลด้วยมือสถาปัตยกรรม RISC ( ลดชุดคำสั่งคอมพิวเตอร์ ) นั้นเป็นสิ่งจำเป็นอย่างยิ่ง ในกรณีของเรานี้หมายความว่าจำนวนของรหัสที่มีขนาดเล็กและที่สำคัญกว่านั้นคือคำสั่งทั้งหมดจะถูกประมวลผลในลักษณะที่คล้ายกันมาก

สำหรับการอ้างอิงคอมพิวเตอร์ Wireworld ใช้สถาปัตยกรรม transport-triggeredซึ่งเป็นคำสั่งเดียวMOVและการคำนวณถูกดำเนินการโดยการเขียน / อ่านรีจิสเตอร์พิเศษ แม้ว่ากระบวนทัศน์นี้จะนำไปสู่สถาปัตยกรรมที่ง่ายต่อการนำไปปฏิบัติ แต่ผลลัพธ์ก็ไม่สามารถใช้ได้ในแนวเขตแดน: การดำเนินการทางคณิตศาสตร์ / ตรรกะ / เงื่อนไขทั้งหมดต้องการสามคำสั่ง เห็นได้ชัดว่าเราต้องการสร้างสถาปัตยกรรมที่ลึกลับน้อยลง

เพื่อให้โปรเซสเซอร์ของเราง่ายขึ้นในขณะที่เพิ่มความสะดวกในการใช้งานเราได้ทำการตัดสินใจการออกแบบที่สำคัญหลายประการ:

  • ไม่มีการลงทะเบียน ที่อยู่ใน RAM ทุกตัวจะได้รับการปฏิบัติอย่างเท่าเทียมกันและสามารถใช้เป็นอาร์กิวเมนต์สำหรับการดำเนินการใด ๆ ก็ได้ ในความหมายนี้หมายความว่า RAM ทั้งหมดสามารถปฏิบัติเช่นเดียวกับการลงทะเบียน ซึ่งหมายความว่าไม่มีคำแนะนำในการโหลด / ร้านค้าพิเศษ
  • ในหลอดเลือดดำที่คล้ายกันหน่วยความจำแมป ทุกสิ่งที่สามารถเขียนหรืออ่านจากการแบ่งปันรูปแบบการกำหนดที่อยู่แบบครบวงจร ซึ่งหมายความว่าตัวนับโปรแกรม (PC) คือที่อยู่ 0 และความแตกต่างเพียงอย่างเดียวระหว่างคำสั่งปกติและคำแนะนำการควบคุมโฟลว์คือคำแนะนำการไหลของการควบคุมใช้ที่อยู่ 0
  • ข้อมูลเป็นแบบอนุกรมในการส่งข้อมูลขนานในการจัดเก็บ เนื่องจากลักษณะของ "อิเล็กตรอน" เป็นพื้นฐานของคอมพิวเตอร์ของเราการเพิ่มและการลบจึงง่ายต่อการนำไปใช้อย่างมากเมื่อข้อมูลถูกส่งในรูปแบบอนุกรม little-endian (บิตที่มีนัยสำคัญน้อยที่สุด) นอกจากนี้ข้อมูลแบบอนุกรมยังช่วยลดความจำเป็นในการใช้บัสข้อมูลที่ยุ่งยากซึ่งมีทั้งความกว้างและความยุ่งยากในการใช้เวลาอย่างเหมาะสม (เพื่อให้ข้อมูลอยู่ด้วยกัน "เลน" ทั้งหมดของรถบัสจะต้องเดินทางล่าช้าเหมือนกัน)
  • สถาปัตยกรรมฮาร์วาร์ดหมายถึงการแบ่งระหว่างโปรแกรมหน่วยความจำ (ROM) และหน่วยความจำข้อมูล (RAM) แม้ว่าสิ่งนี้จะลดความยืดหยุ่นของตัวประมวลผล แต่สิ่งนี้จะช่วยในเรื่องการปรับขนาดให้ดีที่สุด: ความยาวของโปรแกรมมีขนาดใหญ่กว่าจำนวน RAM ที่เราต้องการดังนั้นเราจึงสามารถแยกโปรแกรมออกเป็น ROM แล้วมุ่งเน้นไปที่การบีบอัด ROM ซึ่งง่ายกว่ามากเมื่ออ่านอย่างเดียว
  • ความกว้างของข้อมูล 16 บิต นี่คือพลังที่เล็กที่สุดของสองที่กว้างกว่าบอร์ด Tetris มาตรฐาน (10 บล็อก) สิ่งนี้ทำให้เรามีช่วงข้อมูลที่ -32768 ถึง +32767 และความยาวโปรแกรมสูงสุดคือคำแนะนำ 65536 (2 ^ 8 = 256 คำสั่งนั้นเพียงพอสำหรับสิ่งที่ง่ายที่สุดที่เราอาจต้องการให้ตัวประมวลผลของเล่นทำ แต่ไม่ใช่ Tetris)
  • การออกแบบแบบอะซิงโครนัส แทนที่จะมีนาฬิกากลาง (หรือหลายเท่าเท่ากัน) กำหนดเวลาของคอมพิวเตอร์ข้อมูลทั้งหมดจะมาพร้อมกับ "สัญญาณนาฬิกา" ซึ่งเดินทางไปพร้อมกับข้อมูลที่ไหลไปรอบ ๆ คอมพิวเตอร์ เส้นทางบางเส้นทางอาจสั้นกว่าเส้นทางอื่นและในขณะนี้อาจทำให้เกิดความยากลำบากสำหรับการออกแบบแบบรวมศูนย์การออกแบบแบบอะซิงโครนัสสามารถจัดการกับการดำเนินการตามตัวแปรเวลาได้อย่างง่ายดาย
  • คำแนะนำทั้งหมดมีขนาดเท่ากัน เรารู้สึกว่าสถาปัตยกรรมที่คำสั่งแต่ละคำสั่งมี 1 ตัวเลือกที่มีตัวถูกดำเนินการ 3 ตัว (ปลายทางของค่าของมูลค่า) เป็นตัวเลือกที่ยืดหยุ่นที่สุด สิ่งนี้ครอบคลุมการดำเนินงานข้อมูลไบนารีรวมถึงการย้ายตามเงื่อนไข
  • ระบบที่อยู่โหมดที่เรียบง่าย การมีโหมดการกำหนดแอดเดรสที่หลากหลายนั้นมีประโยชน์อย่างมากสำหรับการรองรับสิ่งต่าง ๆ เช่นอาร์เรย์หรือการเรียกซ้ำ เราจัดการเพื่อใช้โหมดการระบุที่สำคัญหลายประการด้วยระบบที่ค่อนข้างง่าย

ภาพประกอบของสถาปัตยกรรมของเราอยู่ในโพสต์ภาพรวม

ฟังก์ชั่นและการดำเนินงาน ALU

จากที่นี่มันเป็นเรื่องของการกำหนดฟังก์ชั่นที่โปรเซสเซอร์ของเราควรมี ความใส่ใจเป็นพิเศษคือความสะดวกในการใช้งานรวมถึงความสามารถรอบตัวของแต่ละคำสั่ง

การย้ายตามเงื่อนไข

การเคลื่อนไหวตามเงื่อนไขมีความสำคัญมากและทำหน้าที่เป็นทั้งการควบคุมการไหลขนาดเล็กและขนาดใหญ่ "Small-scale" หมายถึงความสามารถในการควบคุมการดำเนินการย้ายข้อมูลเฉพาะในขณะที่ "large-scale" หมายถึงการใช้เป็นการดำเนินการกระโดดแบบมีเงื่อนไขเพื่อถ่ายโอนโฟลว์ควบคุมไปยังโค้ดใด ๆ ไม่มีการดำเนินการกระโดดโดยเฉพาะเพราะเนื่องจากการแมปหน่วยความจำการย้ายแบบมีเงื่อนไขสามารถคัดลอกข้อมูลไปยัง RAM ปกติและคัดลอกที่อยู่ปลายทางไปยังพีซี นอกจากนี้เรายังเลือกที่จะละทิ้งการเคลื่อนไหวแบบไม่มีเงื่อนไขและการกระโดดแบบไม่มีเงื่อนไขด้วยเหตุผลที่คล้ายกัน: ทั้งคู่สามารถนำไปใช้เป็นการย้ายแบบมีเงื่อนไขโดยมีเงื่อนไขซึ่งเป็นรหัสที่ยากสำหรับ TRUE

เราเลือกที่จะมีการเคลื่อนไหวตามเงื่อนไขสองประเภท: "ย้ายหากไม่ใช่ศูนย์" ( MNZ) และ "ย้ายถ้าน้อยกว่าศูนย์" ( MLZ) ในทางปฏิบัติMNZจำนวนเงินที่จะตรวจสอบว่าบิตใด ๆ ของข้อมูลเป็น 1 ในขณะที่MLZจำนวนเงินที่จะตรวจสอบว่าบิตสัญญาณเป็น 1 พวกเขามีประโยชน์สำหรับความเท่าเทียมกันและการเปรียบเทียบตามลำดับ เหตุผลที่เราเลือกสองตัวนี้เหนือคนอื่นเช่น "ย้ายถ้าเป็นศูนย์" ( MEZ) หรือ "ย้ายถ้ามากกว่าศูนย์" ( MGZ) คือMEZต้องสร้างสัญญาณ TRUE จากสัญญาณว่างขณะที่MGZเป็นการตรวจสอบที่ซับซ้อนมากขึ้น เครื่องหมายบิตเป็น 0 ในขณะที่อีกอย่างน้อยหนึ่งบิตเป็น 1

คณิตศาสตร์

คำแนะนำที่สำคัญที่สุดถัดไปในแง่ของแนวทางการออกแบบตัวประมวลผลคือการดำเนินการทางคณิตศาสตร์ขั้นพื้นฐาน ดังที่ฉันได้กล่าวไปแล้วก่อนหน้านี้เรากำลังใช้ข้อมูลอนุกรมแบบเอนด์เอนด์น้อยโดยมีตัวเลือกของความ endianness ที่กำหนดโดยความง่ายในการเพิ่ม / ลบ โดยการให้บิตที่มีนัยสำคัญน้อยมาถึงอันดับแรกหน่วยเลขคณิตสามารถติดตามบิตนำได้อย่างง่ายดาย

เราเลือกที่จะใช้การแทนส่วนประกอบ 2 ของตัวเลขลบเนื่องจากทำให้การบวกและการลบสอดคล้องกัน เป็นที่น่าสังเกตว่าคอมพิวเตอร์ Wireworld ใช้ส่วนประกอบ 1 อย่าง

การเพิ่มและการลบคือขอบเขตของการสนับสนุนทางคณิตศาสตร์พื้นเมืองของคอมพิวเตอร์ของเรา (นอกเหนือจากการเลื่อนบิตซึ่งจะกล่าวถึงในภายหลัง) การดำเนินการอื่น ๆ เช่นการคูณนั้นซับซ้อนเกินกว่าที่จะจัดการโดยสถาปัตยกรรมของเราและต้องนำไปใช้ในซอฟต์แวร์

การใช้งานระดับบิต

หน่วยประมวลผลของเรามีAND, ORและXORคำแนะนำที่ทำในสิ่งที่คุณคาดหวัง แทนที่จะมีNOTคำสั่งเราเลือกที่จะมีคำสั่ง "และ - ไม่" ( ANT) ความยากลำบากในการNOTเรียนการสอนเป็นอีกครั้งที่จะต้องสร้างสัญญาณจากการขาดสัญญาณซึ่งเป็นเรื่องยากที่มีเซลลูลาร์ออโตมาตา ANTคำแนะนำและผลตอบแทนที่ 1 เท่านั้นถ้าบิตอาร์กิวเมนต์แรกคือ 1 และบิตอาร์กิวเมนต์ที่สองเป็น 0 ดังนั้นNOT xจะเทียบเท่ากับANT -1 x(เช่นเดียวกับXOR -1 x) นอกจากนี้ANTมีความหลากหลายและมีข้อได้เปรียบหลักในการปิดบัง: ในกรณีของโปรแกรม Tetris ที่เราใช้เพื่อลบ tetrominoes

การขยับบิต

การดำเนินการเปลี่ยนบิตเป็นการดำเนินการที่ซับซ้อนที่สุดที่จัดการโดย ALU พวกเขาใช้สองอินพุตข้อมูล: ค่าที่จะเปลี่ยนและจำนวนเงินที่จะเปลี่ยนมัน แม้จะมีความซับซ้อน (เนื่องจากจำนวนตัวแปรที่เปลี่ยนแปลง) การดำเนินการเหล่านี้มีความสำคัญสำหรับงานที่สำคัญมากมายรวมถึงการดำเนินงาน "กราฟิก" จำนวนมากที่เกี่ยวข้องกับ Tetris การเลื่อนบิตจะทำหน้าที่เป็นพื้นฐานสำหรับอัลกอริทึมการคูณ / การหารที่มีประสิทธิภาพ

หน่วยประมวลผลของเรามีการดำเนินการกะสามบิต "shift left" ( SL), "shift right logical" ( SRL) และ "shift right arithmetic" ( SRA) สองบิตแรกการเลื่อน ( SLและSRL) เติมบิตใหม่ด้วยค่าศูนย์ทั้งหมด (หมายความว่าจำนวนลบที่ถูกเลื่อนไปทางขวาจะไม่เป็นลบอีกต่อไป) หากอาร์กิวเมนต์ที่สองของการเปลี่ยนแปลงอยู่นอกช่วง 0 ถึง 15 ผลลัพธ์จะเป็นศูนย์ทั้งหมดตามที่คุณคาดหวัง สำหรับการเลื่อนบิตสุดท้ายSRAบิตจะรักษาสัญญาณของอินพุตและดังนั้นจึงทำหน้าที่เป็นการหารที่แท้จริงโดยสอง

ท่อส่งคำสั่ง

ตอนนี้เป็นเวลาที่จะพูดคุยเกี่ยวกับรายละเอียดบางอย่างของสถาปัตยกรรม แต่ละรอบของ CPU ประกอบด้วยห้าขั้นตอนต่อไปนี้:

1. ดึงคำสั่งปัจจุบันจาก ROM

ค่าปัจจุบันของพีซีใช้สำหรับดึงคำสั่งที่สอดคล้องจาก ROM แต่ละคำสั่งมีหนึ่งรหัสและสามตัวถูกดำเนินการ แต่ละตัวถูกดำเนินการประกอบด้วยหนึ่งคำข้อมูลและโหมดที่อยู่หนึ่ง ส่วนเหล่านี้จะแยกออกจากกันตามที่อ่านจาก ROM

opcode คือ 4 บิตเพื่อรองรับ opcodes ที่ไม่ซ้ำกัน 16 อันโดยกำหนดให้ 11:

0000  MNZ    Move if Not Zero
0001  MLZ    Move if Less than Zero
0010  ADD    ADDition
0011  SUB    SUBtraction
0100  AND    bitwise AND
0101  OR     bitwise OR
0110  XOR    bitwise eXclusive OR
0111  ANT    bitwise And-NoT
1000  SL     Shift Left
1001  SRL    Shift Right Logical
1010  SRA    Shift Right Arithmetic
1011  unassigned
1100  unassigned
1101  unassigned
1110  unassigned
1111  unassigned

2. เขียนผลลัพธ์ (ถ้าจำเป็น) ของคำสั่งก่อนหน้านี้ไปยัง RAM

ขึ้นอยู่กับเงื่อนไขของคำสั่งก่อนหน้า (เช่นค่าของอาร์กิวเมนต์แรกสำหรับการย้ายตามเงื่อนไข) การเขียนจะถูกดำเนินการ ที่อยู่ของการเขียนจะถูกกำหนดโดยตัวถูกดำเนินการที่สามของคำสั่งก่อนหน้า

เป็นสิ่งสำคัญที่จะต้องทราบว่าการเขียนเกิดขึ้นหลังจากดึงคำแนะนำ สิ่งนี้นำไปสู่การสร้างช่วงการหน่วงเวลาของสาขาซึ่งคำสั่งทันทีหลังจากคำสั่ง Branch (การดำเนินการใด ๆ ที่เขียนไปยัง PC) จะถูกดำเนินการแทนคำสั่งแรกที่เป้าหมายสาขา

ในบางกรณี (เช่นการข้ามที่ไม่มีเงื่อนไข) สามารถหน่วงเวลาการหน่วงเวลาของสาขาได้ ในกรณีอื่น ๆ ไม่สามารถทำได้และคำแนะนำหลังจากสาขาต้องปล่อยว่างไว้ นอกจากนี้สล็อตความล่าช้าประเภทนี้หมายความว่าสาขาจะต้องใช้เป้าหมายสาขาที่เป็น 1 ที่อยู่น้อยกว่าคำสั่งเป้าหมายจริงเพื่อบัญชีสำหรับการเพิ่มขึ้นของพีซีที่เกิดขึ้น

กล่าวโดยย่อเนื่องจากผลลัพธ์ของคำสั่งก่อนหน้านี้ถูกเขียนไปที่ RAM หลังจากเรียกคำสั่งถัดไปการกระโดดตามเงื่อนไขจำเป็นต้องมีคำสั่งเปล่าหลังจากนั้นมิฉะนั้นพีซีจะไม่ได้รับการอัพเดตอย่างเหมาะสมสำหรับการกระโดด

3. อ่านข้อมูลสำหรับอาร์กิวเมนต์ของคำสั่งปัจจุบันจาก RAM

ดังที่ได้กล่าวไว้ก่อนหน้านี้ตัวถูกดำเนินการทั้งสามประกอบด้วยคำข้อมูลและโหมดการกำหนดแอดเดรส คำข้อมูลคือ 16 บิตความกว้างเท่ากับ RAM โหมดการกำหนดแอดเดรสคือ 2 บิต

โหมดการกำหนดแอดเดรสสามารถเป็นแหล่งของความซับซ้อนที่สำคัญสำหรับตัวประมวลผลเช่นนี้เนื่องจากโหมดการกำหนดแอดเดรสในโลกแห่งความจริงจำนวนมากเกี่ยวข้องกับการคำนวณหลายขั้นตอน (เช่นการเพิ่มออฟเซ็ต) ในเวลาเดียวกันโหมดการกำหนดแอดเดรสที่หลากหลายมีบทบาทสำคัญในการใช้งานโปรเซสเซอร์

เราพยายามที่จะรวมแนวคิดของการใช้หมายเลขตายตัวเป็นตัวถูกดำเนินการและใช้ที่อยู่ข้อมูลเป็นตัวถูกดำเนินการ สิ่งนี้นำไปสู่การสร้างโหมดการกำหนดแอดเดรสแบบตอบโต้: โหมดการกำหนดแอดเดรสของตัวถูกดำเนินการเป็นเพียงตัวเลขที่แสดงถึงจำนวนครั้งที่ข้อมูลควรถูกส่งไปรอบ ๆ ลูปการอ่าน RAM สิ่งนี้ครอบคลุมที่อยู่ทันทีทางตรงและทางอ้อมสองทาง

00  Immediate:  A hard-coded value. (no RAM reads)
01  Direct:  Read data from this RAM address. (one RAM read)
10  Indirect:  Read data from the address given at this address. (two RAM reads)
11  Double-indirect: Read data from the address given at the address given by this address. (three RAM reads)

หลังจากดำเนินการยกเลิกการลงทะเบียนแล้วตัวดำเนินการทั้งสามของคำสั่งจะมีบทบาทที่แตกต่างกัน ตัวถูกดำเนินการแรกมักจะเป็นอาร์กิวเมนต์แรกสำหรับตัวดำเนินการแบบไบนารี แต่ยังทำหน้าที่เป็นเงื่อนไขเมื่อคำสั่งปัจจุบันเป็นการย้ายแบบมีเงื่อนไข ตัวถูกดำเนินการที่สองทำหน้าที่เป็นอาร์กิวเมนต์ที่สองสำหรับผู้ประกอบการแบบไบนารี ตัวถูกดำเนินการตัวที่สามทำหน้าที่เป็นที่อยู่ปลายทางสำหรับผลลัพธ์ของคำสั่ง

เนื่องจากคำสั่งสองคำแรกทำหน้าที่เป็นข้อมูลในขณะที่คำสั่งที่สามทำหน้าที่เป็นที่อยู่โหมดการกำหนดแอดเดรสจึงมีการตีความที่แตกต่างกันเล็กน้อยขึ้นอยู่กับตำแหน่งที่ใช้ในตัวอย่างเช่นโหมดโดยตรงจะใช้ในการอ่านข้อมูลจากที่อยู่ RAM คงที่ จำเป็นต้องอ่าน RAM หนึ่งอัน) แต่ใช้โหมดทันทีเพื่อเขียนข้อมูลไปยังที่อยู่ RAM คงที่ (เนื่องจากไม่จำเป็นต้องอ่าน RAM)

4. คำนวณผลลัพธ์

opcode และตัวถูกดำเนินการสองตัวแรกจะถูกส่งไปยัง ALU เพื่อดำเนินการแบบไบนารี สำหรับการดำเนินการทางเลขคณิต bitwise และ shift หมายถึงการดำเนินการที่เกี่ยวข้อง สำหรับการย้ายแบบมีเงื่อนไขนี่หมายถึงการคืนค่าตัวถูกดำเนินการที่สอง

opcode และ operand แรกถูกใช้เพื่อคำนวณเงื่อนไขซึ่งกำหนดว่าจะเขียนผลลัพธ์ไปยังหน่วยความจำหรือไม่ ในกรณีของการย้ายแบบมีเงื่อนไขนี่หมายถึงการพิจารณาว่าบิตใด ๆ ในตัวถูกดำเนินการเป็น 1 (สำหรับMNZ) หรือพิจารณาว่าบิตสัญญาณเป็น 1 (สำหรับMLZ ) หาก opcode ไม่ใช่การย้ายเงื่อนไขการเขียนจะดำเนินการเสมอ (เงื่อนไขเป็นจริงเสมอ)

5. เพิ่มตัวนับโปรแกรม

ในที่สุดตัวนับโปรแกรมจะถูกอ่านเพิ่มและเขียน

เนื่องจากตำแหน่งของการเพิ่ม PC ระหว่างคำสั่งการอ่านและการเขียนคำสั่งนั่นหมายความว่าคำสั่งที่เพิ่มพีซีโดย 1 คือ no-op คำสั่งที่คัดลอกพีซีไปยังตัวเองทำให้คำสั่งต่อไปที่จะดำเนินการสองครั้งในแถว แต่ได้รับคำเตือนคำแนะนำพีซีหลาย ๆ อันในแถวอาจทำให้เกิดเอฟเฟกต์ที่ซับซ้อนรวมถึงการวนลูปไม่สิ้นสุดหากคุณไม่ใส่ใจกับขั้นตอนการสอน

Quest for Tetris Assembly

เราสร้างภาษาแอสเซมบลีใหม่ที่ชื่อว่า QFTASM สำหรับโปรเซสเซอร์ของเรา ภาษาแอสเซมบลีนี้ตรงกับ 1-to-1 พร้อมกับรหัสเครื่องใน ROM ของคอมพิวเตอร์

โปรแกรม QFTASM ใด ๆ จะถูกเขียนเป็นชุดคำสั่งหนึ่งรายการต่อบรรทัด แต่ละบรรทัดมีรูปแบบดังนี้:

[line numbering] [opcode] [arg1] [arg2] [arg3]; [optional comment]

รายการ Opcode

ตามที่กล่าวไว้ก่อนหน้านี้คอมพิวเตอร์มีตัวรองรับสิบเอ็ดตัวซึ่งแต่ละตัวมีตัวถูกดำเนินการสามตัว:

MNZ [test] [value] [dest]  – Move if Not Zero; sets [dest] to [value] if [test] is not zero.
MLZ [test] [value] [dest]  – Move if Less than Zero; sets [dest] to [value] if [test] is less than zero.
ADD [val1] [val2] [dest]   – ADDition; store [val1] + [val2] in [dest].
SUB [val1] [val2] [dest]   – SUBtraction; store [val1] - [val2] in [dest].
AND [val1] [val2] [dest]   – bitwise AND; store [val1] & [val2] in [dest].
OR [val1] [val2] [dest]    – bitwise OR; store [val1] | [val2] in [dest].
XOR [val1] [val2] [dest]   – bitwise XOR; store [val1] ^ [val2] in [dest].
ANT [val1] [val2] [dest]   – bitwise And-NoT; store [val1] & (![val2]) in [dest].
SL [val1] [val2] [dest]    – Shift Left; store [val1] << [val2] in [dest].
SRL [val1] [val2] [dest]   – Shift Right Logical; store [val1] >>> [val2] in [dest]. Doesn't preserve sign.
SRA [val1] [val2] [dest]   – Shift Right Arithmetic; store [val1] >> [val2] in [dest], while preserving sign.

ที่อยู่โหมด

ตัวถูกดำเนินการแต่ละตัวมีทั้งค่าข้อมูลและการย้ายที่อยู่ ค่าข้อมูลถูกอธิบายด้วยตัวเลขทศนิยมในช่วง -32768 ถึง 32767 โหมดการกำหนดแอดเดรสถูกอธิบายโดยคำนำหน้าตัวอักษรหนึ่งตัวสำหรับค่าข้อมูล

mode    name               prefix
0       immediate          (none)
1       direct             A
2       indirect           B
3       double-indirect    C 

รหัสตัวอย่าง

ลำดับ Fibonacci ในห้าบรรทัด:

0. MLZ -1 1 1;    initial value
1. MLZ -1 A2 3;   start loop, shift data
2. MLZ -1 A1 2;   shift data
3. MLZ -1 0 0;    end loop
4. ADD A2 A3 1;   branch delay slot, compute next term

รหัสนี้คำนวณลำดับ Fibonacci โดยที่อยู่ RAM 1 มีคำศัพท์ปัจจุบัน มันล้นอย่างรวดเร็วหลังจาก 28657

รหัสสีเทา:

0. MLZ -1 5 1;      initial value for RAM address to write to
1. SUB A1 5 2;      start loop, determine what binary number to covert to Gray code
2. SRL A2 1 3;      shift right by 1
3. XOR A2 A3 A1;    XOR and store Gray code in destination address
4. SUB B1 42 4;     take the Gray code and subtract 42 (101010)
5. MNZ A4 0 0;      if the result is not zero (Gray code != 101010) repeat loop
6. ADD A1 1 1;      branch delay slot, increment destination address

โปรแกรมนี้คำนวณรหัสสีเทาและเก็บรหัสในที่อยู่ที่สำเร็จเริ่มต้นจากที่อยู่ 5 โปรแกรมนี้ใช้คุณสมบัติที่สำคัญหลายประการเช่นการกำหนดที่อยู่ทางอ้อมและการกระโดดตามเงื่อนไข มันจะหยุดเมื่อรหัสสีเทาผลลัพธ์คือ101010ซึ่งเกิดขึ้นสำหรับอินพุต 51 ที่ที่อยู่ 56

ล่ามออนไลน์

El'endia Starman ได้สร้างล่ามออนไลน์ที่มีประโยชน์มากที่นี่ที่นี่คุณสามารถผ่านรหัสตั้งค่าเบรกพอยต์ดำเนินการเขียนด้วยตนเองไปที่ RAM และแสดงภาพ RAM เป็นจอแสดงผล

Cogol

เมื่อกำหนดสถาปัตยกรรมและภาษาแอสเซมบลีแล้วขั้นตอนต่อไปในด้าน "ซอฟต์แวร์" ของโครงการคือการสร้างภาษาระดับสูงขึ้นซึ่งเป็นสิ่งที่เหมาะสำหรับ Tetris ดังนั้นฉันจึงสร้างโคกอล ชื่อเป็นทั้งปุนบน "COBOL" และตัวย่อสำหรับ "C of Game of Life" แม้ว่ามันจะคุ้มค่าที่จะต้องทราบว่า Cogol คือ C สำหรับคอมพิวเตอร์ของเราคืออะไรกับคอมพิวเตอร์จริง

โคกอลอยู่ในระดับเหนือภาษาแอสเซมบลี โดยทั่วไปบรรทัดส่วนใหญ่ในโปรแกรม Cogol แต่ละตัวจะสอดคล้องกับชุดประกอบบรรทัดเดียว แต่มีคุณสมบัติที่สำคัญบางอย่างของภาษา:

  • คุณสมบัติพื้นฐานประกอบด้วยตัวแปรที่มีชื่อพร้อมการมอบหมายและโอเปอเรเตอร์ที่มีไวยากรณ์ที่อ่านง่ายขึ้น ตัวอย่างเช่นADD A1 A2 3จะกลายเป็นz = x + y;กับตัวแปรการทำแผนที่คอมไพเลอร์ไปยังที่อยู่
  • วนรอบโครงสร้างเช่นif(){}, while(){}และdo{}while();เพื่อให้คอมไพเลอร์ที่จับแตกแขนง
  • อาร์เรย์หนึ่งมิติ (พร้อมตัวชี้เลขคณิต) ซึ่งใช้สำหรับบอร์ด Tetris
  • รูทีนย่อยและ call stack สิ่งเหล่านี้มีประโยชน์ในการป้องกันการทำซ้ำของโค้ดขนาดใหญ่และเพื่อสนับสนุนการเรียกซ้ำ

คอมไพเลอร์ (ที่ฉันเขียนตั้งแต่เริ่มต้น) นั้นเรียบง่าย / ไร้เดียงสา แต่ฉันพยายามปรับแต่งโครงสร้างภาษาหลาย ๆ ภาษาเพื่อให้ได้ความยาวโปรแกรมที่คอมไพล์สั้น ๆ

ต่อไปนี้เป็นภาพรวมสั้น ๆ เกี่ยวกับคุณลักษณะของภาษาต่างๆ:

tokenization

ซอร์สโค้ดถูกโทเค็นเชิงเส้นตรง (single-pass) โดยใช้กฎง่าย ๆ เกี่ยวกับอักขระที่อนุญาตให้อยู่ติดกันภายในโทเค็น เมื่อพบอักขระที่ไม่สามารถติดกับอักขระตัวสุดท้ายของโทเค็นปัจจุบันโทเค็นปัจจุบันจะถือว่าเสร็จสมบูรณ์และอักขระใหม่จะเริ่มโทเค็นใหม่ อักขระบางตัว (เช่น{หรือ,) ไม่สามารถอยู่ติดกับตัวละครอื่น ๆ ได้ดังนั้นจึงเป็นโทเค็นของตัวเอง อื่น ๆ (เช่น>หรือ=) ที่ได้รับอนุญาตเท่านั้นที่จะอยู่ใกล้เคียงกับตัวละครอื่น ๆ ที่อยู่ในชั้นเรียนของตนและทำให้สามารถสร้างราชสกุลเช่น>>>, ==หรือแต่ไม่ชอบ>= =2อักขระช่องว่างบังคับให้มีขอบเขตระหว่างโทเค็น แต่จะไม่รวมอยู่ในผลลัพธ์ อักขระที่ยากที่สุดในการทำโทเค็นคือ- เพราะมันสามารถเป็นตัวแทนของการลบและการปฏิเสธเอกภาพและดังนั้นจึงต้องมีปลอกพิเศษ

วจีวิภาค

การแยกวิเคราะห์ทำในแบบ single-pass คอมไพเลอร์มีวิธีการจัดการการสร้างภาษาที่แตกต่างกันและโทเค็นจะแตกออกจากรายการโทเค็นส่วนกลางตามที่ใช้โดยวิธีการคอมไพเลอร์ต่างๆ หากคอมไพเลอร์เคยเห็นโทเค็นที่ไม่ได้คาดไว้จะทำให้เกิดข้อผิดพลาดทางไวยากรณ์

การจัดสรรหน่วยความจำร่วม

คอมไพเลอร์กำหนดตัวแปรโกลบอลแต่ละตัว (คำหรืออาร์เรย์) RAM แอดเดรสที่กำหนดของตนเอง มีความจำเป็นต้องประกาศตัวแปรทั้งหมดโดยใช้คีย์เวิร์ดmyเพื่อให้คอมไพเลอร์รู้ที่จะจัดสรรพื้นที่ให้ เย็นกว่าตัวแปรทั่วโลกที่มีชื่อคือการจัดการที่อยู่หน่วยความจำเริ่มต้น คำแนะนำจำนวนมาก (เงื่อนไขที่สะดุดตาและการเข้าถึงอาร์เรย์จำนวนมาก) ต้องใช้ที่อยู่ "เริ่มต้น" ชั่วคราวเพื่อจัดเก็บการคำนวณระดับกลาง ในระหว่างกระบวนการรวบรวมคอมไพเลอร์จะจัดสรรและยกเลิกการจัดสรรที่อยู่ในสครับตามความจำเป็น หากคอมไพเลอร์ต้องการที่อยู่ที่มีรอยขีดข่วนมากขึ้นก็จะอุทิศ RAM มากขึ้นเป็นที่อยู่ของรอยขีดข่วน ฉันเชื่อว่าเป็นเรื่องปกติสำหรับโปรแกรมที่จะต้องใช้เพียงไม่กี่ที่อยู่ในการลบถึงแม้ว่าที่อยู่ของแต่ละคนจะถูกนำมาใช้หลายครั้ง

IF-ELSE งบ

ไวยากรณ์สำหรับif-elseคำสั่งเป็นรูปแบบ C มาตรฐาน:

other code
if (cond) {
  first body
} else {
  second body
}
other code

เมื่อแปลงเป็น QFTASM โค้ดจะถูกจัดเรียงดังนี้:

other code
condition test
conditional jump
first body
unconditional jump
second body (conditional jump target)
other code (unconditional jump target)

หากร่างแรกถูกดำเนินการส่วนที่สองจะถูกข้ามไป หากเนื้อหาแรกถูกข้ามไปเนื้อหาที่สองจะถูกดำเนินการ

ในแอสเซมบลีการทดสอบเงื่อนไขมักจะเป็นเพียงการลบและเครื่องหมายของผลลัพธ์จะเป็นตัวกำหนดว่าจะทำการกระโดดหรือดำเนินการกับร่างกาย MLZการเรียนการสอนจะใช้ในการจัดการกับความไม่เท่าเทียมกันเช่นหรือ> การเรียนการสอนจะใช้ในการจัดการเพราะมันกระโดดทั่วร่างกายเมื่อความแตกต่างไม่ได้เป็นศูนย์ (และดังนั้นเมื่อการขัดแย้งจะไม่เท่ากัน) ไม่รองรับเงื่อนไขแบบหลายนิพจน์<=MNZ==

หากelseละเว้นคำสั่งการข้ามแบบไม่มีเงื่อนไขจะถูกข้ามด้วยเช่นกันและรหัส QFTASM มีลักษณะดังนี้:

other code
condition test
conditional jump
body
other code (conditional jump target)

WHILE งบ

ไวยากรณ์สำหรับwhileงบเป็นรูปแบบ C มาตรฐานด้วย:

other code
while (cond) {
  body
}
other code

เมื่อแปลงเป็น QFTASM โค้ดจะถูกจัดเรียงดังนี้:

other code
unconditional jump
body (conditional jump target)
condition test (unconditional jump target)
conditional jump
other code

การทดสอบเงื่อนไขและการกระโดดตามเงื่อนไขอยู่ที่จุดสิ้นสุดของบล็อกซึ่งหมายความว่าจะถูกดำเนินการอีกครั้งหลังจากการดำเนินการของบล็อกแต่ละครั้ง เมื่อเงื่อนไขส่งคืนค่าเท็จร่างกายจะไม่ทำซ้ำและลูปสิ้นสุด ในระหว่างการเริ่มการทำงานลูปการไหลของการควบคุมจะกระโดดข้ามลูปบอดี้ไปยังรหัสเงื่อนไขดังนั้นร่างกายจะไม่ถูกดำเนินการหากเงื่อนไขเป็นเท็จในครั้งแรก

MLZการเรียนการสอนจะใช้ในการจัดการกับความไม่เท่าเทียมกันเช่นหรือ> <=ต่างจากifคำแถลงMNZคำสั่งที่ใช้ในการจัดการ!=เนื่องจากมันกระโดดไปที่ร่างกายเมื่อความแตกต่างไม่เป็นศูนย์ (ดังนั้นเมื่อข้อโต้แย้งไม่เท่ากัน)

DO-WHILE งบ

ความแตกต่างเพียงอย่างเดียวระหว่างwhileและdo-whileคือตัวdo-whileลูปไม่ได้ข้ามไปในตอนแรกดังนั้นจึงถูกเรียกใช้งานอย่างน้อยหนึ่งครั้งเสมอ ฉันมักจะใช้do-whileคำสั่งเพื่อบันทึกรหัสแอสเซมบลีสองบรรทัดเมื่อฉันรู้ว่าลูปไม่จำเป็นต้องข้ามทั้งหมด

อาร์เรย์

อาร์เรย์หนึ่งมิติถูกนำไปใช้เป็นบล็อกหน่วยความจำต่อเนื่อง อาร์เรย์ทั้งหมดมีความยาวคงที่ตามการประกาศ มีการประกาศอาร์เรย์ดังนี้:

my alpha[3];               # empty array
my beta[11] = {3,2,7,8};   # first four elements are pre-loaded with those values

สำหรับอาร์เรย์นี่เป็นการจับคู่ RAM ที่เป็นไปได้ซึ่งแสดงว่าที่อยู่ 15-18 สำรองไว้สำหรับอาร์เรย์อย่างไร:

15: alpha
16: alpha[0]
17: alpha[1]
18: alpha[2]

ที่อยู่ที่มีป้ายกำกับalphaจะเต็มไปด้วยตัวชี้ไปยังตำแหน่งของalpha[0]ดังนั้นในกรณีที่อยู่ 15 นี้มีค่า 16 alphaตัวแปรสามารถใช้ภายในรหัส Cogol อาจเป็นตัวชี้สแต็กถ้าคุณต้องการใช้อาร์เรย์นี้เป็นกองซ้อน .

การเข้าถึงองค์ประกอบของอาร์เรย์ทำได้ด้วยarray[index]สัญกรณ์มาตรฐาน หากค่าของindexเป็นค่าคงที่การอ้างอิงนี้จะถูกเติมด้วยที่อยู่สัมบูรณ์ขององค์ประกอบนั้นโดยอัตโนมัติ มิฉะนั้นจะดำเนินการทางคณิตศาสตร์พอยน์เตอร์บางส่วน (เพียงแค่เพิ่ม) เพื่อค้นหาที่อยู่ที่แน่นอนที่ต้องการ alpha[beta[1]]นอกจากนี้ยังเป็นไปได้ที่จะจัดทำดัชนีรังเช่น

รูทีนย่อยและการโทร

รูทีนย่อยเป็นกลุ่มของรหัสที่สามารถเรียกใช้จากบริบทหลาย ๆ อันเพื่อป้องกันการทำซ้ำของรหัสและอนุญาตให้สร้างโปรแกรมซ้ำ นี่คือโปรแกรมที่มีรูทีนย่อยแบบเรียกซ้ำเพื่อสร้างหมายเลขฟีโบนักชี (โดยทั่วไปเป็นอัลกอริธึมที่ช้าที่สุด):

# recursively calculate the 10th Fibonacci number
call display = fib(10).sum;
sub fib(cur,sum) {
  if (cur <= 2) {
    sum = 1;
    return;
  }
  cur--;
  call sum = fib(cur).sum;
  cur--;
  call sum += fib(cur).sum;
}

มีการประกาศรูทีนย่อยด้วยคีย์เวิร์ดsubและสามารถวางรูทีนย่อยไว้ที่ใดก็ได้ภายในโปรแกรม แต่ละรูทีนย่อยสามารถมีตัวแปรโลคัลหลายตัวซึ่งถูกประกาศเป็นส่วนหนึ่งของรายการอาร์กิวเมนต์ ข้อโต้แย้งเหล่านี้ยังสามารถได้รับค่าเริ่มต้น

เพื่อจัดการกับการเรียกซ้ำ recursive ตัวแปรโลคัลของรูทีนย่อยจะถูกเก็บไว้ในสแต็ก ตัวแปรสแตติกสุดท้ายใน RAM คือตัวชี้สแต็กการโทรและหน่วยความจำทั้งหมดหลังจากนั้นทำหน้าที่เป็นสแต็กการโทร เมื่อเรียกรูทีนย่อยมันจะสร้างเฟรมใหม่บน call stack ซึ่งรวมถึงตัวแปรโลคัลทั้งหมดรวมถึงแอดเดรส return (ROM) แต่ละรูทีนย่อยในโปรแกรมจะได้รับที่อยู่ RAM แบบคงที่เดียวเพื่อใช้เป็นตัวชี้ ตัวชี้นี้ให้ตำแหน่งของการเรียก "ปัจจุบัน" ของรูทีนย่อยใน call stack การอ้างอิงตัวแปรโลคัลถูกดำเนินการโดยใช้ค่าของตัวชี้แบบสแตติกนี้บวกกับออฟเซ็ตเพื่อให้ที่อยู่ของตัวแปรโลคอลนั้น ๆ ยังมีอยู่ใน call stack เป็นค่าก่อนหน้าของตัวชี้คงที่ นี่'

RAM map:
0: pc
1: display
2: scratch0
3: fib
4: scratch1
5: scratch2
6: scratch3
7: call

fib map:
0: return
1: previous_call
2: cur
3: sum

สิ่งหนึ่งที่น่าสนใจเกี่ยวกับรูทีนย่อยคือพวกมันไม่คืนค่าใด ๆ แต่จะสามารถอ่านตัวแปรโลคัลของรูทีนย่อยทั้งหมดได้หลังจากดำเนินการรูทีนย่อยเพื่อดึงข้อมูลที่หลากหลายจากการเรียกรูทีนย่อย สิ่งนี้สามารถทำได้โดยการเก็บตัวชี้สำหรับการเรียกรูทีนย่อยเฉพาะนั้นซึ่งสามารถใช้เพื่อกู้คืนตัวแปรโลคอลใด ๆ จากภายในกรอบสแต็ก (เพิ่งถูกจัดสรรคืน)

มีหลายวิธีในการเรียกรูทีนย่อยทั้งหมดโดยใช้callคำหลัก:

call fib(10);   # subroutine is executed, no return vaue is stored

call pointer = fib(10);   # execute subroutine and return a pointer
display = pointer.sum;    # access a local variable and assign it to a global variable

call display = fib(10).sum;   # immediately store a return value

call display += fib(10).sum;   # other types of assignment operators can also be used with a return value

สามารถกำหนดค่าจำนวนเท่าใดก็ได้เป็นอาร์กิวเมนต์สำหรับการเรียกรูทีนย่อย อาร์กิวเมนต์ใด ๆ ที่ไม่ได้ระบุจะถูกกรอกด้วยค่าเริ่มต้นหากมี อาร์กิวเมนต์ที่ไม่ได้จัดเตรียมไว้และไม่มีค่าเริ่มต้นจะไม่ถูกล้างออก (เพื่อบันทึกคำแนะนำ / เวลา) ดังนั้นอาจเกิดขึ้นกับค่าใด ๆ ที่จุดเริ่มต้นของรูทีนย่อย

พอยน์เตอร์เป็นวิธีการเข้าถึงตัวแปรย่อยหลายตัวของรูทีนย่อยแม้ว่าจะเป็นสิ่งสำคัญที่จะต้องทราบว่าตัวชี้เป็นเพียงชั่วคราว: ข้อมูลที่ตัวชี้ชี้จะถูกทำลายเมื่อมีการเรียกรูทีนย่อยอื่น

การดีบักป้ายกำกับ

{...}โค้ดบล็อกใด ๆในโปรแกรม Cogol สามารถนำหน้าด้วยเลเบลอธิบายหลายคำ ป้ายกำกับนี้ถูกแนบเป็นความคิดเห็นในรหัสแอสเซมบลีที่รวบรวมและอาจมีประโยชน์มากสำหรับการตรวจแก้จุดบกพร่องเนื่องจากทำให้ง่ายต่อการค้นหาตำแหน่งของรหัสเฉพาะ

การเพิ่มประสิทธิภาพสล็อตสาขาล่าช้า

เพื่อที่จะปรับปรุงความเร็วของโค้ดที่คอมไพล์แล้วคอมไพเลอร์ Cogol จะทำการเพิ่มประสิทธิภาพการหน่วงเวลาสล็อตขั้นพื้นฐานบางอย่างเป็นการส่งผ่านรหัส QFTASM ขั้นสุดท้าย สำหรับการกระโดดที่ไม่มีเงื่อนไขใด ๆ กับสล็อตการหน่วงเวลาสาขาที่ว่างเปล่าสล็อตการหน่วงเวลาสามารถถูกเติมด้วยคำสั่งแรกที่ปลายทางการกระโดดและปลายทางการกระโดดจะเพิ่มขึ้นหนึ่งเพื่อชี้ไปที่คำสั่งถัดไป โดยทั่วไปจะบันทึกหนึ่งรอบในแต่ละครั้งที่ดำเนินการกระโดดแบบไม่มีเงื่อนไข

การเขียนโค้ด Tetris ใน Cogol

โปรแกรม Tetris สุดท้ายที่ถูกเขียนใน Cogol และรหัสที่มาสามารถใช้ได้ที่นี่ รหัส QFTASM รวบรวมใช้ได้ที่นี่ เพื่อความสะดวกคุณสามารถดู Permalink ได้ที่นี่: Tetris ใน QFTASM QFTASM เนื่องจากเป้าหมายคือการตีกอล์ฟรหัสการประกอบ (ไม่ใช่รหัสโคกอล) รหัสโคกอลที่เป็นผลลัพธ์จึงไม่น่าเชื่อถือ หลาย ๆ ส่วนของโปรแกรมโดยปกติจะอยู่ในรูทีนย่อย แต่รูทีนย่อยเหล่านั้นสั้นมากพอที่จะทำซ้ำคำสั่งที่บันทึกรหัสไว้บนcallงบ รหัสสุดท้ายมีเพียงหนึ่งรูทีนย่อยนอกเหนือจากรหัสหลัก นอกจากนี้อาร์เรย์จำนวนมากจะถูกลบและแทนที่ด้วยรายการของตัวแปรแต่ละตัวที่มีความยาวเท่ากันหรือโดยตัวเลขจำนวนมากในโปรแกรม รหัส QFTASM ที่คอมไพล์ครั้งสุดท้ายอยู่ภายใต้ 300 คำสั่งถึงแม้ว่ามันจะมีความยาวมากกว่าแหล่ง Cogol เพียงเล็กน้อยเท่านั้น


22
ฉันชอบที่คำแนะนำการเลือกภาษาแอสเซมบลีถูกกำหนดโดยฮาร์ดแวร์ของคุณ (ไม่มี MEZ เนื่องจากการประกอบจริงจากสองเท็จเป็นเรื่องยาก) อ่านที่ยอดเยี่ยม
AlexC

1
คุณบอกว่า=จะสามารถยืนอยู่ถัดจากตัวเอง !=แต่มี
Fabian Röling

@Fabian and a+=
Oliphaunt

@Oliphaunt ใช่คำอธิบายของฉันไม่ถูกต้องมันเป็นสิ่งที่คลาสตัวละครมากขึ้นซึ่งตัวละครบางชั้นสามารถติดกับแต่ละอื่น ๆ
PhiNotPi

606

ตอนที่ 5: การประกอบการแปลและอนาคต

ด้วยโปรแกรมการประกอบของเราจากคอมไพเลอร์ถึงเวลารวบรวม ROM สำหรับคอมพิวเตอร์ Varlife แล้วแปลทุกอย่างเป็นรูปแบบ GoL ขนาดใหญ่!

การชุมนุม

การประกอบแอสเซมบลีโปรแกรมลงใน ROM ทำได้ในลักษณะเดียวกับในการเขียนโปรแกรมแบบดั้งเดิม: แต่ละคำสั่งจะถูกแปลเป็นไบนารีเทียบเท่าและจากนั้นจะต่อกันเป็นไบนารีหยดขนาดใหญ่ที่เราเรียกใช้ปฏิบัติการ สำหรับเราแล้วความแตกต่างเพียงอย่างเดียวคือต้องแปลงไบนาบิตเป็นวงจร Varlife และเชื่อมต่อกับคอมพิวเตอร์

K Zhang เขียนCreateROM.pyสคริปต์ Python สำหรับ Golly ที่ทำหน้าที่ประกอบและแปล มันค่อนข้างตรงไปตรงมา: มันใช้โปรแกรมแอสเซมบลีจากคลิปบอร์ดประกอบเป็นไบนารี่และแปลไบนานั้นเป็นวงจร นี่คือตัวอย่างของเครื่องมือทดสอบแบบง่าย ๆ ที่มาพร้อมกับสคริปต์:

#0. MLZ -1 3 3;
#1. MLZ -1 7 6; preloadCallStack
#2. MLZ -1 2 1; beginDoWhile0_infinite_loop
#3. MLZ -1 1 4; beginDoWhile1_trials
#4. ADD A4 2 4;
#5. MLZ -1 A3 5; beginDoWhile2_repeated_subtraction
#6. SUB A5 A4 5;
#7. SUB 0 A5 2;
#8. MLZ A2 5 0;
#9. MLZ 0 0 0; endDoWhile2_repeated_subtraction
#10. MLZ A5 3 0;
#11. MNZ 0 0 0; endDoWhile1_trials
#12. SUB A4 A3 2;
#13. MNZ A2 15 0; beginIf3_prime_found
#14. MNZ 0 0 0;
#15. MLZ -1 A3 1; endIf3_prime_found
#16. ADD A3 2 3;
#17. MLZ -1 3 0;
#18. MLZ -1 1 4; endDoWhile0_infinite_loop

สิ่งนี้สร้างไบนารีต่อไปนี้:

0000000000000001000000000000000000010011111111111111110001
0000000000000000000000000000000000110011111111111111110001
0000000000000000110000000000000000100100000000000000110010
0000000000000000010100000000000000110011111111111111110001
0000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000011110100000000000000100000
0000000000000000100100000000000000110100000000000001000011
0000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000110100000000000001010001
0000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000001010100000000000000100001
0000000000000000100100000000000001010000000000000000000011
0000000000000001010100000000000001000100000000000001010011
0000000000000001010100000000000000110011111111111111110001
0000000000000001000000000000000000100100000000000001000010
0000000000000001000000000000000000010011111111111111110001
0000000000000000010000000000000000100011111111111111110001
0000000000000001100000000000000001110011111111111111110001
0000000000000000110000000000000000110011111111111111110001

เมื่อแปลเป็นวงจร Varlife จะมีลักษณะเช่นนี้:

รอม

ROM โคลสอัพ

จากนั้น ROM จะเชื่อมโยงกับคอมพิวเตอร์ซึ่งเป็นโปรแกรมที่ทำงานได้อย่างสมบูรณ์ใน Varlife แต่เรายังไม่ได้ทำ ...

แปลสู่เกมแห่งชีวิต

ตลอดเวลานี้เราได้ทำงานในสิ่งที่เป็นนามธรรมต่าง ๆ เหนือฐาน Game of Life แต่ตอนนี้ได้เวลาดึงม่านแห่งนามธรรมและแปลผลงานของเราเป็นรูปแบบเกมแห่งชีวิต ดังที่ได้กล่าวไว้ก่อนหน้านี้เรากำลังใช้OTCA Metapixelเป็นฐานสำหรับ Varlife ดังนั้นขั้นตอนสุดท้ายคือการแปลงแต่ละเซลล์ใน Varlife เป็น metapixel ใน Game of Life

โชคดีที่ Golly มาพร้อมกับสคริปต์ ( metafier.py ) ที่สามารถแปลงรูปแบบในชุดกฎต่างๆเป็นรูปแบบ Game of Life ผ่าน OTCA Metapixel น่าเสียดายที่มันถูกออกแบบมาเพื่อแปลงรูปแบบด้วยชุดกฎสากลเพียงชุดเดียวดังนั้นจึงไม่สามารถใช้งานได้กับ Varlife ฉันเขียนเวอร์ชันที่ได้รับการแก้ไขซึ่งแก้ไขปัญหาดังกล่าวเพื่อให้กฎสำหรับแต่ละ metapixel ถูกสร้างขึ้นบนพื้นฐานแบบเซลล์ต่อเซลล์สำหรับ Varlife

ดังนั้นคอมพิวเตอร์ของเรา (ที่มี Tetris ROM) มีกล่องขอบเขตเท่ากับ 1,436 x 5,082 จาก 7,297,752 เซลล์ในกล่องนั้น 6,075,811 เป็นพื้นที่ว่างเหลือจำนวนประชากรจริงที่ 1,221,941 แต่ละเซลล์เหล่านั้นจำเป็นต้องได้รับการแปลเป็น OTCA metapixel ซึ่งมีกล่องขอบเขต 2048x2048 และประชากร 64,691 (สำหรับ metapixel ON) หรือ 23,920 (สำหรับ OFF metapixel) นั่นหมายความว่าผลิตภัณฑ์ขั้นสุดท้ายจะมีกล่องขอบเขตจำนวน 2,940,928 x 10,407,936 (บวกอีกสองสามพันพิเศษสำหรับชายแดนของ metapixels) มีประชากรระหว่าง 29,228,828,720 และ 79,048,585,231 ด้วย 1 บิตต่อเซลล์ถ่ายทอดสดนั่นคือระหว่าง 27 และ 74 GiB ที่จำเป็นในการเป็นตัวแทนคอมพิวเตอร์และ ROM ทั้งหมด

ฉันรวมการคำนวณเหล่านี้ไว้ที่นี่เพราะฉันละเลยที่จะเรียกใช้ก่อนเริ่มสคริปต์และหน่วยความจำในคอมพิวเตอร์หมดเร็วมาก หลังจากkillคำสั่งpanicked ฉันทำการปรับเปลี่ยนสคริปต์ metafier ทุก ๆ 10 บรรทัดของ metapixels รูปแบบจะถูกบันทึกลงดิสก์ (เป็นไฟล์ gzipped RLE) และกริดจะถูกลบทิ้ง นี่เป็นการเพิ่มรันไทม์พิเศษให้กับการแปลและใช้พื้นที่ดิสก์เพิ่มเติม แต่เก็บการใช้หน่วยความจำภายในขีด จำกัด ที่ยอมรับได้ เนื่องจาก Golly ใช้รูปแบบ RLE ที่ขยายเพิ่มซึ่งรวมถึงตำแหน่งของรูปแบบสิ่งนี้จึงไม่เพิ่มความซับซ้อนให้กับการโหลดรูปแบบ - เพียงแค่เปิดไฟล์รูปแบบทั้งหมดในเลเยอร์เดียวกัน

K Zhang สร้างขึ้นจากงานนี้และสร้างสคริปต์ metafier ที่มีประสิทธิภาพมากขึ้นซึ่งใช้รูปแบบไฟล์ MacroCell ซึ่งโหลดมีประสิทธิภาพมากกว่า RLE สำหรับรูปแบบขนาดใหญ่ สคริปต์นี้ทำงานได้เร็วขึ้นอย่างมาก (ไม่กี่วินาทีเมื่อเทียบกับสคริปต์ metafier ดั้งเดิมหลายชั่วโมง) สร้างเอาต์พุตที่มีขนาดเล็กมาก (121 KB เทียบกับ 1.7 GB) และสามารถ metafy ทั้งคอมพิวเตอร์และ ROM ในคราวเดียวโดยไม่ต้องใช้จำนวนมาก ของหน่วยความจำ มันใช้ประโยชน์จากความจริงที่ว่าไฟล์ MacroCell เข้ารหัสต้นไม้ที่อธิบายรูปแบบ ด้วยการใช้ไฟล์แม่แบบที่กำหนดเอง metapixels จะถูกโหลดล่วงหน้าลงในทรีและหลังจากการคำนวณและการปรับเปลี่ยนสำหรับการตรวจจับเพื่อนบ้านบางอย่างรูปแบบ Varlife สามารถผนวกเข้าด้วยกันได้

แฟ้มรูปแบบของคอมพิวเตอร์ทั้งหมดและ ROM ในเกมชีวิตสามารถพบได้ที่นี่


อนาคตของโครงการ

ตอนนี้เราได้ทำ Tetris เสร็จแล้วใช่ไหม ไม่ได้ใกล้เคียง. เรามีเป้าหมายอีกหลายประการสำหรับโครงการนี้ที่เรากำลังดำเนินการ:

  • muddyfish และ Kritixi Lithos กำลังทำงานอย่างต่อเนื่องในภาษาระดับสูงที่รวบรวมไปยัง QFTASM
  • El'endia Starman กำลังทำงานเพื่ออัปเกรดเป็นล่าม QFTASM ออนไลน์
  • quartata กำลังทำงานบนแบ็กเอนด์ GCC ซึ่งจะช่วยให้สามารถรวบรวมรหัส C และ C ++ อิสระ (และภาษาอื่น ๆ เช่น Fortran, D หรือ Objective-C) เป็น QFTASM ผ่าน GCC สิ่งนี้จะช่วยให้โปรแกรมที่ซับซ้อนมากขึ้นถูกสร้างขึ้นในภาษาที่คุ้นเคยมากกว่าแม้ว่าจะไม่มีไลบรารี่มาตรฐาน
  • หนึ่งในอุปสรรคที่ใหญ่ที่สุดที่เราต้องเอาชนะก่อนที่เราจะสามารถก้าวหน้าได้มากขึ้นก็คือความจริงที่ว่าเครื่องมือของเราไม่สามารถปล่อยรหัสที่ไม่ขึ้นกับตำแหน่ง (เช่นการกระโดดสัมพัทธ์) หากไม่มี PIC เราจะไม่สามารถทำการเชื่อมโยงได้ดังนั้นเราจึงพลาดข้อดีที่มาจากความสามารถในการเชื่อมโยงไปยังห้องสมุดที่มีอยู่ เรากำลังพยายามหาวิธีในการทำ PIC อย่างถูกต้อง
  • เรากำลังพูดถึงโปรแกรมถัดไปที่เราต้องการเขียนสำหรับคอมพิวเตอร์ QFT ตอนนี้ Pong ดูเหมือนเป็นเป้าหมายที่ดี

2
เพียงแค่มองไปที่ส่วนย่อยในอนาคตไม่ได้เป็นญาติกระโดดเพียงADD PC offset PC? ขอโทษที่ฉันซื่อๆหากนี่ไม่ถูกต้องการเขียนโปรแกรมแอสเซมบลีไม่เคยเป็นมือขวาของฉัน
MBraedley

3
@ Timmmm ใช่ แต่ช้ามาก (คุณต้องใช้ HashLife ด้วย)
สปาเก็ตตี้

75
โปรแกรมถัดไปที่คุณเขียนควรเป็น Game of Life ของ Conway
ACK_stoverflow

13
@ACK_stoverflow นั่นจะเกิดขึ้นในบางจุด
Mego

13
คุณมีวิดีโอของมันทำงานอยู่หรือไม่
PyRulez

583

ตอนที่ 6: คอมไพเลอร์ใหม่กว่าเพื่อ QFTASM

แม้ว่า Cogol จะเพียงพอสำหรับการนำ Tetris ไปใช้เป็นพื้นฐาน แต่ก็ง่ายเกินไปและต่ำเกินไปสำหรับการโปรแกรมมิงวัตถุประสงค์ทั่วไปในระดับที่อ่านได้ง่าย เราเริ่มทำงานกับภาษาใหม่ในเดือนกันยายน 2559 ความคืบหน้าของภาษาช้าเนื่องจากยากที่จะเข้าใจข้อบกพร่องเช่นเดียวกับชีวิตจริง

เราสร้างภาษาระดับต่ำที่มีไวยากรณ์คล้ายกับ Python รวมถึงระบบประเภทง่ายรูทีนย่อยที่รองรับการเรียกซ้ำและตัวดำเนินการอินไลน์ คอมไพเลอร์จากข้อความถึง QFTASM ถูกสร้างขึ้นด้วย 4 ขั้นตอน: โทเค็นเนอร์, ต้นไม้ไวยากรณ์, คอมไพเลอร์ระดับสูงและคอมไพเลอร์ระดับต่ำ

tokeniser

การพัฒนาเริ่มต้นโดยใช้ Python โดยใช้ไลบรารี tokeniser ในตัวซึ่งหมายความว่าขั้นตอนนี้ค่อนข้างง่าย จำเป็นต้องมีการเปลี่ยนแปลงเพียงเล็กน้อยเท่านั้นสำหรับเอาต์พุตเริ่มต้นรวมถึงการลบความคิดเห็น (แต่ไม่ใช่#includes)

ต้นไม้ไวยากรณ์

แผนผังไวยากรณ์ถูกสร้างขึ้นเพื่อให้ขยายได้อย่างง่ายดายโดยไม่ต้องแก้ไขซอร์สโค้ดใด ๆ

โครงสร้างต้นไม้ถูกเก็บไว้ในไฟล์ XML ซึ่งรวมถึงโครงสร้างของโหนดที่สามารถสร้างต้นไม้และวิธีที่พวกเขาทำขึ้นกับโหนดและโทเค็นอื่น ๆ

ไวยากรณ์ที่จำเป็นในการสนับสนุนโหนดซ้ำ ๆ เช่นเดียวกับตัวเลือก สิ่งนี้ทำได้โดยการแนะนำเมตาแท็กเพื่ออธิบายวิธีอ่านโทเค็น

โทเค็นที่สร้างขึ้นจะได้รับการแยกวิเคราะห์ผ่านกฎของไวยากรณ์เพื่อให้ผลลัพธ์เป็นโครงสร้างขององค์ประกอบไวยากรณ์เช่นsubs และgeneric_variablesซึ่งจะมีองค์ประกอบไวยากรณ์และโทเค็นอื่น ๆ

รวบรวมเป็นรหัสระดับสูง

แต่ละคุณสมบัติของภาษาจะต้องสามารถรวบรวมเป็นโครงสร้างระดับสูงได้ เหล่านี้รวมถึงและassign(a, 12) call_subroutine(is_prime, call_variable=12, return_variable=temp_var)คุณสมบัติเช่นการอินไลน์ขององค์ประกอบจะถูกดำเนินการในส่วนนี้ สิ่งเหล่านี้ถูกกำหนดให้เป็นoperators และมีความพิเศษในการที่พวกเขา inline ทุกครั้งที่ผู้ประกอบการเช่น+หรือ%มีการใช้ ด้วยเหตุนี้พวกเขาจึงถูก จำกัด มากกว่ารหัสปกติ - พวกเขาไม่สามารถใช้โอเปอเรเตอร์ของตัวเองหรือโอเปอเรเตอร์ใด ๆ ที่อาศัยการกำหนดไว้

ในระหว่างกระบวนการอินไลน์ตัวแปรภายในจะถูกแทนที่ด้วยสิ่งที่ถูกเรียก ผลกระทบนี้จะเปลี่ยน

operator(int a + int b) -> int c
    return __ADD__(a, b)
int i = 3+3

เข้าไป

int i = __ADD__(3, 3)

พฤติกรรมนี้สามารถเป็นอันตรายและข้อผิดพลาดได้ง่ายหากตัวแปรอินพุตและตัวแปรเอาต์พุตชี้ไปที่ตำแหน่งเดียวกันในหน่วยความจำ ในการใช้พฤติกรรม 'ปลอดภัย' unsafeคำหลักจะปรับกระบวนการรวบรวมซึ่งตัวแปรเพิ่มเติมจะถูกสร้างและคัดลอกไปยังและจากอินไลน์ตามต้องการ

ลบล้างตัวแปรและการดำเนินการที่ซับซ้อน

การคำนวณทางคณิตศาสตร์เช่นa += (b + c) * 4ไม่สามารถคำนวณได้โดยไม่ต้องใช้เซลล์หน่วยความจำเพิ่มเติม คอมไพเลอร์ระดับสูงจัดการกับสิ่งนี้โดยแยกการดำเนินการออกเป็นส่วนต่างๆ:

scratch_1 = b + c
scratch_1 = scratch_1 * 4
a = a + scratch_1

สิ่งนี้แนะนำแนวคิดของตัวแปรรอยขีดข่วนที่ใช้สำหรับการจัดเก็บข้อมูลระดับกลางของการคำนวณ มันถูกจัดสรรตามที่ต้องการและจัดสรรคืนในพูลทั่วไปเมื่อเสร็จแล้ว สิ่งนี้จะลดจำนวนตำแหน่งหน่วยความจำรอยขีดข่วนที่จำเป็นสำหรับการใช้งาน ตัวแปรรอยขีดข่วนถือว่าเป็นรูปกลม

แต่ละรูทีนย่อยมี VariableStore ของตัวเองเพื่อให้การอ้างอิงกับตัวแปรทั้งหมดที่รูทีนย่อยใช้รวมถึงประเภทของพวกเขา ในตอนท้ายของการรวบรวมพวกเขากำลังแปลเป็น offsets สัมพัทธ์จากจุดเริ่มต้นของการจัดเก็บและจากนั้นให้ที่อยู่ที่แท้จริงใน RAM

โครงสร้างแรม

Program counter
Subroutine locals
Operator locals (reused throughout)
Scratch variables
Result variable
Stack pointer
Stack
...

การรวบรวมระดับต่ำ

สิ่งเดียวที่คอมไพเลอร์ระดับต่ำมีการจัดการกับมีsub, call_sub, return, assign, และif whileนี่คือรายการงานที่ลดลงมากซึ่งสามารถแปลเป็นคำสั่ง QFTASM ได้ง่ายขึ้น

sub

ซึ่งจะหาตำแหน่งเริ่มต้นและจุดสิ้นสุดของรูทีนย่อยที่มีชื่อ คอมไพเลอร์ระดับต่ำจะเพิ่มเลเบลและในกรณีของmainรูทีนย่อยให้เพิ่มคำแนะนำการออก (ข้ามไปยังจุดสิ้นสุดของ ROM)

if และ while

ทั้งสองwhileและifล่ามในระดับต่ำจะสวยง่าย: พวกเขาได้รับตัวชี้ไปยังเงื่อนไขและกระโดดของพวกเขาขึ้นอยู่กับพวกเขา whileลูปแตกต่างกันเล็กน้อยในสิ่งที่พวกเขารวบรวมเป็น

...
condition
jump to check
code
condition
if condtion: jump to code
...

call_sub และ return

ซึ่งแตกต่างจากสถาปัตยกรรมส่วนใหญ่คอมพิวเตอร์ที่เราคอมไพล์ไม่มีไม่มีการสนับสนุนฮาร์ดแวร์สำหรับการพุชและ popping จากสแต็ก ซึ่งหมายความว่าทั้งการกดและการ popping จากสแต็กนั้นต้องใช้สองคำสั่ง ในกรณีของการ popping เราจะลดตัวชี้สแต็คและคัดลอกค่าไปยังที่อยู่ ในกรณีของการพุชเราจะคัดลอกค่าจากที่อยู่ไปยังที่อยู่ที่ตัวชี้สแต็คปัจจุบันแล้วเพิ่มขึ้น

โลคัลทั้งหมดสำหรับรูทีนย่อยจะถูกเก็บไว้ที่ตำแหน่งคงที่ใน RAM ที่กำหนดในเวลารวบรวม เพื่อให้การเรียกใช้ซ้ำเกิดขึ้นชาวบ้านทั้งหมดสำหรับฟังก์ชั่นจะถูกวางลงบนสแต็กเมื่อเริ่มต้นการโทร จากนั้นอาร์กิวเมนต์ไปยังรูทีนย่อยจะถูกคัดลอกไปยังตำแหน่งในโลคัลสโตร์ ค่าของที่อยู่ผู้ส่งจะถูกใส่ลงในสแต็กและรูทีนย่อยจะเรียกใช้งาน

เมื่อreturnพบคำสั่งด้านบนสุดของสแต็กจะถูกเปิดออกและตัวนับโปรแกรมถูกตั้งค่าเป็นค่านั้น ค่าสำหรับคนในท้องถิ่นของรูทีนย่อยการโทรคือการแตกจากสแต็กและอยู่ในตำแหน่งก่อนหน้า

assign

การมอบหมายตัวแปรเป็นสิ่งที่ง่ายที่สุดในการรวบรวม: พวกเขาใช้ตัวแปรและค่าและรวบรวมเป็นบรรทัดเดียว: MLZ -1 VALUE VARIABLE

การกำหนดเป้าหมายการกระโดด

ในที่สุดคอมไพเลอร์ทำงานออกเป้าหมายกระโดดสำหรับป้ายกำกับที่แนบมากับคำแนะนำ กำหนดตำแหน่งสัมบูรณ์ของป้ายผนึกแล้วอ้างอิงถึงป้ายกำกับเหล่านั้นจะถูกแทนที่ด้วยค่าเหล่านั้น ฉลากตัวเองจะถูกลบออกจากรหัสและในที่สุดก็เพิ่มหมายเลขคำสั่งในรหัสที่รวบรวม

ตัวอย่างการรวบรวมทีละขั้นตอน

ตอนนี้เราได้ผ่านทุกขั้นตอนแล้วไปผ่านกระบวนการรวบรวมจริงสำหรับโปรแกรมจริงทีละขั้นตอน

#include stdint

sub main
    int a = 8
    int b = 12
    int c = a * b

ตกลงง่ายพอ a = 8มันควรจะเป็นที่ชัดเจนว่าในตอนท้ายของโปรแกรม b = 12, c = 96, ประการแรกให้รวมส่วนที่เกี่ยวข้องของstdint.txt:

operator (int a + int b) -> int
    return __ADD__(a, b)

operator (int a - int b) -> int
    return __SUB__(a, b)

operator (int a < int b) -> bool
    bool rtn = 0
    rtn = __MLZ__(a-b, 1)
    return rtn

unsafe operator (int a * int b) -> int
    int rtn = 0
    for (int i = 0; i < b; i+=1)
        rtn += a
    return rtn

sub main
    int a = 8
    int b = 12
    int c = a * b

ตกลงซับซ้อนกว่าเล็กน้อย ลองย้ายไปที่ tokeniser แล้วดูว่ามีอะไรเกิดขึ้นบ้าง ในขั้นตอนนี้เราจะมีโทเค็นการไหลเชิงเส้นโดยไม่มีโครงสร้างใด ๆ เท่านั้น

NAME NAME operator
LPAR OP (
NAME NAME int
NAME NAME a
PLUS OP +
NAME NAME int
NAME NAME b
RPAR OP )
OP OP ->
NAME NAME int
NEWLINE NEWLINE
INDENT INDENT     
NAME NAME return
NAME NAME __ADD__
LPAR OP (
NAME NAME a
COMMA OP ,
NAME NAME b
RPAR OP )
...

ตอนนี้โทเค็นทั้งหมดจะถูกใส่ผ่านตัวแยกวิเคราะห์ไวยากรณ์และส่งออกต้นไม้ที่มีชื่อของแต่ละส่วน สิ่งนี้แสดงโครงสร้างระดับสูงที่อ่านได้จากโค้ด

GrammarTree file
 'stmts': [GrammarTree stmts_0
  '_block_name': 'inline'
  'inline': GrammarTree inline
   '_block_name': 'two_op'
   'type_var': GrammarTree type_var
    '_block_name': 'type'
    'type': 'int'
    'name': 'a'
    '_global': False

   'operator': GrammarTree operator
    '_block_name': '+'

   'type_var_2': GrammarTree type_var
    '_block_name': 'type'
    'type': 'int'
    'name': 'b'
    '_global': False
   'rtn_type': 'int'
   'stmts': GrammarTree stmts
    ...

แผนผังไวยากรณ์นี้ตั้งค่าข้อมูลที่จะวิเคราะห์โดยคอมไพเลอร์ระดับสูง มันมีข้อมูลเช่นประเภทโครงสร้างและคุณสมบัติของตัวแปร แผนผังไวยากรณ์จากนั้นรับข้อมูลนี้และกำหนดตัวแปรที่จำเป็นสำหรับรูทีนย่อย ต้นไม้ยังแทรกอินไลน์ทั้งหมด

('sub', 'start', 'main')
('assign', int main_a, 8)
('assign', int main_b, 12)
('assign', int op(*:rtn), 0)
('assign', int op(*:i), 0)
('assign', global bool scratch_2, 0)
('call_sub', '__SUB__', [int op(*:i), int main_b], global int scratch_3)
('call_sub', '__MLZ__', [global int scratch_3, 1], global bool scratch_2)
('while', 'start', 1, 'for')
('call_sub', '__ADD__', [int op(*:rtn), int main_a], int op(*:rtn))
('call_sub', '__ADD__', [int op(*:i), 1], int op(*:i))
('assign', global bool scratch_2, 0)
('call_sub', '__SUB__', [int op(*:i), int main_b], global int scratch_3)
('call_sub', '__MLZ__', [global int scratch_3, 1], global bool scratch_2)
('while', 'end', 1, global bool scratch_2)
('assign', int main_c, int op(*:rtn))
('sub', 'end', 'main')

ถัดไปคอมไพเลอร์ระดับต่ำจะต้องแปลงการแสดงระดับสูงนี้เป็นรหัส QFTASM ตัวแปรถูกกำหนดตำแหน่งใน RAM ดังนี้:

int program_counter
int op(*:i)
int main_a
int op(*:rtn)
int main_c
int main_b
global int scratch_1
global bool scratch_2
global int scratch_3
global int scratch_4
global int <result>
global int <stack>

คำแนะนำง่ายๆนั้นจะถูกรวบรวม ในที่สุดหมายเลขการเรียนการสอนจะถูกเพิ่มส่งผลให้รหัส QFTASM ปฏิบัติการ

0. MLZ 0 0 0;
1. MLZ -1 12 11;
2. MLZ -1 8 2;
3. MLZ -1 12 5;
4. MLZ -1 0 3;
5. MLZ -1 0 1;
6. MLZ -1 0 7;
7. SUB A1 A5 8;
8. MLZ A8 1 7;
9. MLZ -1 15 0;
10. MLZ 0 0 0;
11. ADD A3 A2 3;
12. ADD A1 1 1;
13. MLZ -1 0 7;
14. SUB A1 A5 8;
15. MLZ A8 1 7;
16. MNZ A7 10 0;
17. MLZ 0 0 0;
18. MLZ -1 A3 4;
19. MLZ -1 -2 0;
20. MLZ 0 0 0;

ไวยากรณ์

ตอนนี้เรามีภาษาเปลือยเราต้องเขียนโปรแกรมเล็ก ๆ ในนั้น เรากำลังใช้การเยื้องเช่น Python ทำแบ่งบล็อกตรรกะและการควบคุมการไหล ซึ่งหมายความว่าช่องว่างสำคัญสำหรับโปรแกรมของเรา ทุกโปรแกรมเต็มมีmainรูทีนย่อยที่ทำหน้าที่เหมือนกับmain()ฟังก์ชั่นในภาษา C-like ฟังก์ชั่นจะทำงานเมื่อเริ่มต้นโปรแกรม

ตัวแปรและประเภท

เมื่อมีการกำหนดตัวแปรในครั้งแรกพวกเขาจะต้องมีประเภทที่เกี่ยวข้องกับพวกเขา ชนิดที่กำหนดในปัจจุบันคือintและboolด้วยไวยากรณ์สำหรับอาร์เรย์ที่กำหนด แต่ไม่ใช่คอมไพเลอร์

ห้องสมุดและผู้ประกอบการ

ห้องสมุดที่เรียกว่าstdint.txtมีอยู่ซึ่งกำหนดตัวดำเนินการพื้นฐาน หากสิ่งนี้ไม่รวมอยู่แม้จะไม่ได้กำหนดตัวดำเนินการอย่างง่าย #include stdintเราสามารถใช้ห้องสมุดนี้ด้วย stdintกำหนดผู้ประกอบการเช่น+, >>และแม้กระทั่งการ*และ%ทั้งที่มี opcodes QFTASM โดยตรง

ภาษายังช่วยให้ opcodes QFTASM __OPCODENAME__จะเรียกว่าโดยตรงกับ

นอกจากนี้stdintยังหมายถึง

operator (int a + int b) -> int
    return __ADD__(a, b)

ซึ่งกำหนดสิ่งที่+ผู้ประกอบการทำเมื่อได้รับสองints


1
ฉันจะถามว่าทำไมมันก็ตัดสินใจที่จะสร้าง CA wireworld เหมือนในเกมคอนเวย์ของชีวิตและสร้างหน่วยประมวลผลใหม่โดยใช้วงจรนี้แทนที่จะนำมาใช้ใหม่ / retrofit ที่มีอยู่ cgol คอมพิวเตอร์สากลเช่นนี้ ?
eaglgenes101

4
@ eaglgenes101 สำหรับผู้เริ่มฉันไม่คิดว่าพวกเราส่วนใหญ่ตระหนักถึงการมีอยู่ของคอมพิวเตอร์สากลที่ใช้งานได้อื่น ๆ ความคิดเกี่ยวกับ CA ที่มีลักษณะคล้าย wireworld ซึ่งมีกฎหลายข้อรวมกันเป็นผลมาจากการเล่นกับ metacells (ฉันเชื่อว่า - พีเป็นคนที่คิดขึ้นมา) จากนั้นมันเป็นความก้าวหน้าเชิงตรรกะกับสิ่งที่เราสร้างขึ้น
Mego
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.