Monday, November 7, 2016

PSoC 5LP DFB assembler - ACU as a loop counter

This time we will use the address calculation unit ACU as a loop counter. We create a PSoC creator project to test our ideas. The DFB assembler code is checked out in the component simulator until everything seems to work as planned and then we place the code onto a real chip under control of the debugger.

Test project

The test project is setup to transfer ADC readings to the DFB using DMA. A new output value from the DFB is signaled by raising an interrupt. The interrupt flag can be read from the main loop or handled by an interrupt handler. In the test code the interrupt status is checked in the main look and DFB output data is saved in a SRAM array.

Example - Mean value of N samples

This example calculates the mean value of successive blocks of 10 samples.
Samples are read from staging register A, multiplied with 0.1 (1/N) and accumulated in the MAC.
The address calculation register ACUB is used as loop counter. Modulo arithmetic for ACUB is enabled and the final loop counter value N (10) is loaded to the MREG register from ACU RAM[0] during setup . When the loop counter reaches 10 the accumulated value is written to output register A and the accumulator is cleared before waiting for the next input value.

// Calculates average of 10 inputs
area acu
org 0
dw 0x000A

area data_a
org 0
dw 0x0CCCCC // RAMA[0] = 0.1

acu(clear, clear) dmux(sa,sa) alu(set0) mac(hold)
acu(hold, loadm) addr(0) dmux(sa,sa) alu(hold) mac(clra)
acu(setmod, setmod) dmux(sa,sa) alu(hold) mac(hold) jmp(eob,wait_input)
// Read staging register A to MAC port B and multiply-accumulate with RAMA[0]
acu(hold, incr) addr(1) dmux(sra,ba) alu(clearsem, 001) mac(macc)
// Move MAC o/p to ALU
// When ACUB is 10 then block is complete and output is written to holding register
acu(hold,hold) dmux(sm,sm) alu(seta) mac(hold) jmp(acubeq,write_output)
// Use semaphore0 to signal that DFB is waiting for input
acu(hold,hold) dmux(sa,sa) alu(setsem, 001) mac(hold) jmpl(in1,process_input)
//Wait for ALU output
acu(hold, hold) dmux(sm,sm) alu(hold) mac(hold)
// Write the MAC content to holding register A and clear the MAC
acu(clear, clear) addr(1) dmux(sa,sa) alu(set0) mac(hold) write(bus)
acu(clear, clear) dmux(sa,sa) alu(hold) mac(clra) jmp(eob,wait_input)

There are a few notes:
  1. The acubeq condition is true BOTH when the ACUBREG is equal to MREG and when its 0, so we catch the end of the loop when ACUBREG equals MREG and reset it in code
  2. Placing the wait_input state after the process input eliminates one jump 
  3. The jump when waiting for input is done with a loop jump jmpl(in1,process_input) that transfers control to the beginning of the block if the condition is not true, the block is this single instruction. 
  4. The jump when checking the loop count jmp(acubeq,write_output) is a simple jmp that falls through to the next block ( wait_input) if the condition is not set.
  5. Each sample is processed in less than 4 cycles, including the write of the output. If the DFB runs at 48MHz this equals 12MSamp/s.
  6. Mux settings are only important in two instructions in this example: 
      1. When reading from RAMA to channel A and the input register A to channel B, passing these values to the MAC input. dmux(sr,ba) , mux3 settings are not used.
      2. When outputting accumulated value from MAC to ALU input A. dmux(sm,sm) , only mux3a setting is used.

The example project can be found at:


Unknown said...

Hi, thanks for the examples using DFB Assembler :D, i'm trying to understand the code on this post and got a couple of silly questions:
* (this is more a confirmation) On the wait_input routine, the "in1" condition on the jmpl command will be true when the channel A have data available?
* What is the main purpose of setting the semaphore bit 0 on wait_input and clearing it on the process_input routine?

Hope you can help me clarifying it.
Thanks in advance.

Magnus Lundin said...

Yes the in1 condition is true when there is data available on channel A.

The semaphore is to signal from the DFB to the application that the DFB is ready and waiting for input. I use this when testing and sending data to DFB from a loop in my main code.

When the data comes from the ADC through DMA then the DFB code will have finished long before next data value arrives. So no synchronization is needed, but when feeding from a loop in your code, some way is needed to signal that the previous value has been read, or that the DFB is waiting for input. I am still experimenting with how to use semaphores and input signals for communication with the DFB.

Unknown said...

Great, thanks for the explanation, just read the main code and saw that it's using polling method to get the interrupt source.

Thanks for the examples, all are very helpfull.