深度学习计算框架综述(十三)HVX 计算优化实践—HVX 编程基础知识

本节主要介绍HVX编程的基础知识以及示例代码,主要是五部分:

Registers: 介绍常用的寄存器

Program Flow: 介绍程序流中常用到的循环、跳转、调用等指令

Software Stack: 介绍软件栈的结构,以及SP、FP、LR等寄存器的正确使用方法

Scalar/Vector Instructions: 介绍常用的标量和向量指令

Sample Code: 结合前三部分的内容实现的示例代码

 

Registers

General registers

我们先看General register(下面我们统称为通用寄存器),Hexagon 处理器一共有32个32-bit 的通用寄存器,R0-R31,其中R29、R30、R31都有别名,分别为:Stack Pointer(SP)、Frame Pointer(FP)、Link Register(LR)。这三个寄存器和函数栈及函数调用有关,SP与FP分别指向函数栈的栈顶和栈底,LR则保存了函数调用的返回地址,在介绍 Software Stack 时我们会详细分析。

R0-R5用于接受函数传参,如果函数的传参超过6个,则超出的参数通过栈来传递。

在汇编函数中使用通用寄存器时,我们需要注意的一点是”保存现场“,”保存现场“指的是:在使用某些寄存器之前,需要将这些寄存器的内容保存起来,使用完后,需要恢复这些寄存器的内容,确保函数返回后这些寄存器的内容不会改变。需要在代码中手动保存的寄存器我们称之为”Callee saved register“,无需手动保存的我们称之为”Caller saved Register“,如下所示,R16-R27以及SP、FP、LR在修改前需要手动保存:

 

Control registers

Control register(下面我们统称为控制寄存器)有很多个,作用各不相同,比如控制程序计数、硬件循环,完整的介绍如下所示:

 

我们通常只会用到Loop RegistersModifier Registers以及Predicate Register,下面我们通过几个例子来介绍这三种控制寄存器的使用方法:

Loop Registers

Loop Registers的作用是控制硬件循环,一共有4个:LC0/LC1、SA0/SA1。LC用于控制循环次数,SA用于指定函数起始地址,示例代码如下:

loop0(start, R4)    // Modifies LC0 and SA0 (LC0=R4, SA0=&start)
LC1 = R22           // Set loop1 count
R9 = SA1            // Get loop1 start address

loop0(start, R4) 等价于 LC0=R4, SA0=&start

Modifier Registers

Modifier Registers的作用主要是提供了灵活的寻址方式

// Indirect auto-increment register addressing
M1 = R0                     // Set modifier register
R3 = memw(R2++M1)           // Load word
 
 
// Circular addressing
M0 = R7                     // Set modifier register
R0 = memb(R2++#4:circ(M0))  // Load from circ buffer pointed to by R2 with size/K vals in M0
R0 = memb(R7++I:circ(M1))   // Load from circ buffer pointed to by R7 with size/K/I vals in M1
 
 
// Bit-reversed addressing
M1 = R7                     // Set modifier register
R2 = memub(R0++M1:brev)     // The address is (R0.H | bitrev(R0.L)). The orginal R0 (not reversed) is added to M1 and written back to R0

Circular addressing 模式下,M0、M1的定义如下:

 

Predicate Registers

Predicate Registers的作用是存放标量的比较指令的结果:

P1 = cmp.eq(R2, R3)     // Scalar compare
if (P1) jump end        // Jump to address (conditional)
R8 = P1                 // Get compare status (P1 only)
P3:0 = R4               // Set compare status (P0-P3)

下面是 Predicate Registers 的定义:

 

Vector Data Registers

HVX 处理器一共有32个Vector Registers,V0-V31,从V62架构以后,Vecotr Register的大小默认为1024bit,其作用就是存储向量指令的操作数据:

V1 = vmem(R0)                   // load a vector of data from address R0
V4.w = vadd(V2.w, V3.w)         // add each word in V2 to corresponding word in V3
V5:4.w = vadd(V3:2.w, V1:0.w)   // add each word in V1:0 to corresponding word in V3:2

Vector predicate registers

Vector Predicate Registers Q0-Q3P0-P3的作用类似,只是前者是用来存放向量的比较指令的结果:

// In this case, each 32-bit field of V2 and V5 are compared and the corresponding 4-bit field is set
// in the corresponding predicate register Q3. For half-word operations, two bits are set per half-word.
// For byte operations, one bit is set per byte.
Q3 = vcmp.eq(V2.w, V5.w) 
 
 
// This takes each bit in the predicate register and selects the first or second byte in each source,
// and places it in the corresponding destination output field.
V4 = vmux(Q3, V5, V6)

 

Program Flow

Hexagon 处理器支持多种程序流,如下所示:

  • Conditional instructions
  • Hardware loops
  • Software branches
  • Pauses
  • Exceptions

我们重点介绍一下前三种。

Conditional Instructions

Conditional Instructions(条件指令) 在HVX编程时常常会用到,下面通过示例代码来说明:

if (P0) R0 = memw(R2)   // conditionally load word if P0
if (!P1) jump label     // conditionally jump if not P1

还有很多情况下可以使用条件指令:

  • Jumps and calls
  • Many load and store instructions
  • Logical instructions (including AND/OR/XOR)
  • Shift halfword
  • 32-bit add/subtract by register or short immediate
  • Sign and zero extend
  • 32-bit register transfer and 64-bit combine word
  • Register transfer immediate
  • Deallocate frame and return

Hardware Loops

Hardware Loops(硬件循环)有两组指令,loop0和loop1,下面展示了嵌套循环的用法:

// Sum the rows of a 100x200 matrix.
loop1(outer_start,#100)
outer_start:
R0 = #0
loop0(inner_start,#200)
inner_start:
R3 = memw(R1++#4)
{ R0 = add(R0,R3) }:endloop0
{ memw(R2++#4) = R0 }:endloop1

硬件循环的使用规则如下:

  • 如果是非嵌套循环,使用loop0
  • 如果是嵌套循环,内层使用loop0,外层使用loop1

Software Branches

Software Branches(软件分支)包含以下三种指令:

  • Jumps
  • Calls
  • Returns

常见的用法如下:

 

Software Stack

Stack Structure

我们先来看一下栈的结构,如下所示,栈是从高地址向低地址“生长”,SPFP分别指向函数栈的栈顶和栈底,需要注意的是,必须确保SP的地址是8 byte对齐。

 

Stack Frames

每一次函数的调用,都会在Call Stack(调用栈)上维护一个独立的Stack Frame(栈帧),每个独立的栈帧一般包括:

  • 函数的局部变量和参数
  • 函数的返回地址(LR)
  • 上一个栈帧的FP
  • 子函数的参数(传参超过6个,多出来的部分)

Stack Registers

下面是栈相关的寄存器:

 

Stack Instructions

和栈相关的指令主要有allocframe、deallocframe以及dealloc_return,其作用如下所示:

你可能感兴趣的:(深度学习计算框架综述,深度学习)