Search code examples
vhdlspifsm

Registers created for output ports in FSM, why?


I'm implementing a simple SPI master in VHDL. The problem I'm facing is that during the synthesising two flip flops are created for ss and data_rdy. I thought that the output to those ports is always specified, so why are these registers created, and how can I get rid of them?

My code is below, with the states that don't have ss or data_rdy in them omitted.

entity:

library IEEE;
use IEEE.std_logic_1164.ALL;

entity mcu is
    port(clk      : in  std_logic;
         res      : in  std_logic;
         pc       : in  std_logic_vector(7 downto 0);
         pc_new   : in  std_logic;
         data_ack : in  std_logic;
         miso     : in  std_logic;
         data     : out std_logic_vector(12 downto 0);
         data_rdy : out std_logic;
         mosi     : out std_logic;
         sclk     : out std_logic;
         ss       : out std_logic);
end mcu;

Architecture:

library IEEE;
use IEEE.std_logic_1164.ALL;

architecture behaviour of mcu is
    -- r: send read command
    -- a: send address
    -- rx: receive data
    type state_t is (r0, r1, r2, r3, r4, r5, r6, r7,
                     a0, a1, a2, a3, a4, a5, a6, a7,
                     rx0, rx1, rx2, rx3, rx4, rx5, rx6, rx7, rx8, rx9, rx10, rx11, rx12,
                     idle, starting, done);
    signal state    : state_t                      := idle;
    signal datasig  : std_logic_vector(12 downto 0);
begin
    sclk <= clk;
    mosi <= datasig(12);

    sync : process(clk) is
    begin
        if rising_edge(clk) then
            data_rdy <= '0';
            ss       <= '0';

            if res = '1' then
                state <= idle;
            else
                datasig <= datasig(11 downto 0) & miso;

                if pc_new = '1' then
                    state <= starting;
                else
                    case state is
                    when idle =>
                        ss      <= '1';
                        datasig <= (others => '0');
                        state   <= idle;

                    ...

                    when rx12 =>
                        data     <= datasig;
                        data_rdy <= '1';
                        state    <= done;
                    when done =>
                        if data_ack = '1' then
                            state <= idle;
                        else
                            state <= done;
                        end if;
                end case;
                end if;
            end if;
        end if;
    end process sync;
end behaviour;

The relevant synthesiser output:

===============================================================================
|    Register Name    |   Type    | Width | Bus | MB | AR | AS | SR | SS | ST |
===============================================================================
|    data_rdy_reg     | Flip-flop |   1   |  N  | N  | N  | N  | Y  | N  | N  |
|       ss_reg        | Flip-flop |   1   |  N  | N  | N  | N  | Y  | N  | N  |
|      data_reg       | Flip-flop |  13   |  Y  | N  | N  | N  | N  | N  | N  |
|      state_reg      | Flip-flop |   3   |  Y  | N  | N  | N  | N  | Y  | N  |
|      state_reg      | Flip-flop |   2   |  N  | N  | N  | N  | Y  | Y  | N  |
|     datasig_reg     | Flip-flop |  13   |  Y  | N  | N  | N  | N  | N  | N  |
===============================================================================

Also, why is state split into two registers?


Solution

  • Registering your outputs is normally a Good Thing : it allows much better defined timings, which can translate to more reliable operation, especially with external devices like SPI peripherals.

    The problem (I think) you are facing is that when you want an output to be true whenever you are in a certain state, assigning that output while you are in that state (within the clocked process) will introduce a clock cycle delay.

    David Koontz gives one answer : move all the assignments to that output out of the clocked part - or indeed out of that process altogether, dependent only on the "state" signal.

    But another way will preserve the register for clean timings, and still eliminate that cycle delay. That is to advance the output assignment to any condition in any state that transitions into that state, or (once in it) doesn't transition out of it. So for example, modify

                when rx12 =>
                    data     <= datasig;
                    data_rdy <= '1';
                    state    <= done;
    

    and the transition into state rx12

                when rx11 =>
                    if ready_to_go_to_rx12 then
                       data_rdy <= '1';  -- keep data_rdy synched with rx12
                       state    <= rx12;
                    end if;
    
                when rx12 =>
                    data     <= datasig;
                    -- data_rdy <= '1'; -- no, because we leave state rx12 immediately
                    state    <= done;
    

    and likewise for other outputs with the same problem.

    In all probability this doesn't create extra logic, because the synth tool can usually identify that data_rdy and rx12 are always asserted at the same time, and share a register for them.

    EDIT : I came back for another look, and noticed that data <= datasig; is also registered, and therefore also a cycle late : while I only considered data_rdy as one of the signals you mentioned, you need to consider if other assignments like data <= datasig; should also be advanced. (I would guess so : it's counterintuitive to signal data_rdy in the cycle before new data!)