I am an electronics engineer and an experienced FPGA programmer. However, I am new to the Cyclone V SoC.
Currently, I am designing a vision system, in which the FPGA controls a scanning camera, and the HPS needs to store the captured images, and preforms some processing on them. For the camera control, use the building blocks from Platform Designer (QSYS), and all seems to work properly. For capturing the images, I want to let the FPGA write the video stream directly into the HPS SDRAM memory.
To gain some knowledge on the subject, I downloaded the example project from Critical Link:
This uses a mSGDMA Dispatcher IP, a DMA Write Master IP, and a user defined Avalon Streaming Source. I adapted this design to our own custom board based on a 5CSEBA2U23C7S. When I synthesize this example, all seem to work. Since, I am not familiar with programming in Linux, I use U-Boot memory commands to test the design. I reserved memory space 0x07000000 through 0x07FFFFFF for the DMA transfer, the msgDMA descriptor is connected to the lightweight bridge on location 0xFF200300, and the CSR is at 0xFF200200.
I edited the streaming component a bit. It does not use the package mode anymore, and is resettable by bit 2 of a general purpose output on location 0xFF200010. Bits 1 and 0 are connector to LEDs, so I can create also some extra visual information. If bit 2 is zero the component outputs the counter values. When bit 2 is one, the counter is being reset, and while remaining one, the component only outputs zeroes. The VHDL of the streaming component is now:
entity st_cnt_src_otto is
clk : in std_logic := '0'; -- clock.clk
reset : in std_logic := '0'; -- reset.reset
aso_out0_data : out std_logic_vector(63 downto 0); -- out0.data
aso_out0_ready : in std_logic := '0'; -- .ready
aso_out0_valid : out std_logic; -- .valid
conduit_end_new_signal : in std_logic -- preset.set
end entity st_cnt_src_otto;
architecture rtl of st_cnt_src_otto is
signal slice : unsigned(13 downto 0);
if reset = '1' then
slice <= (others=>'0');
aso_out0_valid <= '0';
elsif rising_edge(clk) then
if aso_out0_ready = '1' then
aso_out0_valid <= '1';
slice <= slice + 1;
if conduit_end_new_signal = '1' then
slice <= (others=>'0');
aso_out0_data <= std_logic_vector(Slice) & "00" & std_logic_vector(Slice) & "01" &
std_logic_vector(Slice) & "10" & std_logic_vector(Slice) & "11" when conduit_end_new_signal = '0'
end architecture rtl; -- of st_cnt_src_otto
First I initialize the memory with a recognizable pattern:
mw 07000000 21646142 200 // write string “BAD!” to SDRAM
md.b 07000000 110 // readback the memory
Second, I start the DMA and read back the memory:
mw ff200010 7 // reset the counter, and LEDs on
mw ff200010 0 // release the counter, and LEDs off
mw ff200304 07000000 1 // write destination address in descriptor
mw ff200308 00000100 1 // set length of transport to 256 bytes
mw ff20030C 80000000 1 // start the transport
md.b 07000000 110 // read back the memory
This looks OK, but now I switch to outputting zeroes:
// write xx zeroes
mw ff200010 7 // reset the counter, and keep output to all zeroes
mw ff200304 07000000 1
mw ff200308 00000100 1
mw ff20030C 80000000 1
md.b 07000000 110
It appears the counter was first still running, and from address 0x80 the zeroes appear. Running the DMA again fills the memory correctly
As far as I can see, it seems the DMA action reads more data (0x80 bytes) than needed, and keeps that in a FIFO. When starting again those bytes are output first, before the new data come. In QSYS I have set the FIFO length at 16 at a data with of 64, which matches the 128 (0x80) bytes I see in the output. I played a lot with the parameters in QSYS, but I couldn’t find a solution. Does anyone recognized this behavior? If so, is there a solution, for instance on how to empty the FIFO?
Additional info: I looked into the generated code for the interface between the streaming source and the write master:
snk_data : in std_logic_vector(63 downto 0) := (others => 'X'); -- data
snk_valid : in std_logic := 'X'; -- valid
snk_ready : out std_logic; -- ready
snk_sop : in std_logic := 'X'; -- startofpacket
snk_eop : in std_logic := 'X'; -- endofpacket
snk_empty : in std_logic_vector(2 downto 0) := (others => 'X'); -- empty
In the demo, the empty signal is not used, the sop and eop are also not used in my application. This leaves only the data and the valid and ready signals. I placed these signals on an oscilloscope to monitor them. As soon as I start the DMA transfer, the signal ready becomes high. In response, I make signal valid high and output the sample counter data. As soon as signal valid becomes low again, I stop the transfer by pulling valid low. I guess that signal ready becomes low as soon as the programmed number of bytes are transferred to the SDRAM. In my opinion, I think that the FIFO is still full of data. Apparently, valid and ready cannot be used as flow control. I can probably fix this by instructing the streaming unit to output exactly the same number of bytes as the write unit expects. But what about if an error occurs? Then there could be data in the FIFO again, and I still need to flush the FIFO.
Otto Tuil (the Netherlands)
- Cyclone® V FPGAs
In the meantime I had some progress.
The f2h_sdram0_clock of the bridge is connected to a 100 MHz clock source. The same clock runs the h2f lw bridge, and also the peripherals (including the mSGDMA IP) connected to this bridge. The st_sink (Avalon Streaming Sink) contains three signals: input sink_data, input sink_valid, and output sink_ready. It appears that sink_valid may not be active permanently while sink_ready is active, but may only be active at minimum once every two clock cycles. Otherwise the DMA transfer may miss a word when the FIFO gets full.
In addition, when I reset the mSGDMA (writing bit 1 of the control register) prior to starting the DMA by writing the descriptor (including the “go” bit), the transfer is correct. If I do not reset the DMA, I receive the contents of the FIFO of the previous transfer prior to the correct data. I still appears that sink_ready is active during the descriptor‘s length plus the size of the FIFO. So, I have to clear the FIFO with the reset command, before starting a new transfer. I still don’t know why this is needed, but with this work around, I can capture the images from the camera.