I finally bit the bullet and started learning my way around the FPGA Replay Framework and it proved quite hard to initially get going. I flailed around in the dark for some time, but every so often had a “aha” moment as a new concept clicked into place, how the library was structured hierarchy wise, how it instantiates a user core, where clocks come in and which new clocks are generated…

Following on from learning the basics of VHDL and the 1-bit dac experiment, I decided to try to play an audio file via the Replay Framework using the “getting_started” core as a minimal starting base. This time the file would be coming off of the SD card and transfered via the ARM cpu to the FPGA.

It turns out that the framework wraps the underlying complexity quite well but as an absolute newcomer to FPGA/VHDL and the replay board, I was unable to treat the framework components as complete black boxes. Some of the difficulty is simply down to my lack of knowledge working with VHDL and FPGAs, others were down to lack of documentation which meant having to delve into how each entity worked (sometimes recursively) in order to work out what the constraints on any given in/out ports were.

I imagine in time this will change as documentation is created. A stable framework was clearly needed before anything could be documented and I get the feeling doing so took a fairly significant amount of time/effort to produce.

File Transfer

One method of transferring data from the SD card to the FPGA is to make use of one of the three “drivers” the arm firmware supports for fileio. One is used to replicate floppy IO, another HDD IO and the third that I used, a Generic IO protocol.

A few ini file changes are all that’s needed to allow the user to select a file via the on screen display and expose it to the FPGA on a given drive (0-3). Between reading forum posts and looking over other core implementations, it’s relatively easy to work out the ini options supported and what they do.

On the FPGA side, the Replay framework provides a Generic File IO entity which wraps the underlying SPI communication with the arm, allowing user cores to request a block of data from a starting offset within the mounted file and some time later to be notified that the data has now been added to the end of the FIFO queue.

The interface to this on the FPGA is as follows:

entity Replay_FileIO_FCh_Generic 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)

Making use of this even with a rough understanding of what it’s doing and examples of other cores using it, I still found quite tricky. It’s not until you understand more of the underlying FileIO Generic protocol that all the in/out ports finally make sense.

Without knowing that however, you’re left wondering, when should i_req be set? what is the difference between o_ack_req and o_ack_trans? How do I know when I can request more data without exceeding the buffer capacity?

More Questions than Answers

Looking at the Generic IO code at first just added to my confusion and list of questions. Since a chunk of it is dependent on communication with the ARM (via SPI) to stream data over which required a basic understanding of how that and the arm firmware worked in order to get a handle on when signals would be raised. In time however, the mind fog gradually lifted and whilst it took a few evenings of study, I feel I now have a basic understanding of how the Framework works, what it provides and how to make use of at least parts of it.

As far as example source goes, the “loader” core proved an incredibly useful reference for figuring out how much of this worked, although even that raised new questions, “domain crossing” and “pulse hold” was the answer to one, as for the question, that will be part of the guide mentioned below.

I’m not going to cover how my core works in this blog post as there’s too much background needed. I will however be posting a guide/tutorial that breaks it down, although even that will only provide a superficial view of the underlying workings.

I expect each area of the framework outside of audio, will require a similar level of study to understand enough to use. Perhaps in time there’ll be documentation covering the entity interfaces for items that user cores might make use of. That said, even without documentation I managed to avoid delving deeper into exactly how the SPI transfer works or how the framework is outputing audio, so the wrapping certainly saved me a vast amount of time (let alone had I needed to code those myself, see you in a few years?).

I’ll update this blog post with a link to the guide once available along with the final project files. My plan is to make it a follow-along tutorial with hopefully enough context to orient yourself should you want to dig into the real details yourself. In the mean time, here’s a youtube video of my getting_started core streaming a music file off of the SD card and playing it back via the replay framework.

It likely doesn’t seem like much, just mount the file and play it back, but as always the devil is in the details and as a complete newcomer to VHDL and FPGAs, I’m very pleased to have reached this point even if a lot of the underlying code was already written and I had the “loader” core to fall back to as a reference.

Update: I’ve now typed my notes up and added the FPGA Replay Audio Example guide. A step by step walkthrough to take you from the getting-started core to playing audio off the SD card.