Generic FileIO Protocol

In the previous section, the “Generic” driver type was configured for channel A. But what is that?

The ARM firmware provides three different driver types, Floppy, ATA and Generic. The details of which can be found in “sw/arm_sw/Replay_Boot/” in the filio* files.

Generic in this case refers to the protocol used between the ARM and FPGA to transfer data. It provides a way for the FPGA to request the transfer of a block of data from the file mounted on a given channel and slot, starting at an offset within that file.

This allows the FPGA to request starting at an offset e.g 0x0 , a block of data (say 512 words), which the arm will package up and transmit back, then another block can be requested and another until the end of the file is reached (or another error encountered). At which time the FPGA can loop back to the start of the file, offset 0x0.

This is a gross over simplification of how the ARM firmware and FPGA accomplish (via SPI) the transfer but only some of the underlying details need to be understood in order to use this functionality.

FCh Generic Entity

The receipt of generic fileio data by the FPGA is handled by the Replay_FileIO_FCh_Generic entity. This provides a way to access data on the currently mounted sd card file via memory addressing. Given a read from addr of size fileio_size (512 in this case), behind the scenes the FCH_Generic entity will request 512 words of data and a fifo queue will be filled with data from the ARM (and in turn SD card) and vice-versa for writing.

As soon as one transfer of 512 16bit words is complete, another should be requested to ensure when the first block of 512 words has been played, the next block is already in the queue ready to play i.e double buffered playback.

Take a look at the entity in “hw/replay/cores/replay_lib/rtl/replay_fileio_fch_generic.vhd” the interface of which is:

port (
  -- clocks
  i_clk                 : in  bit1;
  i_ena                 : in  bit1;
  i_rst                 : in  bit1;
  -- FileIO / Syscon interface
  i_fch_to_core         : in  r_FileIO_to_core;
  o_fch_fm_core         : out r_FileIO_fm_core;
  -- to user space
  i_req                 : in  bit1; -- request block
  o_ack_req             : out bit1; -- request taken, params latched
  o_ack_trans           : out bit1; -- transfer done
  o_trans_err           : out word(2 downto 0); -- aborted, truncated, seek error
  -- below latched on ack
  i_dir                 : in  bit1; -- low is from arm
  i_chan                : in  word( 1 downto 0); -- chan (drive 0 - 3)
  i_addr                : in  word(31 downto 0); -- address ONLY USE EVEN ADDRESS/SIZE when in 16 bit mode!!
  i_size                : in  word(15 downto 0); -- request size "
  -- chan0
  o_size0               : out word(31 downto 0); -- DO NOT USE BEFORE INSERTED FROM SYSCON IS SET
  -- .. others chans could be added
  --
  i_fifo_to_core_flush  : in  bit1;
  o_fifo_to_core_data   : out word(17 downto 0); -- fifo_ch(1..0) & data
  i_fifo_to_core_taken  : in  bit1;
  o_fifo_to_core_valid  : out bit1;
  o_fifo_to_core_level  : out word(10 downto 0); -- bit 9 is HF, bit 10 set is FULL
  o_fifo_to_core_overfl : out bit1;
  -- from user space
  i_fifo_fm_core_flush  : in  bit1;
  i_fifo_fm_core_data   : in  word(15 downto 0);
  i_fifo_fm_core_we     : in  bit1;
  o_fifo_fm_core_level  : out word(10 downto 0)
  --
  );

It contains two FIFO queues near the end, one for reading and one for writing. These use FIFO_SyncBlock_D1024_W18 which provides space for 1024 18bit words. Once the FIFO queue is full, another request should not be made until the FIFO queue is half empty to avoid overflowing the buffer and losing data.

Logic in the user core needs to handle this pausing of requests to avoid overfilling the buffer. p_fileio_req (described below) will do that. It will also keep requesting consecutive 512 word blocks until a o_trans_err of 1 which represents a seek error such as attempting to read beyond the end of the file.

Note: A word is 16 bits from the arm whilst the FIFO uses 18bit words as the last two MSB represent the channel slot the data is for. Data for all 4 slots is kept in the same FIFO queue.

As for how we know when to not request more data, the current size of the buffer is given by o_fifo_to_core_level. If bit 10 (512) is not set, then the buffer has at least enough room out of its 1024 capacity to read another 512 words.

The following additions take place within Example_Audio_Top.

Generic File IO Entity

An instance of the Generic entity should be added just above the “some blinking LED thingy” and has the following mapping:

  --
  -- FILEIO
  --
  u_FileIO_FCh : entity work.Replay_FileIO_FCh_Generic
  port map (
    -- clocks
    i_clk                 => i_clk_sys,
    i_ena                 => i_ena_sys,
    i_rst                 => i_rst_sys,

The clocks can be directly linked to the ones passed into Example_Audio_Top.

For the FileIO interface we want to use channel A which is the “fcha” port.

    -- FileIO / Syscon interface
    i_fch_to_core         => i_fcha_to_core,
    o_fch_fm_core         => o_fcha_fm_core,

The remaining ports are mapped to signals as these will be needed by other entity instances within Example_Audio_Top to make transfer requests and extract data from the FIFO queue when available

    -- to user space
    i_req                 => fileio_req,
    o_ack_req             => fileio_ack_req,
    o_ack_trans           => fileio_ack_trans,-- transfer done
    o_trans_err           => fileio_trans_err, -- aborted, truncated, seek error

These are used to request a new transfer be initiated (fileio_req) by the Generic FileIO. The address of the read or write and size is defined lower down and latched in when fileio_req is 1. fileio_req should be kept at 1 until the request is acknowledge by fileio_ack_req going high.

At this point a transfer will take place behind the scenes and completion of the data transfer is signaled by fileio_ack_trans going high.

fileio_trans_err signals error conditions including attempts to read beyond the end of the file at which point audio playback could be stopped (once the buffer empties) or loop by setting the file address back to 0x0.

The request address and size should not be changed once i_req has been taken high until o_ack_req goes high to signal the request has been sent to the arm.

    -- below latched on ack
    i_dir                 => '0', -- read only
    i_chan                => "00",
    i_addr                => fileio_addr,
    i_size                => fileio_size,
    o_size0               => fileio_src_size,

These are pretty self explanatory, direction is either 0 for read or 1 for write. Channel selects which of the four slots on channel A (previously selected) the data is to/from. Received data will also be tagged with the slot it came in on (see below)

addr is an offset within the file to start the transfer request from. size is the amount of data to receive in this transfer block and finally size0 contains the total size of the file currently mounted on the SD card. Note, size0 should not be used until inserted is signalled by SYSCON.

Next comes the part we’re really after, the actual data. The generic entity provides 18 bits worth of data on fileio_data which is comprised of 16 bits of the requested sample data as well as the two most significant bits for the channel the data belongs to. It’s possible to request data from several slots on the same channel in turn and all four slots worth of data are intermixed in the same FIFO queue.

    -- For reading
    i_fifo_to_core_flush  => '0',
    o_fifo_to_core_data   => fileio_data,
    i_fifo_to_core_taken  => fileio_taken,
    o_fifo_to_core_valid  => fileio_valid,
    o_fifo_to_core_level  => fileio_rx_level,
    o_fifo_to_core_overfl => fileio_rx_overfl,

Once fileio_valid is high, the data word can be read out and handed off to the audio DAC, at which time fileio_taken should be set high to signal readyness to accept the next word of data from the front of the FIFO.

The last two entries represent the number of words of data currently in the FIFO queue (fileio_rx_level) which is used to avoid requesting additional data until the queue is emtpy enough to hold it. Finally fileio_rx_overfl for when you fail to pay attention to the buffer level and cause the queue to fill with yet more data incoming which will be lost.

That leaves the final set of ports for writing data. Those are not important for this example and can be defaulted to 0/open.

    -- For writing
    i_fifo_fm_core_flush  => '0',
    i_fifo_fm_core_data   => (others => '0'),
    i_fifo_fm_core_we     => '0',
    o_fifo_fm_core_level  => open
    );

Lastly the fileio_size signal is set to 512 words. This is done as a signal as the size is needed later on to increment the read address as each 512 word transfer block completes.

  -- request size is 512 16 bits words
  fileio_size <= x"0400";

signals now need adding for each of the fileio_ entries. The types of which simply match the the Generic FileIO port entries and should be added within the Example_Audio_Top architecture just below the tick signal.

-- fileio
signal fileio_addr            : word(31 downto 0);
signal fileio_size            : word(15 downto 0);
signal fileio_src_size        : word(31 downto 0);

signal fileio_req             : bit1;
signal fileio_ack_req         : bit1;
signal fileio_ack_trans       : bit1;
signal fileio_trans_err       : word( 2 downto 0);

signal fifoio_tx_flush        : bit1;
signal fileio_data            : word(17 downto 0);
signal fileio_taken           : bit1;
signal fileio_valid           : bit1;
signal fileio_rx_level        : word(10 downto 0);
signal fileio_rx_overfl       : bit1;

In replay.prj just after the replay_lib_wrap line the fileio vhd needs adding to the project

vhdl    work    replay_fileio_fch_generic.vhd

If you attempt to build now, you’ll receive a number of errors such as

ERROR:Xst:528 - Multi-source in Unit <Example_Audio_Top> on signal <o_fcha_fm_core_req_rx_ok>; this signal is connected to multiple drivers.
Drivers are: 
   Signal <u_core/u_Core/u_FileIO_FCh/o_fch_fm_core_req_tx_ok> in Unit <Replay_FileIO_FCh_Generic> is assigned to VCC
   Signal <u_core/u_Core/o_fcha_fm_core_req_rx_ok> in Unit <Example_Audio_Top> is assigned to GND

This is because the o_fcha_fm_core port is now used by an entity, but the Example_Audio_Top is still setting a default of z_Fileio_fm_core, this line needs removing.

Any time a port is used, make sure to check a default wasn’t being applied and likewise, if you remove an entity or stop using a port, check if the parent entity should now apply a default for that port.

The core should now build without error although it is not yet functional as nothing requests data and even if it did, there’s nothing passing that data on to the Audio output.

Next section Block Requester

Back to index