如何在标准C / C ++代码中包含和转换自定义指令/扩展,从而保持高性能

时间:2012-01-13 13:29:45

标签: c++ c compilation machine-code microblaze

我正在为FPGA和ASIC开发通用图像处理核心。我们的想法是将标准处理器与它连接起来。我遇到的一个问题是如何“编程”它。让我解释一下:核心有一个指令解码器,用于我的“自定义”扩展。例如:

vector_addition $vector[0], $vector[1], $vector[2]    // (i.e. v2 = v0+v1) 

还有更多这样的人。该操作由处理器通过总线发送到核心,使用处理器进行循环,非向量操作等,如下所示:

for (i=0; i<15;i++)           // to be executed in the processor
     vector_add(v0, v1, v2)   // to be executed in my custom core

程序是用C / C ++编写的。核心只需要指令本身,在机器代码中

  1. opcode = vector_add = 0x12h
  2. register_src_1 = v0 = 0x00h
  3. register_src_2 = v1 = 0x01h
  4. register_dst = v2 = 0x02h

    机器代码= opcore | v0 | v1 | v2 = 0x7606E600h

  5. (或者其他,只是用二进制构建指令的不同字段的连接)

    通过总线将其发送到核心后,核心能够通过专用总线从内存中请求所有数据,并在不使用处理器的情况下处理所有内容。最大的问题是:如何将前一条指令转换为十六进制表示?(通过总线发送它不是问题)。想到的一些选项是

    • 运行解释代码(在处理器的运行时转换为机器代码) - &gt; 非常慢,甚至使用某种内联宏
    • 使用外部自定义编译器编译自定义部分,从外部存储器加载二进制文件并使用一些独特的指令将其移动到核心 - &gt;难以阅读/理解源代码,糟糕的SDK集成,如果代码非常细分,则会有太多部分
    • JIT编译 - &gt;为此复杂化?
    • 扩展编译器 - &gt;噩梦!
    • 连接到自定义核心以处理所有内容的自定义处理器:循环,指针,内存分配,变量... - &gt;太多的工作

    问题在于软件/编译器,但对于那些对本主题有深入了解的人来说,这是一个FPGA中的SoC,主处理器是MicroBlaze,而IP Core则使用AXI4总线。

    我希望我能正确解释...提前致谢!

3 个答案:

答案 0 :(得分:1)

我不确定我完全理解,但我认为我之前遇到过类似的事情。根据对rodrigo的回应的评论,听起来你的代码中散布着一些小的指令。你也提到外部编译器是可能的,只是一个痛苦。如果将外部编译器与C宏结合使用,可以获得一些体面的东西。

考虑以下代码:

for (i=0; i<15;i++)
     CORE_EXEC(vector_add(v0, v1, v2), ref1)

CORE_EXEC宏有两个目的:

  1. 您可以使用外部工具扫描源文件以查找这些条目并编译核心代码。使用“ref1”名称作为变量,此代码将链接到C(只生成带有二进制位的C文件)。
  2. 在C中,您将定义CORE_EXEC宏以将“ref1”字符串传递给核心进行处理。
  3. 因此第1阶段将生成一个已编译的二进制核心指令文件,例如上面可能有这样的字符串:

    const char * const cx_ref1[] = { 0x12, 0x00, 0x01, 0x02 };
    

    您可以像这样定义CORE_EXEC:

    #define CORE_EXEC( code, name ) send_core_exec( cx_##name )
    

    显然你可以选择你想要的前缀,不过在C ++中你可能希望使用命名空间。

    就工具链而言,您可以为所有位生成一个文件,或者为每个C ++文件生成一个文件 - 这可能更容易进行脏检测。然后,您只需在源代码中包含生成的文件即可。

答案 1 :(得分:0)

你无法在程序开始时将所有代码段翻译成机器代码(只需一次),将它们以二进制格式保存在内存块中,然后在需要时使用这些二进制文件。

这基本上是OpenGL着色器的工作原理,我觉得很容易管理。

主要缺点是内存消耗,因为在内存中有相同脚本的文本和二进制表示。我不知道这对你来说是否有问题。如果是,则有部分解决方案,因为在编译源文本后将其卸载。

答案 2 :(得分:0)

让我们说我要修改一个arm核心来添加一些自定义指令,我想要运行的操作在编译时就已知(将在一秒内到达运行时)。

我会使用汇编,例如:

.globl vecabc
vecabc:
   .word 0x7606E600 ;@ special instruction
   bx lr

或者内联它与编译器的内联语法无关,如果您需要使用处理器寄存器,例如c编译器以内联汇编语言填充寄存器,那么汇编程序就会组装这些指令。我发现编写实际的asm并且只是像上面那样在指令流中注入单词,只有编译器将一些字节作为数据并且将一些字节作为指令进行删除,核心将按照所写的顺序看到它们。

如果你需要实时做事,你可以使用自我修改代码,我再次喜欢使用asm到蹦床。构建你想要在ram中运行的指令,比如地址0x20000000,然后让蹦床调用它:

.globl tramp
tramp:
    bx r0 ;@ assuming you encoded a return in your instructions

调用它
tramp(0x20000000);

与上述相关的其他路径是修改汇编程序以添加新指令,为这些指令创建语法。然后你可以随意使用直接汇编语言或内联汇编语言,你不会让编译器在不修改编译器的情况下使用它们,这是修改汇编程序后的另一条路径。