PSoCでダブルバッファDMAを使ってのデータパス転送

Datapath transfering with Double-Buffered DMA for PSoC
 
カスタムコンポーネントのDMA定義ファイル(*.cydmacap)のLocationタグで「direction="destination"」を指定すると、CPUのメモリー領域からデータパスへのDMAを使った転送が行えるようになります。
out.cydmacap
<?xml version="1.0" encoding="us-ascii"?>
<DMACapability>
  <Category name="led_out" 
            enabled="true" 
            bytes_in_burst="1"
            bytes_in_burst_is_strict="true" 
            spoke_width="1" 
            inc_addr="false" 
            each_burst_req_request="true">
    <Location name="`$INSTANCE_NAME`_dma_ptr" enabled="true" direction="destination"/>
  </Category>
</DMACapability>
私が調べた限り、データパス(ペリフェラル)への転送は最大16bitまでで、32bitはサポートされていないようなので、ここでは8bit DMAの例を紹介します。
DMA転送ではFIFOバッファを使うことが推奨されています。しかしながら、FIFOバッファからのAレジスタへのコピーで1サイクルが使用されるため、LEDの点滅など非常に低い周期での出力となると、タイミングがずれる原因となります。ここではその解決法の一例として、データパスレジスタを制御する高速なクロックと、GPIOの出力を制御する低速なクロックを分けて実装しています。
led_out.v
`include "cypress.v"

module led_out (
	input   clock,
    input   sync,
	output  led_out,
    output  dma
);

localparam PG_IDLE  = 3'b000;
localparam PG_COPY  = 3'b001;
localparam PG_SHIFT = 3'b010;
localparam PG_WAIT  = 3'b011;

reg[2:0] pg_state;
reg[3:0] bit_count;

wire f0_not_full;
wire f0_is_empty;

reg sync_edge;
reg shift_out;
reg led_out_reg;

always @(posedge clock)
begin
    case(pg_state)
    PG_IDLE:
    begin
        if(~f0_is_empty)
            pg_state <= PG_COPY;
    end
    
    PG_COPY:
    begin
        bit_count <= 4'd0;
        pg_state <= PG_SHIFT;
        sync_edge <= sync;
    end
    
    PG_SHIFT:
    begin
        led_out_reg <= shift_out;
        bit_count <= bit_count + 4'd1;
        pg_state <= (bit_count[3]) ? PG_IDLE : PG_WAIT;
    end
    
    PG_WAIT:
    begin
        if(sync_edge != sync)
        begin
            sync_edge <= sync;
            pg_state <= PG_SHIFT;
        end
    end

    endcase
end

assign led_out = led_out_reg;
assign dma = f0_not_full;

cy_psoc3_dp8 #(.cy_dpconfig_a(
{
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM0:IDLE*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC___F0, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM1:A0<=F0*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP___SL, `CS_A0_SRC__ALU, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM2:SHIFT A0*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM3:*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM4:*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM5:*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM6:*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM7:*/
    8'hFF, 8'h00,  /*CFG9:*/
    8'hFF, 8'hFF,  /*CFG11-10:*/
    `SC_CMPB_A1_D1, `SC_CMPA_A1_D1, `SC_CI_B_ARITH,
    `SC_CI_A_ARITH, `SC_C1_MASK_DSBL, `SC_C0_MASK_DSBL,
    `SC_A_MASK_DSBL, `SC_DEF_SI_0, `SC_SI_B_DEFSI,
    `SC_SI_A_DEFSI, /*CFG13-12:*/
    `SC_A0_SRC_ACC, `SC_SHIFT_SL, 1'h0,
    1'h0, `SC_FIFO1_BUS, `SC_FIFO0_BUS,
    `SC_MSB_DSBL, `SC_MSB_BIT0, `SC_MSB_NOCHN,
    `SC_FB_NOCHN, `SC_CMP1_NOCHN,
    `SC_CMP0_NOCHN, /*CFG15-14:*/
    10'h00, `SC_FIFO_CLK__DP,`SC_FIFO_CAP_AX,
    `SC_FIFO_LEVEL,`SC_FIFO_ASYNC,`SC_EXTCRC_DSBL,
    `SC_WRK16CAT_DSBL /*CFG17-16:*/
}
)) dp(
    .reset(1'b0),
    .clk(clock),
    .cs_addr(pg_state),
    .route_si(1'b0),
    .route_ci(1'b0),
    .f0_load(1'b0),
    .f1_load(1'b0),
    .d0_load(1'b0),
    .d1_load(1'b0),
    .so(shift_out),
    .f0_bus_stat(f0_not_full),
    .f0_blk_stat(f0_is_empty)
);

endmodule
FIFO入力時において、バッファが満杯でないときは、データパスのf0_bus_stat値がHighになります。そこで、dma出力にこの値を紐付けることで、DMAによる転送が完了したタイミングを伝えています。A0レジスタにコピーしてからは、8bit分シフトアウトを実行し、はみ出した値はGPIOにそのまま出力されます。
DMAウィザードではSourceにメモリー領域であるSRAMを、Destinationには上記のデザイン図のように配置したDMAコンポーネントを指定します。

PSoCではIndexed DMAという手法によって、複数のTDを連結させることができ、待機中のTD領域にデータを書き込んでいくことでダブルバッファとして機能します。「Number of TDs」の値を変更して、複数のDMAデータを設定してみましょう。
各TDには異なるバッファアドレスを与えます。
FIFOバッファに空きが出るごとに配置したInterruptコンポーネントを介して割り込みが発生します。この割り込み関数内でCyDmaChStatusで取得できる値が現在のTDの保有するIDなので、待機中となっているもう片方のバッファに任意のデータをコピーします。
main.c
#include "project.h"

uint8_t send_buffer_pos;
uint8_t send_buffer[2][24];
const uint8_t send_buffer_data[] = {
    0b11111111, 0b00000000, 0b11111111, 0b00000000,
    0b11110000, 0b11110000, 0b11110000, 0b11110000,
    0b11001100, 0b11001100, 0b11001100, 0b11001100,
    0b10101010, 0b10101010, 0b10101010, 0b10101010,
    0b11001100, 0b11001100, 0b11001100, 0b11001100,
    0b11110000, 0b11110000, 0b11110000, 0b11110000,
};

/* Defines for led_dma */
#define led_dma_BYTES_PER_BURST 1
#define led_dma_REQUEST_PER_BURST 1
#define led_dma_SRC_BASE (CYDEV_SRAM_BASE)
#define led_dma_DST_BASE (CYDEV_PERIPH_BASE)

/* Variable declarations for led_dma */
/* Move these variable declarations to the top of the function */
uint8 led_dma_Chan;
uint8 led_dma_TD[2];

void InitDma()
{
    /* DMA Configuration for led_dma */
    led_dma_Chan = led_dma_DmaInitialize(led_dma_BYTES_PER_BURST, led_dma_REQUEST_PER_BURST, 
        HI16(led_dma_SRC_BASE), HI16(led_dma_DST_BASE));
    led_dma_TD[0] = CyDmaTdAllocate();
    led_dma_TD[1] = CyDmaTdAllocate();
    CyDmaTdSetConfiguration(led_dma_TD[0], 24, led_dma_TD[1], led_dma__TD_TERMOUT_EN | CY_DMA_TD_INC_SRC_ADR);
    CyDmaTdSetConfiguration(led_dma_TD[1], 24, led_dma_TD[0], led_dma__TD_TERMOUT_EN | CY_DMA_TD_INC_SRC_ADR);
    CyDmaTdSetAddress(led_dma_TD[0], LO16((uint32)send_buffer[0]), LO16((uint32)led_out_dma_ptr));
    CyDmaTdSetAddress(led_dma_TD[1], LO16((uint32)send_buffer[1]), LO16((uint32)led_out_dma_ptr));
    CyDmaChSetInitialTd(led_dma_Chan, led_dma_TD[0]);
    CyDmaChEnable(led_dma_Chan, 1);   
}

void CopyDmaBuffer()
{
    uint8_t cid;
    CyDmaChStatus(led_dma_Chan, &cid, NULL);
    
    uint8_t bpos = (cid == led_dma_TD[1]) ? 0 : 1; 
    memcpy(send_buffer[bpos], send_buffer_data, 24);
}

CY_ISR(OnInterruptLed)
{
    CopyDmaBuffer();
}

int main(void)
{
    CyGlobalIntEnable;
    
    memcpy(send_buffer[0], send_buffer_data, 24);
    memcpy(send_buffer[1], send_buffer_data, 24);
   
    InitDma();
    led_dma_isr_StartEx(OnInterruptLed);

    for(;;)
    {
    }
}
このプログラムでは同じデータを延々と上書きコピーしているだけので、ダブルバッファの意味は薄いのであしからず。
2019/06/21