Приведение таблицы истинности к троичным логическим операциям, vpternlog

У меня много таблиц истинности многих переменных (7 или более), и я использую инструмент (например, logic friday 1) для упрощения логической формулы. Я мог бы сделать это вручную, но это слишком подвержены ошибкам. Затем эту формулу I переводят в встроенные компиляторы (например,_mm_xor_epi32), которая работает нормально.

вопрос: С vpternlog Я могу делать троичные логические операции. Но я не знаю метода для упрощения моих таблиц истинности до последовательностей vpternlog инструкции, которые (несколько) эффективны.

Я не спрашиваю, знает ли кто-нибудь инструмент, который упрощает произвольные троичные логические операции, хотя это было бы здорово, я ищу метод для таких упрощений.

Edit: я задал аналогичный вопрос на Электротехника.

2 ответов


за пределами просто оставляя его компилятору, или ручные волнистые предложения во 2-м разделе моего ответа, см. HJLebbink's self-answer с использованием инструментов логической оптимизации FPGA. (Этот ответ закончился щедростью, потому что он не смог привлечь такой ответ от кого-либо другого; это не действительно достойно щедрости. : / Я написал это до того, как была награда, но больше ничего полезного добавить не могу.)


ICC18 оптимизирует цепной _mm512_and/or/xor_epi32 встроенные функции в vpternlogd инструкции, но gcc / clang не делают.

На Godbolt для этого и более сложной функции с использованием некоторых входов несколько раз:

#include <immintrin.h>

__m512i logic(__m512i a, __m512i b, __m512i c,
               __m512i d, __m512i e, __m512i f, __m512i g) {
//     return _mm512_and_epi32(_mm512_and_epi32(a, b), c);
     return a & b & c & d & e & f;
}

gcc -O3 -march=skylake-avx512 сборка

logic:
    vpandq  zmm4, zmm4, zmm5
    vpandq  zmm3, zmm2, zmm3
    vpandq  zmm4, zmm4, zmm3
    vpandq  zmm0, zmm0, zmm1
    vpandq  zmm0, zmm4, zmm0
    ret

ICC18 -O3 -march=skylake-avx512

 logic:
    vpternlogd zmm2, zmm0, zmm1, 128                        #6.21
    vpternlogd zmm4, zmm2, zmm3, 128                        #6.29
    vpandd    zmm0, zmm4, zmm5                              #6.33
    ret                                                     #6.33

IDK насколько это хорошо при выборе оптимальных решений, когда каждая переменная используется более одного раза в разных подвыражениях.


чтобы увидеть, если он делает хорошую работу, вы должны сделать оптимизацию себе. вы хотите найти наборы из 3 переменных, которые могут быть объединены вместе в одно логическое значение без необходимости этих 3 переменных где-либо еще в выражении.

Я думаю, что это возможно для таблицы истинности с более чем 3 входы не упростите этот путь, к меньшей таблице истинности, где один из столбцов является результатом тройной комбинации 3 входов. например, я думаю, что не гарантируется, что это возможно упростите функцию ввода 4 для vpternlog + И, ИЛИ ИЛИ ИЛИ XOR.

Я бы определенно беспокоился, что компиляторы могут выбрать 3 входа для объединения, что не привело к такому упрощению, как другой выбор 3.

это может быть даже оптимальным для компилятора, чтобы начать с двоичной операции или двух на пару пар, чтобы настроить для троичной операции, особенно если это позволяет лучше ILP.

вы, вероятно, могли бы написать оптимизатор таблицы правды грубой силы, который выглядел для триплетов переменных, которые могут быть объединены, чтобы сделать меньшую таблицу только для троичного результата и остальной части таблицы. Но я не уверен, что жадный подход гарантированно даст лучшие результаты. Если существует несколько способов объединения с одним и тем же общим количеством команд, они, вероятно, не все эквивалентны для ILP (параллелизм уровня инструкции).


как перевести таблицу истинности в последовательности vpternlog инструкция.

  1. переведите таблицу истинности в логическую формулу; используйте, например, Logic Friday.
  2. сохраните логическую формулу в формате уравнения Synopsys (.eqn по). Например, Я использовал сеть с 6 входными узлами A-F, двумя выходными узлами F0 и F1 и несколько сложной (не unate) булевой функцией.

содержание BF_Q6.eqn по:

INORDER = A B C D E F; 
OUTORDER = F0 F1;
F0 = (!A*!B*!C*!D*!E*F) + (!A*!B*!C*!D*E*!F) + (!A*!B*!C*D*!E*!F) + (!A*!B*C*!D*!E*!F) + (!A*B*!C*!D*!E*!F) + (A*!B*!C*!D*!E*!F);
F1 = (!A*!B*!C*!D*E) + (!A*!B*!C*D*!E) + (!A*!B*C*!D*!E) + (!A*B*!C*!D*!E) + (A*!B*!C*!D*!E);
  1. использовать " ABC: Система последовательного синтеза и верификации" из Исследовательского центра верификации и синтеза в Беркли. Я использовал версию windows. Получить ABC здесь.

в ABC я бегу:

abc 01> read_eqn BF_Q6.eqn
abc 02> choice; if -K 3; ps
abc 03> lutpack -N 3 -S 3; ps
abc 04> show
abc 05> write_bench BF_Q6.bench

вам понадобится choice; if -K 3; ps несколько раз, чтобы получить лучшие результаты.

в результате BF_Q6.стенд содержит 3-Луц для FPGA:

INPUT(A)
INPUT(B)
INPUT(C)
INPUT(D)
INPUT(E)
INPUT(F)
OUTPUT(F0)
OUTPUT(F1)
n11         = LUT 0x01 ( B, C, D )
n12         = LUT 0x1 ( A, E )
n14         = LUT 0x9 ( A, E )
n16         = LUT 0xe9 ( B, C, D )
n18         = LUT 0x2 ( n11, n14 )
F1          = LUT 0xae ( n18, n12, n16 )
n21         = LUT 0xd9 ( F, n11, n14 )
n22         = LUT 0xd9 ( F, n12, n16 )
F0          = LUT 0x95 ( F, n21, n22 )

4. Это можно перевести на C++ механически:

__m512i not(__m512i a) { return _mm512_xor_si512(a, _mm512_set1_epi32(-1)); }
__m512i binary(__m512i a, __m512i b, int i) {
    switch (i)
    {
        case 0: return _mm512_setzero_si512();
        case 1: return not(_mm512_or_si512(a, b));
        case 2: return _mm512_andnot_si512(a, b);
        case 3: return not(a);
        case 4: return _mm512_andnot_si512(b, a);
        case 5: return not(b);
        case 6: return _mm512_xor_si512(a, b);
        case 7: return not(_mm512_and_si512(a, b));
        case 8: return _mm512_and_si512(a, b);
        case 9: return not(_mm512_xor_si512(a, b));
        case 10: return b;
        case 11: return _mm512_or_si512(not(a), b);
        case 12: return a;
        case 13: return mm512_or_si512(a, not(b)); 
        case 14: return _mm512_or_si512(a, b);
        case 15: return _mm512_set1_epi32(-1);
        default: return _mm512_setzero_si512();
    }
}
void BF_Q6(const __m512i A, const __m512i B, const __m512i C, const __m512i D, const __m512i E, const __m512i F, __m512i& F0, __m512i& F1) {
    const auto n11 = _mm512_ternarylogic_epi64(B, C, D, 0x01);
    const auto n12 = binary(A, E, 0x1);
    const auto n14 = binary(A, E, 0x9);
    const auto n16 = _mm512_ternarylogic_epi64(B, C, D, 0xe9);
    const auto n18 = binary(n11, n14, 0x2);
    F1 = _mm512_ternarylogic_epi64(n18, n12, n16, 0xae);
    const auto n21 = _mm512_ternarylogic_epi64(F, n11, n14, 0xd9);
    const auto n22 = _mm512_ternarylogic_epi64(F, n12, n16, 0xd9);
    F0 = _mm512_ternarylogic_epi64(F, n21, n22, 0x95);
}

в остается вопрос, Является ли полученный код C++ является оптимальным. Я не думаю, что этот метод (часто) дает наименьшие сети 3-Лутов, просто потому, что эта проблема NP-жесткая. Кроме того, невозможно сообщить ABC о параллелизме инструкций, и невозможно определить приоритетность порядка переменных таким образом, чтобы переменные, которые будут использоваться позже, не находились в первой позиции LUT (так как первый исходный операнд перезаписывается с результатом). Но компилятор может быть умным достаточно делать такие оптимизации.

ICC18 дает следующую сборку:

00007FF75DCE1340  sub         rsp,78h
00007FF75DCE1344  vmovups     xmmword ptr [rsp+40h],xmm15
00007FF75DCE134A  vmovups     zmm2,zmmword ptr [r9]
00007FF75DCE1350  vmovups     zmm1,zmmword ptr [r8]
00007FF75DCE1356  vmovups     zmm5,zmmword ptr [rdx]
00007FF75DCE135C  vmovups     zmm4,zmmword ptr [rcx]

00007FF75DCE1362  vpternlogd  zmm15, zmm15, zmm15, 0FFh
00007FF75DCE1369  vpternlogq  zmm5, zmm1, zmm2, 0E9h
00007FF75DCE1370  vmovaps     zmm3, zmm2
00007FF75DCE1376  mov         rax, qword ptr[&E]
00007FF75DCE137E  vpternlogq  zmm3, zmm1, zmmword ptr[rdx], 1
00007FF75DCE1385  mov         r11, qword ptr[&F]
00007FF75DCE138D  mov         rcx, qword ptr[F0]
00007FF75DCE1395  mov         r10, qword ptr[F1]
00007FF75DCE139D  vpord       zmm0, zmm4, zmmword ptr[rax]
00007FF75DCE13A3  vpxord      zmm4, zmm4, zmmword ptr[rax]
00007FF75DCE13A9  vpxord      zmm0, zmm0, zmm15
00007FF75DCE13AF  vpxord      zmm15, zmm4, zmm15
00007FF75DCE13B5  vpandnd     zmm1, zmm3, zmm15
00007FF75DCE13BB  vpternlogq  zmm1, zmm0, zmm5, 0AEh
00007FF75DCE13C2  vpternlogq  zmm15, zmm3, zmmword ptr[r11], 0CBh
                              ^^^^^        ^^^^^^^^^^^^^^^^  
00007FF75DCE13C9  vpternlogq  zmm5, zmm0, zmmword ptr[r11], 0CBh
00007FF75DCE13D0  vmovups     zmmword ptr[r10], zmm1
00007FF75DCE13D6  vpternlogq  zmm5, zmm15, zmmword ptr[r11], 87h                                  
00007FF75DCE13DD  vmovups     zmmword ptr [rcx],zmm5

00007FF75DCE13E3  vzeroupper
00007FF75DCE13E6  vmovups     xmm15,xmmword ptr [rsp+40h]
00007FF75DCE13EC  add         rsp,78h
00007FF75DCE13F0  ret

ICC18 может изменить порядок переменных в const auto n22 = _mm512_ternarylogic_epi64(F, n12, n16, 0xd9); to vpternlogq zmm15, zmm3, zmmword ptr[r11], 0CBh такое, что переменная F не перезаписывается. (Но, как ни странно, извлекается из памяти 3 раза...)