Forum Discussion

Altera_Forum's avatar
Altera_Forum
Icon for Honored Contributor rankHonored Contributor
12 years ago

Generic driver to rest of the system communication - advice needed?

Hello all,

First - I am new to VHDL but:

I have built a state machine that communicates with FT245 USB chip using parallel protocol (it is very simple - 8-bit data bus, read and write strobes and 2 flags from the FT245 to indicate if its internal FIFO has space - to write or has data - to read). I have tested it and it sends data to PC fine and I can also read received byte in VHDL.

My next step is to make this code more generic so I can re-use it in my other projects and to really be able to send data to PC and receive the data into some controller code to make some action.

As such I was thinking for my FT245 driver to have an input byte STD_LOGIC_VECTOR(7 DOWNTO 0) that module user would send byte which has to be send to FT245 and consequently to the PC and the same 1 byte as output byte. Since I will be utilizing FIFO from the FT245 chip, I would pass state of its flags to the module user, so it can be checked if read or write is possible.

For writing, user would have to assert write_request flag to '1' and then my FT245 module would go into the writing part of the state machine (of course - flag that FIFO for writing from FT245 would have to be in proper state). Next write request can happen only if write_request flag is put back to '0' and then back to '1'.

For reading, user would have to assert read_request flag to '1' and then my data_ready flag would drop from '1' to '0'. On next clock cycle, data will be read (lasts several clock cycles @ 50Mhz FPGA clock) and then data_ready flag will be asserted back to '1'.

In case both write_request and read_request are asserted on same clock edge, write would take precedence.

I am posting this since I am not particularly happy with the flags approach.

What is write_request flag is forgoten to be set back to 0? Should I think it is again a new write_request and continue from idle state to writing part immediately thus sending data again and again? Same with read_request - is it really read or? Similarly notifying that new that is available is so-so....

I guess after one write cylce, I can make a new state where I wait for write_request to drop back to 0, if it is not already. And same for the read cycle. But then I can be stuck in those states....

Since such "communication" between modules aka. various hardware drivers is I guess common - what are the usual approaches taken?

After this is done, I need to do communication with ADC, then make 32-bit quadrature encoder decoder with counter and some configuration part where PC sends some configuration like encoder counts to mm coefficient and so on. That is why I am thinking of using modular approach.

I am using some chinese Cyclone IV E FPGA module.

7 Replies

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    Your description is very wordy - no timing diagrams, so I dont really know what you're getting at. Are there any standard interfaces to use rather than this that would allow you to plug it into more mudules (like an Avalon or AXI-4?) read_req/wr_request is pretty standard and straight forward, just like a FIFO. But it seems a bit odd (and inefficient) to have to detect a rising edge on wr_req, you're data rate will be half the clock rate. THis seems to be more suitable for a requesd/acknowledge type interface.

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    Thanks for your reply.

    There is no code nor timing diagrams since I wanted to describe my idea first in an human time-efficient manner which is by using words.

    Developing the solution to produce code and timing diagrams would take some time after which someone can provide better or more efficient protocol type, which is also simple to implement but I didn't even know it exists.

    Are there any "design patterns" or "best practices" books or papers for always repeating codes and/or glue logic such as this? Because I am sure that in most more complex FPGA solutions you have bunch of "modules" communicating with each other (sending, reading, waiting for completion of some task, etc). It is very hard to find something online as opposed to software where this is common.

    Request/acknowledge type interface would work as:

    1. Byte data put on input vector of FT245 driver. FT245 driver "data_sent" bit is low.

    2. Request to write put high.

    3. FT245 driver goes to next state and performs sending sequence.

    4. FT245 driver goes to state upon completion of sending sequence and puts data_sent bit high.

    5. Requester waits for data_sent to become high. When this happens, puts request to write to low and puts ACK bit to FT245 as high.

    6. FT245 driver puts "data_sent" bit back to low and goes to idle state.

    As for data rate:

    @ 50Mhz clock every clock is at 20ns. Write pulse to FT245 chip is at minimum 50ns (= 3 clock cycles = 60ns). Read pulse is also 50ns (= 3 clock cycles = 60ns). I would loose about 3-4 clocks on this "communication protocol" making my write speed about 7 clock cycles (140ns) for 1 byte. This is 6.811MB/s which is well over the USB 2.0 data rate limit. so It is OK. however I would definetly like to learn more about more efficient implementations to know for the future!
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    I have looked at the Avalon bus specification for memory mapped slaves and devised a similar protocol:

    Below is the user of the FT245 driver - it is a 2 process state machine:

    
    LIBRARY ieee;
    USE ieee.std_logic_1164.all;
    ENTITY Comm IS
            PORT 
            (
               clk : IN STD_LOGIC;
                rstn : IN STD_LOGIC;
                debug_wr_flag : STD_LOGIC;
                debug_in_data : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
                debug_data_rd : OUT STD_LOGIC_VECTOR(7 DOWNTO 0);
                debug_data_wr : OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
            );
    END ENTITY;
    ARCHITECTURE behavioral OF Comm is
       SIGNAL debug_wr_flag_reg : STD_LOGIC;
       SIGNAL debug_in_data_reg : STD_LOGIC_VECTOR(7 DOWNTO 0);
        
       SIGNAL rd_fifo_has_data : STD_LOGIC; -- FT245 fifo has data for reading
        SIGNAL wr_fifo_full : STD_LOGIC; -- FT245 fifo is full for writing
       SIGNAL waitreq : STD_LOGIC; -- FT245 is busy
        SIGNAL rd : STD_LOGIC; -- request rd
        SIGNAL wr : STD_LOGIC; -- request wr
        SIGNAL rddatavalid : STD_LOGIC; -- data is valid    
        SIGNAL rd_data : STD_LOGIC_VECTOR(7 DOWNTO 0); -- received (read) data by FT245
        SIGNAL wr_data : STD_LOGIC_VECTOR(7 DOWNTO 0); -- data to be written to FT245
        SIGNAL can_write : STD_LOGIC;
        SIGNAL can_read : STD_LOGIC;
        
        TYPE state IS (init, idle, wait_write, wait_read);    
        SIGNAL pr_state, nx_state : state;
        
        
        COMPONENT FT245 IS
       PORT 
        ( 
           clk : IN STD_LOGIC;
            rstn : IN STD_LOGIC;
            rd_flag : IN STD_LOGIC;
            wr_flag : IN STD_LOGIC;
            rd_fifo_has_data : OUT STD_LOGIC;
            wr_fifo_full : OUT STD_LOGIC;
            waitreq : OUT STD_LOGIC;
            rddatavalid : OUT STD_LOGIC;
            data_write : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
            data_read : OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
        );
        
       END COMPONENT;
        
    BEGIN
       driver : FT245 port map 
        (
            clk => clk, 
             rstn => rstn, 
             rd_flag => rd,
             wr_flag => wr,
             rd_fifo_has_data => rd_fifo_has_data, 
             wr_fifo_full => wr_fifo_full, 
             waitreq => waitreq, 
             rddatavalid => rddatavalid, 
             data_write => wr_data, 
             data_read => rd_data
        );
     
    -- For debug purposes - show current values of rd and wr data. Not synced on purpose. 
    debug_data_rd <= rd_data;
    debug_data_wr <= wr_data;
    -- For debug purposes - to inject some data to send...
    PROCESS(clk)
    BEGIN
       IF(rising_edge(clk)) THEN
         debug_wr_flag_reg <= debug_wr_flag;
         debug_in_data_reg <= debug_in_data;
        END IF;
    END PROCESS;
    -- Sequential part
    PROCESS(clk, rstn)
    BEGIN
       IF(rstn = '0') THEN
            pr_state <= init;
        ELSIF(rising_edge(clk)) THEN
            pr_state <= nx_state;
        END IF;
    END PROCESS;
    -- Combinatorial part
    -- can write only if debug wr flag is 1 and fifo is not full and no transaction is in progress
    can_write <=  '1' WHEN debug_wr_flag_reg = '1' and wr_fifo_full = '0' and wr = '0' and rd = '0'  and waitreq = '0'
                      ELSE '0';
    -- can read only if FT245 fifo has data and no transacation is in progress
    can_read <= '1' WHEN rd_fifo_has_data = '1' and wr = '0' and rd = '0'  and waitreq = '0' 
                    ELSE '0';
    PROCESS(pr_state, wr_fifo_full, waitreq, rddatavalid, rd_data, can_write, can_read, debug_in_data_reg)
    BEGIN
      CASE pr_state IS
        WHEN init =>
              wr_data <= (others => '0');
                rd <= '0';
                wr <= '0';            
              nx_state <= idle;
         WHEN idle =>
         
              wr_data <= (others => '0');
                rd <= '0';
                wr <= '0';
                nx_state <= idle;
                
              IF(can_write = '1') THEN
                   wr_data <= debug_in_data_reg;
                    wr <= '1';
                    nx_state <= wait_write;
                ELSIF(can_read = '1') THEN
                   rd <= '1';
                    nx_state <= wait_read;            
                END IF;        
                
         WHEN wait_write =>
         
                wr_data <= debug_in_data_reg;
                wr <= '1';
                rd <= '0';
                nx_state <= wait_write;
                
              IF(waitreq = '0') THEN
                   wr <='0';
                    nx_state <= idle;            
                END IF;          
                
         WHEN wait_read =>
         
                rd <= '1';
                wr <= '0';
                wr_data <= (others => '0');
               nx_state <= wait_read;
              IF(waitreq = '0' and rddatavalid = '1') THEN
                   rd <= '0';                
                   nx_state <= idle;                
                END IF;     
                
         WHEN others =>
         
            wr_data <= (others => '0');
              wr <= '0';
             rd <= '0';  
             nx_state <= init;
              
      END CASE;
    END PROCESS;
    END behavioral;
    

    And this is the skeleton of FT245 driver as a single process timed state machine. When it enters do_write and do_read, it will have sub-states to actually strobe the FT245 chip with data, etc. This code only shows the communication side towards.

    I had some trouble with inferring latches for the output so I did a single process FSM.

    
    library ieee;
    USE ieee.std_logic_1164.all;
    USE ieee.numeric_std.all;
    ENTITY FT245 IS
       PORT 
        ( 
           clk : IN STD_LOGIC;
            rstn : IN STD_LOGIC;
            rd_flag : IN STD_LOGIC;
            wr_flag : IN STD_LOGIC;
            rd_fifo_has_data : OUT STD_LOGIC;
            wr_fifo_full : OUT STD_LOGIC;
            waitreq : OUT STD_LOGIC;
            rddatavalid : OUT STD_LOGIC;
            data_write : IN STD_LOGIC_VECTOR(7 DOWNTO 0);
            data_read : OUT STD_LOGIC_VECTOR(7 DOWNTO 0)
        );
    END ENTITY;
    ARCHITECTURE behavioral OF FT245 IS
    BEGIN
    -- Debug values 
    rd_fifo_has_data <= '0'; -- will not go into reading 
    wr_fifo_full <= '0'; -- will always mark as write is OK....
    PROCESS(clk, rstn) 
        TYPE state IS (init, idle, do_write, write_end, do_read, read_end);    
        VARIABLE pr_state, nx_state : state;
        
       VARIABLE dummy : INTEGER RANGE 0 to 100; -- dummy counter for read data
        VARIABLE count : INTEGER RANGE 0 to 5;
        VARIABLE timer : INTEGER RANGE 0 to 5;
    BEGIN
        IF(rstn = '0') THEN
           count := 0;
            pr_state := init;
            nx_state := init;
            
        ELSIF(rising_edge(clk)) THEN
        
           count := count +1;
            
            CASE pr_state IS
                   
           WHEN init =>
              
              dummy := 0;                    
              rddatavalid <= '0';
              waitreq <= '0';          
              timer := 0;
              nx_state  := idle;
              data_read <= (others => '0');
              
            WHEN idle => 
                        
                rddatavalid <= '0';
                waitreq <= '0';
                nx_state := idle;
                timer := 0;
                
               IF(wr_flag = '1') THEN 
                  waitreq <= '1';
                  nx_state := do_write;
                ELSIF(rd_flag = '1') THEN
                  waitreq <= '1';
                  nx_state := do_read;
                END IF;              
           WHEN do_write =>
                    
                rddatavalid <= '0';
                waitreq <= '1';            
                nx_state := write_end;
                timer := 5; -- 100ns delay - as needed for FT245 to do its work....substates should be
                            -- here to do actual protocol to FT245
                  
           WHEN write_end => 
                    
                rddatavalid <= '0';
                waitreq <= '0';
                nx_state := idle;
                timer := 0;            
                
            WHEN do_read =>
          
              dummy := dummy +1;            
                rddatavalid <= '0';
                waitreq <= '1';
                
                nx_state := read_end;
                timer := 5; -- 100 ns delay; actually there should be sub-states here 
                            -- to talk to FT245 chip as required
                
            WHEN read_end =>
          
             data_read <= std_logic_vector(to_unsigned(dummy,8));         
             rddatavalid <= '1';
             waitreq <= '0';
             
             nx_state := idle;
             timer := 0;            
                
             WHEN others =>
              
              data_read <= (others => '0');          
              rddatavalid <= '0';
              waitreq <= '0';
              
              timer := 0;
              nx_state  := init;
            
          END CASE;
            
          -- Update present state with next state 
          IF(count >= timer) THEN
            pr_state := nx_state;
            count := 0;
          END IF;  
        END IF; -- rising clock
    END PROCESS; -- fsm
    END behavioral;
    

    Didn't have time to do simulation yet - holidays & such.

    What do you all think?
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    --- Quote Start ---

    Here's some information on interfacing to FTDI devices ...

    http://www.ovro.caltech.edu/~dwh/correlator/pdf/ftdi.pdf

    Cheers,

    Dave

    --- Quote End ---

    Thanx.

    Protocol itself is very simple - I did a functional proof of concept and tested in on real hardware in 1 hour.

    However, there is some interesting stuff in there to check it out in terms of integrating FSM for FTDI chips into rest of the code. I will check it out.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    --- Quote Start ---

    I have looked at the Avalon bus specification for memory mapped slaves and devised a similar protocol ...

    Didn't have time to do simulation yet - holidays & such.

    What do you all think?

    --- Quote End ---

    We think you need to write a simulation.

    Altera provides a verification IP suite. You should use that suite to confirm your code simulates correctly.

    You comment above that you are new to VHDL, so here is a piece of advice for you to follow; Writing a simulation should be an integral part of your code development, not an afterthought.

    Cheers,

    Dave
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    --- Quote Start ---

    We think you need to write a simulation.

    Altera provides a verification IP suite. You should use that suite to confirm your code simulates correctly.

    You comment above that you are new to VHDL, so here is a piece of advice for you to follow; Writing a simulation should be an integral part of your code development, not an afterthought.

    Cheers,

    Dave

    --- Quote End ---

    Well, I need to build something to be able to simulate it and then consequently to verify it. Now that I have built it, I can simulate it.

    I have noticed though, based on reading most of the threads on this sub-forum for VHDL that community on this forum/site is not as warm and welcoming as is on forums for mbed, avr or arduino where people discuss, help and teach/learn from each other. Here it is more business-like which does not really welcome beginners or people wanting to learn something....it is strange - because if it were different more people would probably be eager to learn and use FPGA/CPLDs.