PCode Compiler

From SEGGER Wiki
Jump to: navigation, search

WIP

This wiki article contain any information useful when writing code for the PCode Compiler. Feel free to correct or add missing information.

Used PCode version:

SEGGER J-Link PCode Compiler/Assembler/Linker (U-Flash Edition)
Compiled Apr  5 2023 14:51:12

Flasher PCode script file language

The syntax of the Flasher PCode script file language follows the conventions of the C-language, but it does not support all expressions and operators which are supported by the C-language. In the following, the supported operators and expressions are listed.

Supported Operators

The following operators are supported:

  • Multiplicative operators: *
  • Additive operators: +, -
  • Bitwise shift operators: <<, >>
  • Relational operators: <, >, <=, >=
  • Equality operators: ==, !=
  • Bitwise operators: &, |, ^, ~
  • Logical operators: &&, ||
  • Assignment operators: =, *=, +=, -=, <<=, >>=, &=, ^=, |=

Supported basic type specifiers

The following basic type specifiers are supported:

Name Size (Bit) Signed
void N/A N/A
char 8 signed
unsigned char 8 unsigned
short 16 signed
unsigned short 16 unsigned
int 32 signed
unsigned int 32 unsigned
long 32 signed
unsigned long 32 unsigned
U8 8 unsigned
U16 16 unsigned
U32 32 unsigned
I8 8 signed
I16 16 signed
I32 32 signed

Supported type qualifiers

The following type qualifiers are supported:

  • const

Supported declarators

The following declarators are supported:

  • Array declarators

Supported selection statements

The following selection statements are supported:

  • if-statements
  • if-else-statements

Supported iteration statements

The following iteration statements are supported:

  • while
  • do-while

Jump statements

The following jump statements are supported:

  • return

Pseudo variables

Pseudo variables are variables that can be read and written as usual to facilitate their access although they can only be accessed via JLINK_SYS_SetVar() and JLINK_SYS_GetVar(). Internally, the access to the pseudo variable is converted into the according function call which takes care of reading and writing the variable.

Index Declaration Read/write Description
1 U32 JLINK_JTAG_IRPre rw Sets or returns the sum of JTAG IRLen of all TAPs preceding the device.
2 U32 JLINK_JTAG_DRPre rw Sets or returns the number of JTAG TAPs preceding the device.
3 U32 JLINK_JTAG_IRPost rw Sets or returns the sum of the JTAG IRLen of all TAPs following the device.
4 U32 JLINK_JTAG_DRPost rw Sets or returns the number of JTAG TAPs following the device.
5 U32 JLINK_JTAG_IRLen rw Sets or returns the JTAG IRLen of the device.
6 U32 JLINK_JTAG_TotalIRLen r Returns the currently set total IR length of the JTAG chain.
8 U32 JLINK_JTAG_Speed rw Sets or returns the target interface speed in kHz.
9 U32 JLINK_JTAG_ResetPin rw Sets or clears pin 15.
10 U32 JLINK_JTAG_TRSTPin rw Sets or clears pin 3.
11 U32 JLINK_JTAG_TCKPin rw Sets or clears pin 9.
12 U32 JLINK_JTAG_TMSPin rw Sets or clears pin 7.
13 U32 JLINK_JTAG_TDIPin rw Sets or clears pin 5.
22 U32 JLINK_ActiveTIF r Returns the currently active target interface.
33 U32 JLINK_TargetVoltage r Returns the voltage on the line connected to VTREF.
34 U8* JLINK_pDataArea r Returns the address of the buffer used for transferring data between the Flasher and the executing PCode.

Predefined constants

Constant name Value
JLINK_SPI_FLAG_CS_START_STATE_U 0x00000000
JLINK_SPI_FLAG_CS_START_STATE_0 0x00000002
JLINK_SPI_FLAG_CS_START_STATE_1 0x00000003
JLINK_SPI_FLAG_CS_END_STATE_U 0x00000000
JLINK_SPI_FLAG_CS_END_STATE_0 0x00000004
JLINK_SPI_FLAG_CS_END_STATE_1 0x0000000C
JLINK_PIN_MAX_NUM_PINS 0x00000008
JLINK_PIN_OVERRIDE_MODE_RELEASE 0x00000000
JLINK_PIN_OVERRIDE_MODE_PIO_IN 0x00000001
JLINK_PIN_OVERRIDE_MODE_PIO_OUT_LOW 0x00000002
JLINK_PIN_OVERRIDE_MODE_PIO_OUT_HIGH 0x00000003
JLINK_PIN_OVERRIDE_MODE_UART_TX 0x00000004
JLINK_PIN_OVERRIDE_MODE_UART_RX 0x00000005
JLINK_PIN_OVERRIDE_MODE_UART_RXTX 0x00000006
JLINK_TIF_JTAG 0x00000000
JLINK_TIF_SWD 0x00000001
JLINK_TIF_FINE 0x00000003
JLINK_TIF_ICSP 0x00000004
JLINK_TIF_SPI 0x00000005
JLINK_TIF_C2 0x00000006
JLINK_TIF_CJTAG 0x00000007
JLINK_TIF_SWIM 0x00000008
JLINK_TIF_PDI 0x00000009
JLINK_TIF_SPI_IDLE_CLOCK_LOW 0x0000000B
JLINK_TIF_SPI2FE 0x0000000D
JLINK_TIF_QSPI 0x0000000E
JLINK_TIF_DAP 0x00000010

PCode Quirks

This section describes the differences between the Flasher PCode script language for the Flasher PCode compiler and the C language.

Global and Static Variables with File Scope

Global and static variables with file scope are handled the same. The size allocated for global and static variables is, in contrast to the size of variables with function scope, the size returned by sizeof() for this data type.

Local Variables with Function Scope

Local variables with function scope are using 4 bytes on the stack no matter which data type is used. A U8 on the stack is padded with 3 bytes. However, sizeof(MyVariable) will still return the actual size of the data type without padding bytes.

Unused local variables with function scope are not eliminated. Variables are allocated on the stack in the order they were defined with the first variable having the highest address and the last the lowest address. Initialization does not work and assignment has to be used instead.

PCode:

void LocalsOnStack(void) {
  U32  DataU32;
  U16  DataU16;
  U8   DataU8;
  U32* pDataU32;
  U16* pDataU16;
  U8*  pDataU8;
}

Emitted pasm:

// void LocalsOnStack(void) {
  Align   1                                  // Ensure code is 16-bit aligned
LocalsOnStack:                               // void  LocalsOnStack(void);
  // 6 Local(s), using 24 bytes
  //  @ SP +  20: U32 DataU32;
  //  @ SP +  16: U16 DataU16;
  //  @ SP +  12: U8 DataU8;
  //  @ SP +   8: U32 * pDataU32;
  //  @ SP +   4: U16 * pDataU16;
  //  @ SP +   0: U8 * pDataU8;
// }
  add     SP, -24
  mov     R0, 0
  add     SP, 24
  ret                                        // Emulate return 0 for old scripts in which publics were void

Arrays

Arrays defined on the stack don't behave as expected and thus should not be used. They should be defined as global arrays instead and when passing the address of an array as a buffer to a function, an ampersand has to be used to retrieve its address.

char aArray[32];
void Foo(void) {
  char  aLocalArray[32];
  char* ArrayPointer;

  // Stores [SP+n] (with n being the offset of the array on the stack) in ArrayPointer
  // => LocalArray[0]
  // => the first element instead of the array address is stored in ArrayPointer
  ArrayPointer = aLocalArray;

  // Stores SP in ArrayPointer
  // => &LocalArray[-n] with n being the offset of the array on the stack
  // => the current SP is stored in ArrayPointer which points outside the array if a variable is defined after the array
  // Here, n is 4, because ArrayPointer is defined after the array.
  ArrayPointer = &aLocalArray;

  // Error: Object type not yet supported
  //ArrayPointer = aArray;     
  
  // Finally stores the array's address
  ArrayPointer = &aArray;       
}

Reads/Writes

When reading or writing variables with function scope always 4 bytes are loaded from and stored on the stack, no matter which size the data type has. If the result of an expression, which is to be assigned to a variable, is not masked with the size of the variable written, the variable's padding bytes might be written with values other than zero, resulting in a value bigger than the variable should be able to hold when it is read next time.

The following code shows that when the data from DataU8 is shifted left by 4 and stored back in DataU8 without masking, the padding bytes of DataU8 are written. When DataU8 is read again to be shifted right by 4, the data from the padding bytes is shifted back into the actual U8 variable.

PCode:

void ReadWrite(void) {
  U8 DataU8;

  //
  // Using U8 without masking
  //
  DataU8 = 0xFF;                          // Set LSByte to 0xFF and padding bytes to 0x00
  DataU8 = DataU8 << 4;                   // Shift left by 4 resulting in 0x00000FF0
  DataU8 = DataU8 >> 4;                   // Shift right by 4, shifting the 0xF(F0) back into the LSByte
  JLINK_SYS_Report1(" DataU8:", DataU8);  // Prints "DataU8: 000000FF"
  //
  // Using U8 with masking
  //
  DataU8 = 0xFF;
  DataU8 = (DataU8 << 4) & 0xFF;
  DataU8 = (DataU8 >> 4) & 0xFF;
  JLINK_SYS_Report1(" DataU8:", DataU8);  // Prints "DataU8: 0000000F"
}

API Calls

When API functions like JLINK_SYS_Report1() are called, arguments are passed to the API function as 32-bit values on the stack. When passing local variables with function scope as an argument to the API function, the variable will be loaded despite its data type as a 32-bit value from its location (if not already loaded) and pushed as a 32-bit value on the stack again. This might lead to confusing behavior when an U8 is passed as an argument, but the called function receives a value greater than 255, because the U8 wasn't properly masked when written.

Static Variables with Function Scope

Static variables with function scope behave the same as non-static variables with function scope. That means that memory for the static variables is allocated on the stack on function entry and freed again on function exit. Their content is not retained and is undefined on the next function call.

PCode:

void StaticVariablesWithFunctionScope(void) {
  static int StaticDataInt;
  static U32 StaticData32;
  static U16 StaticData16;
  static U8  StaticData8;
}

Emitted pasm:

// void StaticVariablesWithFunctionScope(void) {
  Align   1                                  // Ensure code is 16-bit aligned
StaticVariablesWithFunctionScope:            // void  StaticVariablesWithFunctionScope(void);
  // 4 Local(s), using 16 bytes
  //  @ SP +  12: static int StaticDataInt;
  //  @ SP +   8: static U32 StaticData32;
  //  @ SP +   4: static U16 StaticData16;
  //  @ SP +   0: static U8 StaticData8;
// }
  add     SP, -16
  mov     R0, 0
  add     SP, 16
  ret                                        // Emulate return 0 for old scripts in which publics were void

Negative Values

Negative Values should be used with caution, because they can cause undesired behavior if not handled properly.

Let's look at following example:

I16 GlobalI16;
void NegativeValuesAndVariables(void) {
  I16 LocalI16;

  GlobalI16 = -16;
  LocalI16  = -16;
  JLINK_SYS_Report1(" GlobalI16:", GlobalI16);  // Prints " GlobalI16: 0000FFF0"
  JLINK_SYS_Report1(" LocalI16 :", GlobalI16);  // Prints " LocalI16 : FFFFFFF0"
}

Here we have a global I16 variable called GlobalI16, a local I16 variable called LocalI16 and an integer literal -16. For both assignments the integer literal is loaded into the register where it has the value 0xFFFFFFF0. Since the code is not optimized, the register content is stored into the memory and later loaded again before it is used. GlobalI16 is stored as a 16-bit value by using strh which truncates the upper 16-bit. When GlobalI16 is loaded again, the instruction ldrh is used, which loads 0x0000FFF0 from memory into the register, but doesn't sign-extend it leaving it as 0x0000FFF0 in the register. When -16 is stored in LocalI16 the instruction str is used instead of strh, storing the full 32-bit on the stack. When it is loaded again by using ldr, the value 0xFFFFFFF0 is loaded into the register.

This example shows that it makes a difference if a variable is global or local and whether integer literals are used. To prevent undesired behavior, for instance when comparing values, both sides of the comparison should be masked to the size of the smallest data type. Alternatively, 32-bit values can be used for all variables. Just make sure that the sign extension bits have the same length for all negative values.

Pointers

Pointers are always loading (without sign extension) and storing the size of the data type they are pointing to, no matter if the variable it is pointing to is local with function scope, or if it is global.

I16 GlobalI16;
void Pointers(void) {
  I16  LocalI16;
  I16* pData;

  GlobalI16 = 0x0000FFFF << 8;
  LocalI16  = 0x0000FFFF << 8;

  pData = &GlobalI16;
  JLINK_SYS_Report1(" GlobalI16:", GlobalI16);  // Prints " GlobalI16: 0000FF00"
  JLINK_SYS_Report1(" GlobalI16:", *pData);     // Prints " GlobalI16: 0000FF00"
  pData = &LocalI16;
  JLINK_SYS_Report1(" LocalI16 :", LocalI16);   // Prints " LocalI16 : 00FFFF00"
  JLINK_SYS_Report1(" LocalI16 :", *pData);     // Prints " LocalI16 : 0000FF00"
}


Functions

Functions support max. 7 function parameters.

Operations and Length of Operands

Operations like addition, subtraction, shifts and comparisons (which are subtractions with an optional shift) are all performed with 32-bit operands and also result in a 32-bit value. The sign extension bits of negative values do not need to go up to the 31th bit, but should go up to the MSB of the smallest data type used. Furthermore, the result should be masked to the size of the smallest data type used.

I16 GlobalI16;
I8  GlobalI8;
void Operations(void) {
  I16 LocalI16;

  GlobalI16 = -16;
  GlobalI8  = -16;
  LocalI16  = 116;
  JLINK_SYS_Report1(" LocalI16 + GlobalI16:", LocalI16 + GlobalI16);           // Prints " LocalI16 + GlobalI16: 00010064"
  JLINK_SYS_Report1(" LocalI16 + GlobalI8 :", LocalI16 + GlobalI8);            // Prints " LocalI16 + GlobalI8 : 00000164"
  JLINK_SYS_Report1(" LocalI16 + GlobalI16:", (LocalI16 + GlobalI16) & 0xFF);  // Prints " LocalI16 + GlobalI16: 00000064"
  JLINK_SYS_Report1(" LocalI16 + GlobalI8 :", (LocalI16 + GlobalI8 ) & 0xFF);  // Prints " LocalI16 + GlobalI8 : 00000064"
  GlobalI16 = -16;
  GlobalI8  = -16;
  LocalI16  = -16;
  JLINK_SYS_Report1(" LocalI16 + GlobalI16:", LocalI16 + GlobalI16);           // Prints " LocalI16 + GlobalI16: 0000FFE0"
  JLINK_SYS_Report1(" LocalI16 + GlobalI8 :", LocalI16 + GlobalI8);            // Prints " LocalI16 + GlobalI8 : 000000E0"
  JLINK_SYS_Report1(" LocalI16 + GlobalI16:", (LocalI16 + GlobalI16) & 0xFF);  // Prints " LocalI16 + GlobalI16: 000000E0"
  JLINK_SYS_Report1(" LocalI16 + GlobalI8 :", (LocalI16 + GlobalI8 ) & 0xFF);  // Prints " LocalI16 + GlobalI8 : 000000E0"
  GlobalI16 = -16;
  GlobalI8  = -16;
  JLINK_SYS_Report1(" GlobalI16 + GlobalI16:", GlobalI16 + GlobalI16);           // Prints " GlobalI16 + GlobalI16: 0001FFE0"
  JLINK_SYS_Report1(" GlobalI16 + GlobalI8 :", GlobalI16 + GlobalI8);            // Prints " GlobalI16 + GlobalI8 : 000100E0"
  JLINK_SYS_Report1(" GlobalI16 + GlobalI16:", (GlobalI16 + GlobalI16) & 0xFF);  // Prints " GlobalI16 + GlobalI16: 000000E0"
  JLINK_SYS_Report1(" GlobalI16 + GlobalI8 :", (GlobalI16 + GlobalI8 ) & 0xFF);  // Prints " GlobalI16 + GlobalI8 : 000000E0"
}

Comparisons are always performed unsigned, independent of the type of the variables.

void Comparisons(void) {
  I32 LocalI32;
  U32 LocalU32;

  LocalI32 = 0x80000000;  // -2147483648
  LocalU32 = 0x80000000;  // -2147483648
  if (LocalI32 == LocalU32) {  // This expression is true
    JLINK_SYS_Report("LocalI32 == LocalU32: True");
  } else {
    JLINK_SYS_Report("LocalI32 == LocalU32: False");
  }

  LocalI32 = 0x80000000;  // -2147483648
  LocalU32 = 0x00000000;
  if (LocalU32 < LocalI32) {  // This expression is true
    JLINK_SYS_Report("LocalI32 < LocalU32: True");
  } else {
    JLINK_SYS_Report("LocalI32 < LocalU32: False");
  }
}

Unsupported Code Constructs

Description Code Example Error Message
Compound Assignment with Arrays
aSomeArray[n] &= 0;
Error while compiling. Line X, column Y:
aSomeArray[n] &= 0;
                   ^
Tried to free unused register.