About this Application Note
The Multi-Instance AWE Core Integration Guide is meant to guide users who wish to implement multiple instances of AWE Core and take advantage of the IPC audio routing and tuning communication features of AWE Core.
AWE Core Instance Overview
An instance of AWE Core can be thought of as the smallest individual addressable unit of AWE Core on an embedded target.
...
Distribute audio processing across multiple processing cores in a single design
Increase parallelism by assigning each AWE Core instance to a unique OS thread
Architect groups of features into their own instance to better monitor and analyze feature behavior
AWE Designer profiles CPU and memory usage per individual instance
Standard SoC Architecture Example
Consider a standard multicore SoC architecture:
...
In this typical multicore SoC architecture, an instance of AWE Core can be placed on each core. The shared memory of the SoC is utilized by AWE Core to communicate between the two instances, as well as buffer audio data between instances. AWE Core handles all data exchange between instances; no user protocol or software needs to be written to facilitate.
Setting Up Multi-Instance AWE Core
Initializing Shared Heap
To facilitate multi-instance with AWE Core, each AWE Core instance requires an initialization of the shared heap.
...
The size of this region is provided to AWE Core using sharedHeapSize.
Handling Multi-Instance Tuning Packets
Multi-instance AWE Core requires that the tuning packet buffer registered to each AWE Core instance occupy the same contiguous address space (see Figure 2).
...
Code Block |
---|
/** * @brief Multi-instance Wrapper for tuning packet processing. * If called by the tuning instance, call whenever a complete packet is received. Wait until the return value is not E_MULTI_PACKET_WAITING to forward reply back to the tuning interface. * If called by a non-tuning instance, poll continuously in a low-priority task. * @param [in] pAWE The AWE instance pointer to process * @param [in] isTuningInstance Boolean marking if the instance calling this API implements the tuning interface * @return error @ref E_SUCCESS, @ref E_MULTI_PACKET_WAITING, @ref E_COMMAND_NOT_IMPLEMENTED, @ref E_MESSAGE_LENGTH_TOO_LONG, * @ref E_CRC_ERROR, @ref E_BADPACKET **/ INT32 awe_packetProcessMulti(AWEInstance * pAWE, BOOL isTuningInstance); |
Tuning Master Instance Usage
On the tuning master instance, this API is called whenever a complete packet has been received and is ready to be processed.
...
See Core 0 Packet Processing for a usage example.
Non-Tuning Instance Usage
For non-tuning interface instances, this API is polled continuously. This is done so that the calling instance can check if it has been flagged by the tuning master instance and handle any packet processing if required.
...
See Core 1 Packet Processing for a usage example.
Assigning Unique Instance Ids
AWE Core’s instanceId parameter is used by AWE Designer to identify and address tuning packets to specific instances on an embedded target. For this reason, it is required that each AWE Core instance be registered with a unique instance Id.
...
Code Block |
---|
/* The ID of this instance. Single instance systems should always be 0. */ UINT32 instanceId; /** The number of audio processing instances of AWE Core configured on a single target */ UINT32 numProcessingInstances; |
Processing Audio in Multi-Instance AWE Core
All AWE Core instances handle processing of audio in the same manner. Each instance has its own pump mask, retrieved with awe_audioGetPumpMask(), and uses the returned value to trigger the corresponding layout(s) to pump.
...
It is required to trigger all instances on the target synchronously with the rate of audio buffer I/O. This is a user-specific piece of logic that could be a global RTOS event, global system software interrupt, or other similar logic. Most commonly, this means that a user must flag an interrupt in an audio DMA routine which all cores containing AWE Core instances react and trigger processing to.
Code Example for Standard SoC Architecture
Consider the generic SoC architecture from the Standard SoC Architecture Example.
Core 0 Initialization
Core 0 has a single AWE Core instance and allocates a shared heap memory segment before passing it to its AWE Core instance.
...
The memory section “.shared_heap” is defined to reside in the “shared memory” region of the SoC. Similarly, “.shared_packet” is defined to in the same shared memory region. This is to enforce the AWE Core requirement that all AWE Core instances must have access to the shared heap and packet buffer.
Core 1 Initialization
Similar logic is placed on Core 1, taking care that g_shared_heap and g_PacketBuffer are allocated in the exact same address space as on Core 0, and that this AWE Core instance receives a unique instanceId.
Code Block |
---|
#define INSTANCE_ID (1) #define AUDIO_BLOCK_SIZE (48) #define SHARED_HEAP_SIZE (4*1024) #define MAX_COMMAND_BUFFER_LEN (264) __attribute__((__section__(".shared_heap"))) __ALIGN_BEGIN volatile UINT32 g_shared_heap[SHARED_HEAP_SIZE] __ALIGN_END; __attribute__((__section__(".shared_packet"))) __ALIGN_BEGIN volatile UINT32 g_PacketBuffer[MAX_COMMAND_BUFFER_LEN] __ALIGN_END; void AWEInstanceInit() { /** Other g_AWEInstance init code here **/ /* Set instanceId to 1 */ g_AWEInstance.instanceId = INSTANCE_ID; /* Set fundamental block size (must be same for all instances) */ g_AWEInstance.fundamentalBlockSize = AUDIO_BLOCK_SIZE; /* Set up packet buffer for multi-instance */ g_AWEInstance.packetBufferSize = MAX_COMMAND_BUFFER_LEN; g_AWEInstance.pPacketBuffer = g_PacketBuffer; g_AWEInstance.pReplyBuffer = g_PacketBuffer; /* Define multi-instance setup */ g_AWEInstance.pSharedHeap = g_shared_heap; g_AWEInstance.sharedHeapSize = SHARED_HEAP_SIZE; g_AWEInstance.numProcessingInstances = 2; awe_init(&g_AWEInstance); } |
Core 0 Packet Processing
Finally, the multi-instance packet process API must be called in each core. Core 0 is the core containing the tuning interface, and triggers the packet process API only when a complete packet has been received.
Code Block |
---|
void AWEIdleLoop(void) { while(TRUE) { /* g_bPacketReceived is a global flag set in tuning interface when complete packet is received */ if (g_bPacketReceived) { /* Process the newly received packet */ /* Second argument is TRUE as Core 0 is tuning master */ if (awe_packetProcessMulti(&g_AWEInstance, TRUE) != E_MULTI_PACKET_WAITING) { /* Only reply if tuning instance isn’t waiting for a reply */ g_bPacketReceived = FALSE; /* Send Reply back to PC */ User_ReplyFunction(); } } } } |
Core 1 Packet Processing
Core 1 is not attached to the tuning interface, and only needs to repeatedly poll the packet process API.
Code Block |
---|
void AWEIdleLoop(void) { while(TRUE) { /* Check for and process packet if it exists */ /* Second argument is FALSE as Core 1 is not tuning master */ awe_packetProcessMulti(&g_AWEInstance, FALSE); } } |
Core 0 Audio Processing Synchronization
Instance 0 is the only instance which interfaces with AWE Designer’s I/O pins, and is also the instance which needs to synchronize the audio processing of every other instance.
...
Code Block |
---|
/* This is a callback tied to the audio processing clock domain */ void AudioDMACallback(void) { /* Insert the received CODEC samples into the Audio Weaver buffer */ awe_audioImportSamples(&g_AWEInstance, &CODECBufferIn[0], STRIDE2, CHANNEL1, Sample16bit); awe_audioImportSamples(&g_AWEInstance, &CODECBufferIn[1], STRIDE2, CHANNEL2, Sample16bit); /* Insert the processed Audio Weaver samples into the USB buffer */ awe_audioExportSamples(&g_AWEInstance, &USBBufferIn[0], STRIDE2, CHANNEL1, Sample16bit); awe_audioExportSamples(&g_AWEInstance, &USBBufferIn[1], STRIDE2, CHANNEL2, Sample16bit); /* Get layout mask and pump audio accordingly */ INT32 layoutMask = awe_audioGetPumpMask(&g_AWEInstance); // If higher priority level processing ready pend an interrupt for it if (layoutMask & 1) { /* Pump layout 0 */ /* One can also trigger this logic from here to take place in a different interrupt context */ awe_audioPump(&g_AWEInstance, 0); } if (layoutMask & 2) { /* Pump layout 1 */ awe_audioPump(&g_AWEInstance, 1); } /* Trigger global interrupt within synchronous callback */ /* (the following is a silicon-specific API, many others like it can be used to signal to each core to pump) */ MU_TriggerInterrupts(MUA, kMU_GenInt0InterruptTrigger); } |
Core 1 Audio Processing Synchronization
Core 1 does not have any physical audio I/O connected to it. Core 1 also needs to react to Core 0’s global interrupt flag to process its own audio. No import or export API is called, because only instance 0 can interface with AWE Designer’s I/O pins.
Code Block |
---|
/* This callback is registered to the generic global interrupt flagged by Core 0 This will handle all audio processing for Core 1. It is NOT tied to any audio peripheral or DMA event */ void GenInt0Callback(void) { /* Check the pump mask and pump audio accordingly */ INT32 layoutMask = awe_audioGetPumpMask(&g_AWEInstance); /* If higher priority level processing ready pend an interrupt for it */ if (layoutMask & 1) { /* Pump layout 0 */ /* One can also trigger this logic from here to take place in a different interrupt context */ awe_audioPump(&g_AWEInstance, 0); } if (layoutMask & 2) { /* Pump layout 1 */ awe_audioPump(&g_AWEInstance, 1); } } |
Creating multi-instance designs in Audio Weaver
Audio Weaver provides a single design canvas which can place modules on any instance. Audio data can be routed between instances in one design file, which allows users to move processing loads seamlessly to available cores on an embedded target. The below example canvas has processing distributed over two AWE Core instances residing on two different processing cores.
...
Using the IPCBuffer module
(Source modules only) Modify the clockDivider field of a module
IPCBuffer Module
The IPCBuffer module is a simple module which takes an input data wire, and using a tunable parameter targetInstance, directs this data to a new instance. Modules connected to the output of the IPCBuffer module will inherit this new instance ID.
...
Changing the instance ID of a Source module
For source modules (modules which do not have an input wire), one can change the instance ID by modifying the clockDivider field of the module.
...
Finally, click the Propagate Changes button at the top of Designer to register the change with the module. The resulting module text will update to report the change in Instance ID.
...
Loading Multi-Instance Designs in AWE Core
Multi-instance designs created in Designer are exported as a single binary file (in .awb or C array format) which encompasses all AWE instances.
...
Code Block |
---|
//Example Setup Code, g_AWEInstance is tuning master instance awe_init(&g_AWEInstance); //At this point in the code, all AWE instances should be initialized and reachable //Load multi-instance design from the tuning master instance (g_AWEInstance) UINT32 position; INT32 ret = awe_loadAWBfromArray(&g_AWEInstance, Core0_InitCommands, Core0_InitCommands_Len, &position); |
Loading multi-instance designs on a single-core target
A single-core architecture can support multi-instance AWE Core, where both a tuning master instance and non-tuning instance(s) exist on the same core. In this architecture, one additional integration step is required when loading a multi-instance design.
...
Code Block |
---|
// There are two AWE instances on this core #define NUM_INSTANCES_ON_CORE (2) // Table of AWE Instances used for single-core multi-instance applications AWEInstance *pInstances[NUM_INSTANCES_ON_CORE]; // Initialize AWE signal processing instance awe_init(&g_AWEInstance1); awe_init(&g_AWEInstance2); // Initialize Instance Table with AWE Core pInstances[0] = &g_AWEInstance1; pInstances[1] = &g_AWEInstance2; awe_setInstancesInfo(pInstances, NUM_INSTANCES_ON_CORE); // Load multi-instance design UINT32 position; INT32 ret = awe_loadAWBfromArray(&g_AWEInstance1, Core0_InitCommands, Core0_InitCommands_Len, &position); |
Summary
Utilizing multi-instance AWE Core requires these steps:
...