将真值表减少到三元逻辑运算,vpternlog

HJL*_*ink 9 boolean-logic intrinsics truthtable avx512

我有许多变量(7或更多)的真值表,我使用工具(例如逻辑星期五1)来简化逻辑公式.我可以手工完成,但这太容易出错了.这些公式然后转换为编译器内在函数(例如_mm_xor_epi32),它工作正常.

问题:使用vpternlog我可以进行三元逻辑运算.但我不知道一种方法来简化我的真值表到vpternlog指令序列(有些)有效.

我不是在问是否有人知道一个简化为任意三元逻辑运算的工具,虽然这很好,但我正在寻找一种方法来进行这种简化.

编辑:我问了一个关于电气工程的类似问题.

Pet*_*des 7

除了将它留给编译器,或者我的答案的第二部分中的手工波形建议之外,请参阅HJLebbink使用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;
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

IDK在每个变量在不同的子表达式中多次使用时,选择最佳解决方案有多好.


要查看它是否做得好,您必须自己进行优化. 您希望找到3个变量的集合,这些变量可以组合成一个布尔值,而不需要在表达式中的任何其他位置使用这3个变量.

我认为有一个超过3个输入的真值表可能不会以这种方式简化到一个较小的真值表,其中一列是三个输入的三元组合的结果.例如,我认为不能保证将4输入函数简化为vpternlog + AND,OR或XOR.

我肯定担心编译器可能会选择3个输入来组合,这不会导致3个不同选择的简化.

对于编译器来说,最好是以二进制操作开始,或者在两对上开始设置三元运算,尤其是如果能够实现更好的ILP.

您可能可以编写一个强力真值表优化器,它可以查找变量的三元组,这些变量可以组合起来,只为三元结果和表格的其余部分创建一个较小的表.但我不确定贪婪的方法能保证给出最好的结果.如果有多种方法可以将相同的总指令数组合在一起,那么它们可能并不完全等同于ILP(指令级并行).

  • 难道你不经常建造一张卡诺图,然后手工减少以完成真值表吗?或者自从我在大学时就改变了? (2认同)
  • @PeterCordes我正在研究一个真正的答案:归结为告诉MVSIS(https://embedded.eecs.berkeley.edu/mvsis/)等工具合成3-lut FPGA,将结果转换为vpternlog并希望它很有效率. (2认同)

HJL*_*ink 5

如何将真值表转换为vpternlog指令序列。

  1. 将真值表翻译成逻辑公式;例如,使用“逻辑星期五”。
  2. 以 Synopsys 方程格式 (.eqn) 存储逻辑公式。例如,我使用了一个具有 6 个输入节点 A 到 F、两个输出节点 F0 和 F1 以及一个有点复杂(非统一)布尔函数的网络。

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);
Run Code Online (Sandbox Code Playgroud)
  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
Run Code Online (Sandbox Code Playgroud)

您可能需要运行choice; if -K 3; ps多次才能获得更好的结果。

生成的 BF_Q6.bench 包含 FPGA 的 3-LUT:

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 )
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

问题仍然是生成的 C++ 代码是否最优。我不认为这种方法(通常)会产生最小的 3-LUT 网络,仅仅是因为这个问题是 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
Run Code Online (Sandbox Code Playgroud)

ICC18 能够更改变量顺序,const auto n22 = _mm512_ternarylogic_epi64(F, n12, n16, 0xd9);以便vpternlogq zmm15, zmm3, zmmword ptr[r11], 0CBh变量 F 不会被覆盖。(但奇怪的是,从内存中提取了 3 次......)