Extra Credit

The ARM/FPGA protocol for generic I/O is conceptually simple, but the devil is always in the details. The following information isn’t needed to make use of the Generic FileIO but it may help to understand at least superficially some of the under-the-hood details.

These extra notes are mostly a disorganised brain dump after looking over the Generic I/O protocol. Whilst incomplete, I’ve included them in case they provide a useful starting point for further study.

ARM

The ARM code provides three different types of FileIO: ATA, Floppy and Generic with drivers within that selected based on the file type ADF HDF etc

The number of slots the core supports on each channel as well as driver type is read from the FPGA/core via an SPI read request (OSDCMD_READSTAT | 0x04 and OSDCMD_READSTAT | 0x05) which is handled on the FPGA in rtl/Replay_Syscon.

ATA and Floppy are used to mount disk images of various types and supported filesystems like ADF for example. The Generic driver on the other hand takes a single file on the SD card and makes it accessible to the FPGA Core via an addressable block of mapped memory.

Relevant files are fileio.c and the three fileio_drv*.c files, in particular the fileio_drv00_generic.

Six commands are available for IO comms across channel A or B.

// note values are for chan A. Chan B is value + 0x40
#define FILEIO_FCH_CMD_STAT_R 0x00
#define FILEIO_FCH_CMD_STAT_W 0x08
#define FILEIO_FCH_CMD_CMD_W  0x10
#define FILEIO_FCH_CMD_CMD_R  0x18
#define FILEIO_FCH_CMD_FIFO_R 0x20
#define FILEIO_FCH_CMD_FIFO_W 0x30

When the file (disk) is inserted the FileIO_Drv00_InsertInit function sends a write command

rSPI(FCH_CMD(ch, FILEIO_FCH_CMD_CMD_W | 0x0));
rSPI(drive_number);

That’s CMD_W 0x10 or’d with the register write address (see fpga section below) and finally + 0x0 or 0x40 depending on whether this is channel A or B (applied by FCH_CMD).

In the above case for channel A, the output is 0x10 for the first byte and then the drive_number for the second byte (0x0 in this case).

After this it then sends another write cmd, but this time starting writes with register 4 (0x4).

rSPI(FCH_CMD(ch, FILEIO_FCH_CMD_CMD_W | 0x4));
rSPI((uint8_t)(pDesc->file_size      ));
rSPI((uint8_t)(pDesc->file_size >>  8));
rSPI((uint8_t)(pDesc->file_size >> 16));
rSPI((uint8_t)(pDesc->file_size >> 24));

Again with channel A and CMD_W (0x10) this results in the first byte sent of 0x14 followed by four more bytes for the 32bit source file size. The 0x4 start address will be clearer when the FPGA side is covered below but it ensures the first byte of data is written into the command register 4, the next 5 then 6 and finally 7. If another byte was written, the wrap around to register 0 would cause an overwrite of the drive_number currently stored there.

After this it will eventually enter a process loop (see FileIO_Drv00_Process) where a read command is sent to the FPGA to determine the address to read and size of data requested. The ARM will then seek to this offset in the actual file, read the requested amount of data and send this back to the FPGA in 512 byte chunks. The next chunk is only read/transmit once the FPGA has acknowledged the previous.

A similar process occurs for writes.

FPGA

On the FPGA side, things are a little more complex and this is just a rough overview. I have not delved into the SPI handling at all.

The replay lib handles receiving data from the ARM via SPI using the Replay_FileIO entity. The output of this is then fed through the Replay_FileIO_FCh_Generic entity to decode it (as the ARM was instructed by the FPGA to use the File generic protocol).

The replay_fileio_fch_generic makes available two 1024 entry 18 wide FIFO buffers for incoming and outgoing data as well as an 8x8bit read/write command.

The 18 bits covers 16bits of data and two bits to store the slot the data came in on or is going out to.

On disk insert the ARM sends a write command of 0x10 which is CMD_W (0x10) with channel A (0x0) and offset of 0x0. Following this byte is the drive (slot) number byte.

After that the ARM sends a second command 0x14 which decodes to CMD_W (0x10), channel A (0x0)and a start register of the written data of 4 (CMD_W makes has 8 registers in the range 0..7 available).

Note: Whilst data is sent via two separate CMD_W uses, each writes data to a different command register. The CMD_W on the FPGA has 8 available write locations. In this case, 0 is used for the drive (slot) number and locations 4-7 for the file size. Any attempt to send additional data using CMD_W risks overwriting this unless the data is written to locations 1-3.

CMD_W data sent from the ARM by both of those commands is handled by the fch_generic “p_cmd” process

p_cmd : process
begin
  wait until rising_edge(i_clk);
  if (i_ena = '1') then
    if (i_fch_to_core.cmd_we = '1') then
      case rx_addr_i is
        when 0 => cmd_w_cur <= i_fch_to_core.cmd_data(1 downto 0);
        when 4 => cmd_w_size(cmd_w_cur_i)( 7 downto  0) <= i_fch_to_core.cmd_data;
        when 5 => cmd_w_size(cmd_w_cur_i)(15 downto  8) <= i_fch_to_core.cmd_data;
        when 6 => cmd_w_size(cmd_w_cur_i)(23 downto 16) <= i_fch_to_core.cmd_data;
        when 7 => cmd_w_size(cmd_w_cur_i)(31 downto 24) <= i_fch_to_core.cmd_data;
        when others => null;
      end case;
    end if;
  end if;
end process;
o_size0 <= cmd_w_size(0);

The cmd_addr (via rx_addr_i) is used to read out the the “slot” (cmd_addr 0) then cmd_addr 4-7 to obtain the data for the 32bit source file size. Bytes 1..3 are ignored due to the 0x4 write address. The file size result will eventually be valid in o_size0, usage of which is only safe after inserted from SYSCON is set.

Note the total file size is only exposed for files on slot 0 at this time. Although data from other slots can be requested and will be appended to the end of the FIFO queue.

When the arm enters the read loop, it first sends the read request to determine how much data to read and from which address, this is handled by p_out. Which sends back a 16 bit size and 32 bit address.

Back to index