【问题标题】:Truth-table reduction to ternary logic operations, vpternlog真值表简化为三元逻辑运算,vpternlog
【发布时间】:2018-05-12 06:21:06
【问题描述】:

我有很多变量的真值表(7 个或更多),我使用一个工具(例如逻辑星期五 1)来简化逻辑公式。我可以手动完成,但这太容易出错了。然后我将这些公式转换为运行良好的编译器内在函数(例如_mm_xor_epi32)。

问题:用vpternlog我可以进行三元逻辑运算。但是我不知道有一种方法可以将我的真值表简化为(有点)有效的 vpternlog 指令序列。

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

编辑:我在 Electrical Engineering 上问过类似的问题。

【问题讨论】:

  • 您是否检查过编译器是否会为您优化_mm_xor / _mm_and / 等为vpternlogd 指令?
  • @PeterCordes 英特尔编译器会将布尔逻辑合并到 ternlog 中。但我从未测试过简化更大的序列有多聪明。
  • @PeterCordes 我不认为任何编译器都会努力尝试以最佳方式解决更大的变量#。因为这本质上是一个更难的 k-map 缩减版本,它是 NP 完全的。
  • 另外,你不能直接在逻辑星期五或类似的包中这样做吗?其中很多用于针对“普通门”(即二进制布尔运算符)以外的其他输出 - 例如,4 位 LUT 之类的东西在 FPGA 中很常见,所以我希望它们有更灵活的“目标”选项,虽然我只承认我前段时间粗略地扫描了这个区域。
  • @BeeOnRope 我只检查了几个更流行的工具,例如几年前来自 Xilinx 的那些。但我找不到这个特定的功能,可能是因为只有少数人需要它。我正在考虑实现 Expresso 算法,但如果有另一种更简单的方法,我不想浪费我的时间(调试)。

标签: boolean-logic intrinsics truthtable avx512


【解决方案1】:

除了将其留给编译器或我的答案第二部分中的手动建议之外,请参阅HJLebbink 使用 FPGA 逻辑优化工具的自我回答。 (这个答案最终得到了赏金,因为它未能吸引其他任何人的这样的答案;它并不是真正值得赏金的。:/我在有赏金之前写了它,但没有其他有用的东西可以添加。)


ICC18 将链式 _mm512_and/or/xor_epi32 内部函数优化为 vpternlogd 指令,但 gcc/clang 不会。

On 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 个输入的真值表,not 可以通过这种方式简化为一个较小的真值表,其中一列是 3 个的三元组合的结果输入。例如我认为不能保证可以将 4 输入函数简化为 vpternlog + AND、OR 或 XOR。

我肯定会担心编译器可能会选择 3 个输入进行组合,这并没有像选择 3 个不同那样简化。

对于编译器来说,从一对或两个二元运算开始以设置三元运算甚至可能是最佳选择,特别是如果这样可以实现更好的 ILP。

您可能会编写一个蛮力真值表优化器,它会寻找三元组变量,这些变量可以组合成一个较小的表,仅用于三元结果和表的其余部分。但我不确定贪婪的方法是否能保证给出最好的结果。如果有多种方法可以组合相同的总指令数,那么它们可能并不完全等同于 ILP (Instruction Level Parallelism)

【讨论】:

  • 您通常不构建卡诺图,然后手动进行归约以完成真值表吗?或者自从我上大学后这种情况发生了变化?
  • @jww 卡诺图用于简化布尔表达式。简化的表达式仅包含逻辑函数“and”、“or”和“neg”,而不包含我希望使用的任何三元函数。但我不确定你是否可以修改这个方法来做到这一点。
  • @HJLebbink:太糟糕了,你的赏金没有吸引任何其他有趣的答案。我希望有比我刚刚编造的更具体的东西:/
  • @PeterCordes 我正在研究一个真正的答案:归结为告诉诸如 MVSIS (embedded.eecs.berkeley.edu/mvsis) 之类的工具来合成 3-lut FPGA,将结果转换为 vpternlog 并希望它是有效的.
  • @HJLebbink:很有趣。我想知道你是否可以让它支持高 ILP,以及不需要额外的 vmovdqa32vpternlogd 破坏它之前复制寄存器的序列。 (对于 512b 向量的 SKX,前端是 4 宽,但后端只有 2 个向量 ALU,所以只要在寄存器重命名阶段消除它,寄存器复制就有一些空间。但事情要紧得多当 port1 没有关闭时(256b 或更小的向量)。我想知道是否有很多情况下以非破坏性 VPOR 或其他东西开头是一个优势......
【解决方案2】:

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

  1. 将真值表翻译成逻辑公式;使用例如 Logic Friday。
  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);
  1. 使用伯克利验证与合成研究中心的“ABC:顺序合成与验证系统”。我用的是windows版本。获取 ABC here

我在 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.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 )

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-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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-06
    • 1970-01-01
    • 1970-01-01
    • 2014-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    相关资源
    最近更新 更多