- vpternlogd ของ AVX-512 คือคำสั่ง SIMD ที่เลือกและรันตรรกะ Boolean ระดับบิตแบบใดก็ได้ของอินพุตสามตัว ด้วย immediate 8 บิตในครั้งเดียว
- แก่นสำคัญไม่ใช่การท่องจำสมการตรรกะที่ซับซ้อน แต่คือการมอง
#imm8 เป็น ตารางค้นหา สำหรับ 8 กรณีของอินพุต A/B/C
- blitter ของ Amiga ในปี 1985 ก็ใช้วิธีเดียวกันในการระบุการผสมตรรกะ โดยใช้แหล่งบิตแมปสามตัวและ minterm 8 บิต และ
0xE2 ก็ถูกใช้บ่อยกับ masked sprite
- เมื่ออ่าน 8 บิตของคอลัมน์ผลลัพธ์จากล่างขึ้นบน ก็จะได้ค่า immediate ดังนั้นเงื่อนไขอย่าง “มีค่าเป็น 1 อยู่พอดีสองอินพุตจากสามอินพุต” ก็สร้างตรง ๆ เป็น
0x68 ได้
- ตัวอย่าง vpternlogd ในเอกสารของ Intel และ
0xE2 ที่พบบ่อยใน demoscene ของ Amiga เชื่อมโยงกันพอดี แสดงให้เห็นว่าคำสั่ง SIMD สมัยใหม่และฮาร์ดแวร์กราฟิกย้อนยุคมีแนวคิดการออกแบบร่วมกัน
vpternlogd ของ AVX-512
- vpternlogd เป็นคำสั่งตรรกะสามตัวแปรระดับบิตที่สะดุดตาในเอกสารนำเสนอการออกแบบ ISA ของ AVX-512
- มันรับอินพุต A, B, C แล้วแสดงตรรกะ Boolean แบบใดก็ได้ด้วยคำสั่งเดียว
- ตัวอย่าง:
(NOT A) OR ((NOT B) XOR (C AND A))
- อินพุตอาจเป็น รีจิสเตอร์ 512 บิต ทำให้สามารถใช้ตรรกะที่ซับซ้อนกับทั้ง 512 บิตพร้อมกันได้
- แทนที่จะเพิ่มคำสั่งเฉพาะทางอย่าง
foo_and_a_or_not_b ไปเรื่อย ๆ ก็ใช้คำสั่งยืดหยุ่นตัวเดียวร่วมกับ immediate 8 บิตเพื่อรองรับตรรกะได้หลายแบบ
VPTERNLOGD r0, r1, r3, #imm8
#imm8 ใช้กำหนดว่าจะทำฟังก์ชันตรรกะระดับบิตแบบใด
- เอกสารจำนวนมากอธิบายเพียงว่า immediate นี้ “กำหนดฟังก์ชันไบนารีบางอย่าง” ทำให้ยากจะเข้าใจวิธีคำนวณจริงอย่างเป็นธรรมชาติ
Amiga blitter และ minterm
- คอมพิวเตอร์ในยุค 1980 มักใช้ ชิปคัสตอม สำหรับงานกราฟิก
- blitter ของ Commodore Amiga 500 สามารถย้ายกราฟิกแบบบิตแมปจากตำแหน่งหนึ่งไปยังอีกตำแหน่งหนึ่งพร้อมใช้การดำเนินการเชิงตรรกะได้
- มันรองรับแหล่งบิตแมปได้สูงสุดสามแหล่ง และการดำเนินการเชิงตรรกะระหว่างแหล่งเหล่านี้จะถูกกำหนดด้วยค่า minterm แบบ 8 บิต
- ในแง่ที่เลือกการผสมตรรกะด้วยแหล่งข้อมูลสามตัวและค่า 8 บิต มันจึงมีโครงสร้างแบบเดียวกับ vpternlogd
- โปรแกรมเมอร์ Amiga จำนวนมากไม่ได้เข้าใจวิธีคำนวณ minterm โดยตรง แต่จะใช้ค่าที่พบบ่อยซ้ำ ๆ
- ล้างบัฟเฟอร์ใช้
0x00
- วาด masked sprite ใช้
0xE2
- “Amiga Hardware Reference Manual” ปี 1989 อธิบายการคำนวณ minterm ด้วยสัญลักษณ์ที่ชวนสับสน จึงไม่ได้ช่วยผู้ทำเดโมในยุคนั้นมากนัก
คำนวณค่า 8 บิตแบบตารางค้นหา
#imm8 หรือ minterm ไม่จำเป็นต้องมองเป็นการผสมกันของโอเปอเรเตอร์ตรรกะ แต่สามารถมองเป็นตารางค้นหา 8 รายการได้
- อินพุตสามตัว A, B, C ต่างมีค่าได้เป็น 0 หรือ 1 ดังนั้นจึงมีความเป็นไปได้ทั้งหมด 8 แบบ
- เพียงกรอกผลลัพธ์ที่ต้องการลงในคอลัมน์ที่สี่โดยตรง
| A |
B |
C |
ผลลัพธ์ที่ต้องการ |
| 0 |
0 |
0 |
? |
| 0 |
0 |
1 |
? |
| 0 |
1 |
0 |
? |
| 0 |
1 |
1 |
? |
| 1 |
0 |
0 |
? |
| 1 |
0 |
1 |
? |
| 1 |
1 |
0 |
? |
| 1 |
1 |
1 |
? |
- ตัวอย่างเช่น ถ้าต้องการให้ผลลัพธ์เป็น 1 เมื่อมีค่าเป็น 1 อยู่พอดีสองอินพุต จากสามอินพุต ก็กรอกค่าลงในคอลัมน์ที่สี่ตามเงื่อนไขนั้น
- เมื่ออ่าน 8 บิตของคอลัมน์ผลลัพธ์ จากล่างขึ้นบน จะได้
01101000 หรือ 0x68
- ดังนั้นฟังก์ชัน
0x68 จะตั้งผลลัพธ์เป็น 1 เมื่อมีอินพุตสองตัวจากสามตัวเป็น 1 พอดี
- ด้วยวิธีเดียวกันนี้ เราสามารถหาค่า
#imm8 ที่ต้องใช้สำหรับฟังก์ชันตรรกะใด ๆ ระหว่างแหล่งข้อมูลสามตัวได้
0xE2 ของ masked sprite
- หนึ่งในค่า minterm ที่พบบ่อยมากบน Amiga คือ
0xE2
- ค่านี้มักใช้ตอนเรนเดอร์ masked 2D sprite
- A: bitmap ของ sprite
- B: mask ของ sprite
- C: background
- เงื่อนไขสามารถเขียนเป็นตรรกะของโปรแกรมแบบง่าย ๆ ได้ดังนี้
- ถ้าพิกเซล mask B ถูกตั้งค่าไว้ ผลลัพธ์คือ sprite A
- ถ้าพิกเซล mask B ไม่ได้ถูกตั้งค่าไว้ ผลลัพธ์คือ background C
| A |
B |
C |
ผลลัพธ์ที่ต้องการ |
| 0 |
0 |
0 |
0 |
| 0 |
0 |
1 |
1 |
| 0 |
1 |
0 |
0 |
| 0 |
1 |
1 |
0 |
| 1 |
0 |
0 |
0 |
| 1 |
0 |
1 |
1 |
| 1 |
1 |
0 |
1 |
| 1 |
1 |
1 |
1 |
- เมื่ออ่านคอลัมน์ผลลัพธ์จากล่างขึ้นบน จะได้
11100010 หรือ 0xE2
0xE2 เป็นค่า minterm ที่พบได้บ่อยมากในวัฒนธรรม demoscene ของ Amiga
ความเชื่อมโยงที่บังเอิญกับเอกสารของ Intel
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
มีวิธีง่าย ๆ ในการหา immediate value จากนิพจน์ที่ต้องการคำนวณ เช่น ถ้าต้องการคำนวณ
(NOT A) OR ((NOT B) XOR (C AND A))ก็เขียนได้เป็น~_MM_TERNLOG_A | (~_MM_TERNLOG_B ^ (_MM_TERNLOG_C & _MM_TERNLOG_A))มันก็คือนิพจน์ที่ต้องการคำนวณตรง ๆ และจะถูก evaluate เป็น immediate value จากค่าคงที่
_MM_TERNLOG_A/B/Cที่นิยามไว้ใน intrinsic header ของ gcc และ clang:typedef enum { _MM_TERNLOG_A = 0xF0, _MM_TERNLOG_B = 0xCC, _MM_TERNLOG_C = 0xAA } _MM_TERNLOG_ENUM;ใน MSVC ก็นิยามเองได้
A = 0b11110000,B = 0b11001100,C = 0b10101010เห็นแค่ชื่อเรื่องแล้วคิดว่าหมายถึงคำสั่งนี้ทำงานไม่ถูกต้อง แต่บทความจริง ๆ แค่อธิบาย วิธีการทำงาน ของมัน
ตอนเป็นวัยรุ่น ผมไม่ได้เขียนคำว่า “CRAP!” ลงบนหน้านั้นของคู่มือฮาร์ดแวร์ก็จริง แต่ผมนั่งจ้องมันอยู่นานมากเพื่อพยายามทำความเข้าใจ
สุดท้ายก็เหมือนแทบทุกคน คือไปหา Bobs กับ BLTCON0 สำหรับการคัดลอกแบบง่าย ๆ มาใช้ แล้วทำเป็นไม่เห็นส่วนนั้น
แต่หลายปีต่อมาผมได้ A+ วิชาตรรกศาสตร์เชิงคำนวณที่มหาวิทยาลัย ดังนั้นบาดแผลทางใจนั้นอาจช่วยได้อยู่บ้าง
ถ้าพูดถึงชื่อเรื่อง “ternary logic” ปกติหมายถึง ตรรกะที่มีค่าความจริงสามค่า แต่บทความนี้พูดถึงคำสั่งคอมไพเลอร์ที่จัดการเกตตรรกะไบนารีทั้งหมดที่รับอินพุตสามตัว
ternlogและ intrinsic ก็ชื่อternarylogicดังนั้นถึงจะน่าเสียดาย แต่ชื่อเรื่องก็เหมาะสมแล้วแถมคำว่า
bitwiseก็ช่วยแยกจาก “ตรรกะสามค่า” ได้ในระดับหนึ่งอยู่แล้ว และternaryก็ถูกใช้บ่อยมากในความหมายว่าอินพุตสามตัวเช่นกันa ? b : cก็มักเรียกว่า ตัวดำเนินการแบบสามส่วน และจริง ๆ แล้วternlogก็เลียนแบบการดำเนินการสามส่วนนั้นได้ ซึ่งบทความก็พูดถึงเรื่องนั้นโดยตรงtrinaryไม่ใช่ternaryหรือเปล่าจากมุมมองของ C++, JavaScript, Python การใช้ที่พบบ่อยที่สุดน่าจะเป็น นิพจน์สามส่วน รูปแบบ
(a < b) ? 5 : 2https://www.programiz.com/cpp-programming/ternary-operatorอย่างไรก็ดี ไม่ว่าจะตั้งสมมติฐานแบบไหนก็ไม่ได้สำคัญมากนัก คำหรือวลีมักมีได้หลายความหมาย และ
ternaryหมายถึงประกอบด้วยสามส่วน จึงเข้ากับที่นี่ด้วยternaryในที่นี้ไม่ได้หมายถึงอย่างนั้นใน C เครื่องหมาย
+รับอินพุตสองตัว จึงเป็น ตัวดำเนินการทวิภาค ส่วน?:รับอินพุตสามตัว จึงเป็นตัวดำเนินการสามส่วน เพียงแต่ใน C มันมีอยู่ตัวเดียว จึงมักถูกเรียกว่า “the ternary operator” เท่านั้น ไม่ได้มีอะไรพิเศษโดยเนื้อแท้vpternlogdimplements ตัวดำเนินการสามส่วนระดับบิตทุกแบบที่รับอินพุตสามตัวคิดว่ามันคล้ายกับฟังก์ชัน
BitBltของ Windows หรือเปล่า อย่างน้อยน่าจะมีมาตั้งแต่ Windows 3.1 แล้ว โดยพารามิเตอร์opใช้กำหนดว่าจะผสม source, destination, mask อย่างไรจำได้ว่ามีชื่อโค้ดอย่าง
BLACKNESSที่ทำให้เป็นสีดำไม่ว่าอินพุตจะเป็นอะไร และCOPYที่คัดลอก source ไปยัง destinationBLACKNESSกับWHITENESSฟังดูเป็นกวีนิด ๆ อย่างไรไม่รู้จากที่จำได้จาก Petzold แม้มันจะ implement ด้วยซอฟต์แวร์ แต่ตอนเรียกใช้ opcode จะถูกแปลงเป็น assembly เฉพาะภายในฟังก์ชัน จึงเหมือนจะเป็นกรณีที่พบได้ยากของ self-modifying code ในระบบปฏิบัติการ Windows
BitBltเดิมใช้ “operation codes” แบบ 16 บิตที่ซับซ้อน ซึ่งเก็บการดำเนินการไบนารีในรูป สัญกรณ์โปแลนด์ย้อนกลับต่อมามีการเพิ่ม “operation index” ที่เก็บข้อมูลเดียวกันใน 1 ไบต์เหมือน Amiga ซึ่งสั้นกว่าและสวยงามกว่า ตอนนี้ raster operation code แต่ละตัวเก็บทั้ง operation index และ operation code ทำให้ encoding ซ้ำซ้อนกัน https://devblogs.microsoft.com/oldnewthing/20180528-00/?p=98...
เหมือนกับวิธีที่ FPGA implement ฟังก์ชันตรรกะตามอำเภอใจด้วย lookup table
เพียงแต่โดยทั่วไปจะไม่ทำแบบนั้น ยกเว้นงานเฉพาะทางอย่าง FPGA หรือคำสั่งในบทความนี้ รีจิสเตอร์ความเร็วสูงและ static RAM บางครั้งก็สร้างด้วยตรรกะ แต่การสร้างโดยตรงด้วยทรานซิสเตอร์พบได้บ่อยกว่าการสร้างด้วยเกต
74181ก็ทำแบบนั้นเช่นกันไปที่ https://www.sandpile.org แล้วหา
VPTERNLOGในหน้า opcode 3 ไบต์ https://www.sandpile.org/x86/opc_3.htm จะเห็น AVX512BITALG2 ซึ่งเป็น variant แบบ byte/word masking ที่ดูเหมือน Intel เคยวางแผนไว้ในอดีตนอกจากนี้จาก operand
Ibยังลิงก์ไปยังหน้าตารางตรรกะสามส่วนที่รวมทั้ง 256 กรณีไว้ https://www.sandpile.org/x86/ternlog.htmที่เอกสารเลือกฟังก์ชัน
E2เป็นตัวอย่าง อาจเพราะมันเป็น mux ที่แทบจะพื้นฐานและเป็นมาตรฐานที่สุดในบรรดาฟังก์ชันบูลีนสามอินพุต: ถ้าBก็เป็นAไม่เช่นนั้นเป็นCนี่เป็นฟังก์ชันสากล จึงไม่จำเป็นต้องเป็นแฟน Amiga ถึงจะเลือกใช้ได้ แน่นอนว่าจริง ๆ แล้วเขาอาจเป็นแฟนก็ได้
อีกตัวอย่างหนึ่งของการใส่การดำเนินการระดับบิตไว้ในจำนวนเต็มคือ ROP code ของ GDI ใน Win32: https://learn.microsoft.com/en-us/windows/win32/gdi/ternary-...
ไม่มีคู่มือฮาร์ดแวร์ Amiga อย่างเป็นทางการ เลยอ่านหนังสือ “Mapping the Amiga” แทน ซึ่งอธิบายเนื้อหาเดียวกันแบบยืดยาวกว่าเล็กน้อย
จำไม่ได้แล้วว่าตอนนั้นใช้มินเทอมไหน แต่คิดว่าหลังอ่านหนังสือเล่มนี้ก็พอจะ implement สิ่งต่าง ๆ อย่าง shadebobs, bobs, การวาดเส้น 3D แบบ XOR ได้อย่างไรก็ไม่รู้
หน้าที่เกี่ยวข้องใน Mapping the Amiga: https://archive.org/details/1993-thomson-randy-rhett-anderso...