Event Driven State Machine

Discussion in 'VHDL' started by M. Norton, Oct 7, 2008.

  1. M. Norton

    M. Norton Guest

    Okay, this is sort of a spin-off to my previous question about
    bidirectionals and their behavior. This is a bit of a separate topic
    though and I don't want to get them confused.

    Essentially I'm looking for a semantic template for what I'm trying to
    do, but I don't have a lot of experience with non-RTL construction.
    Usually with RTL there's a fast clock I can use to synchronize
    everything nicely. However, with this DAC, it does not have an
    interior clock, and is completely driven by a SCLK input that is only
    occasionally active, during
    data transfers.

    So I thought I'd try to model this device the way it behaves, but the
    lack of a consistent clock makes things decidedly tricky (not to
    mention the bidirectional port.)

    Here's how I compartmentalized the behavior. I identified four main
    states: idle, rx_cmd, rx_data, tx_data. Now, for each of those states
    there are a number of similar behaviors that happen that I thought
    would be good to break into procedures. rx_byte, tx_byte, rx_ack,
    tx_ack, and wait_for_stop.

    So, here's kind of how I thought the process might look. However, I'm
    not sure it's going to work, so I thought I'd ask and see what folks
    thought. It's not all filled in yet, but here's the basic structure.
    After the code I'll ask the questions.

    --
    -- Reset behavior
    --
    reset <= '1', '0' after POR_DURATION;

    DAC_FSM : process
    procedure rx_byte(clk : in std_logic,
    sda : in std_logic,
    byte : out std_logic) is
    begin
    end procedure receive_byte;

    procedure tx_byte(clk : in std_logic,
    byte : in std_logic,
    sda : out std_logic) is
    begin
    end procedure tx_byte;

    procedure tx_ack(clk : in std_logic,
    sda : out std_logic) is
    end procedure tx_ack;

    procedure rx_ack(clk : in std_logic,
    sda : in std_logic,
    ok : out boolean) is
    end function rx_ack;

    procedure

    begin
    --
    -- Define behavior under reset
    --
    addr_reg <= (others => '0');
    rw_reg <= (others => '0');
    pd_reg <= (others => '0');
    control_reg <= (others => '0');
    sda <= 'Z';
    dac_state <= IDLE;

    wait until falling_edge(reset);

    case dac_state is
    when IDLE =>
    sda <= 'Z';
    wait until falling_edge(sda) and sclk = '1';
    dac_state <= RX_CMD;
    when RX_CMD =>
    rx_byte(sclk, sda, byte);
    tx_ack(sclk, sda);
    if (byte(0) = '1') then
    dac_state <= RX_DATA;
    else
    dac_state <= TX_DATA;
    end if;
    when RX_DATA =>

    when TX_DATA =>
    when others =>
    null;
    end case

    end process;

    So, some questions
    1) If I suspend the process inside a procedure, when the process re-
    enters, will it jump to the procedure the way it's supposed to? I
    think this is true, but I need to check.
    2) In that IDLE state, I select the next state based on a bit. From
    what I can tell then the flow will move to end case, and exit the
    process and.... a miracle happens? How can I get it to re-enter the
    process to pick up on the next state? I don't have a clock to depend
    on here, it's entirely based on some level transitions, so I can't put
    in a "wait until rising_edge(clk)" for re-entry.
    3) If it did re-enter after the idle... will it try to run those
    default assignments and wait for reset again? That could be very bad.
    4) Addressed in that other thread, but for inout port sda, assigning
    it to Z and also checking to see if it's a value in the same region is
    legal? Inouts are starting to drive me crazy and wish for some simple
    RTL to design.

    I think that's the main points of interest. I love the richness of
    the language, but when I leave my little shelter of synthesizable
    code, I feel a bit adrift at sea ;-).

    Best regards,
    Mark Norton
     
    M. Norton, Oct 7, 2008
    #1
    1. Advertising

  2. M. Norton

    M. Norton Guest

    I actually fleshed this out a bit more and thought it might be more
    illustrative than what I posted in the text above. Hopefully this
    will give a little more insight (and some ideas on if I'm barking up
    the wrong tree, or possible the right one).

    DAC_FSM : process
    procedure rx_byte(clk : in std_logic,
    sda : in std_logic,
    byte : out std_logic) is
    begin
    end procedure receive_byte;

    procedure tx_byte(clk : in std_logic,
    byte : in std_logic,
    sda : out std_logic) is
    begin
    end procedure tx_byte;

    procedure tx_ack(clk : in std_logic,
    sda : out std_logic) is
    end procedure tx_ack;

    procedure rx_ack(clk : in std_logic,
    sda : in std_logic,
    ok : out boolean) is
    end function rx_ack;

    variable byte : std_logic_vector(7 downto 0) := X"00";

    begin
    --
    -- Define behavior under reset
    --
    addr_reg <= (others => '0');
    rw_reg <= '0';
    pd_reg <= (others => '0');
    control_reg <= (others => '0');
    sda <= 'Z';
    dac_state <= IDLE;
    wait until falling_edge(reset);

    case dac_state is
    when IDLE =>
    sda <= 'Z';
    wait until falling_edge(sda) and sclk = '1';
    dac_state <= RX_CMD;
    when RX_CMD =>
    rx_byte(sclk, sda, byte);
    addr_reg <= byte(7 downto 1);
    rw_reg <= byte(0);
    tx_ack(sclk, sda);
    if (rw_reg = '1') then
    dac_state <= RX_DATA;
    else
    dac_state <= TX_DATA;
    end if;
    when RX_DATA =>
    rx_byte(sclk, sda, byte);
    pd_reg <= byte(5 downto 4);
    control_reg(11 downto 8) <= byte(3 downto 0);
    tx_ack(sclk, sda);
    rx_byte(sclk, sda, byte);
    control_reg(7 downto 0) <= byte;
    tx_ack(sclk, sda);
    wait_for_stop(sclk, sda);
    dac_state <= IDLE;
    when TX_DATA =>
    byte := "00" & pd_reg & control_reg(11 downto 8);
    tx_byte(sclk, sda, byte);
    rx_ack(sclk, sda, ack);
    if (not ack) then
    -- report something here
    end if;
    byte := control_reg(7 downto 0);
    if (ack) then
    -- report something here
    end if;
    wait_for_stop(sclk, sda);
    dac_state <= IDLE;
    when others =>
    null;
    end case

    end process;
     
    M. Norton, Oct 7, 2008
    #2
    1. Advertising

  3. Interesting...

    > DAC_FSM : process
    >........
    > begin
    >........
    > wait until falling_edge(reset);
    >........
    > end process;


    This implies that the process will only ever execute on a falling edge of the
    reset. After the falling edge of the reset it will execute the code below it,
    then loop around, and wait for the falling edge of the reset again. Are you sure
    this is what you want? Seems to me like your dac_state will always be IDLE.

    Kind regards,

    Pieter Hulshoff
     
    Pieter Hulshoff, Oct 7, 2008
    #3
  4. M. Norton

    M. Norton Guest

    On Oct 7, 4:45 am, Brian Drummond <>
    wrote:
    > if it suspends at a WAIT is in the procedure, it will restart after the
    > WAIT.


    Good. I ended up using this quite a lot.

    > And passing signals to the procedure as arguments will work nicely...
    > BUT if you are always connecting the same signal to the procedure AND
    > the procedure is defined in the process; the procedure can see the
    > signal and use the process's driver on the signal, so you don't need the
    > arguments. (If you wanted the procedure to read/write different signals
    > on calls in different places, you would need arguments to connect them)


    I eliminated some of the procedural arguments and I think it reads
    better now. Thanks for the suggestion.

    > But you don't have to let the process exit in the first place.
    > Place the case statement in an infinite loop, and check that each path
    > round the loop has a "Wait" somewhere. Then it won't need a sensitivity
    > list because re-entering it isn't an issue; it never exits, and it is
    > entered ONCE at simulation time 0.


    Yes, that was the key to the whole puzzle really. Since the DAC is
    just a lone device sitting out at the end of the bus, without a clock
    of it's own (aside from the occasional serial clock) it has to operate
    continuously and independently. The loop seems to do this nicely.
    Now I have to write a testbench for my testbench and make sure it's
    modelling the way I think it is ;-).

    Here's what the final ended up being. It compiles cleanly, though I
    haven't yet checked behavior.

    entity dac_model is
    port (
    --
    -- Basic 2-line interface
    --
    sclk : in std_logic;
    sda : inout std_logic;

    --
    -- Reporting ports
    --
    addr_reg : out std_logic_vector(6 downto 0);
    rw_reg : out std_logic;
    pd_reg : out std_logic_vector(1 downto 0);
    control_reg : out std_logic_vector(11 downto 0)
    );

    end dac_model;

    architecture behavioral of dac_model is

    type FSM_STATES is (IDLE, RX_CMD, RX_DATA, TX_DATA);

    signal dac_state : FSM_STATES := IDLE;

    --
    -- DAC device does not have external reset pin, so
    -- I will create an initial start-up time.
    --
    constant POR_DURATION : time := 10 ns;
    signal reset : std_logic := '1';

    begin

    --
    -- Reset behavior
    --
    reset <= '1', '0' after POR_DURATION;

    DAC_FSM : process

    procedure rx_byte(byte : out std_logic_vector(7 downto 0)) is
    variable bit_cnt : integer := 0;
    variable int_byte : std_logic_vector(7 downto 0) := X"00";
    begin
    --
    -- Data is asserted during sclk=low and valid on rising
    -- clock edge and held while clock is high. Using
    internal
    -- byte representation as cannot read from output
    procedure
    -- port.
    --
    for bit_cnt in 0 to 7 loop
    wait until rising_edge(sclk);
    int_byte := int_byte(6 downto 0) & sda;
    end loop;
    --
    -- Assign output
    --
    byte := int_byte;
    end procedure rx_byte;

    procedure tx_byte(byte : in std_logic_vector(7 downto 0)) is
    variable bit_cnt : integer := 0;
    begin
    --
    -- We enter this while clock is low after
    -- a falling edge, so assert immediately and then
    -- wait for the next falling edge.
    for bit_cnt in 7 downto 0 loop
    sda <= byte(bit_cnt);
    wait until falling_edge(sclk);
    end loop;
    --
    -- End by releasing the bus.
    --
    sda <= 'Z';
    end procedure tx_byte;

    procedure tx_ack is
    begin
    --
    -- Wait until the next clock low period,
    -- and assert bus until the next clock low
    -- period.
    --
    wait until falling_edge(sclk);
    sda <= '0';
    wait until falling_edge(sclk);
    sda <= 'Z';
    end procedure tx_ack;

    procedure rx_ack(ack : out boolean) is
    begin
    --
    -- Master will assert acknowledge during a
    -- low period and will be valid during the
    -- rising edge of the clock.
    --
    wait until rising_edge(sclk);
    if (sda = '0') then
    ack := true;
    else
    ack := false;
    end if;
    end procedure rx_ack;

    variable byte : std_logic_vector(7 downto 0) :=
    X"00";
    variable ack : boolean :=
    false;
    -- Internal representation of registers
    variable pd_reg_i : std_logic_vector(1 downto 0) :=
    "00";
    variable control_reg_i : std_logic_vector(11 downto 0) :=
    X"000";

    begin
    --
    -- Loop always
    --
    loop
    case dac_state is
    when IDLE =>
    --
    -- Reset behavior should happen here
    --
    if (reset = '1') then
    addr_reg <= (others => '0');
    rw_reg <= '0';
    pd_reg <= (others => '0');
    control_reg <= (others => '0');
    sda <= 'Z';
    dac_state <= IDLE;
    end if;
    --
    -- Keep sda undriven for now.
    --
    sda <= 'Z';
    --
    -- Defined as I2C Master Start Sequence
    --
    wait until falling_edge(sda) and sclk = '1';
    dac_state <= RX_CMD;

    when RX_CMD =>
    --
    -- Receive the 8-bit command.
    --
    rx_byte(byte);
    --
    -- Parse the received byte and place
    -- on output reporting ports
    --
    addr_reg <= byte(7 downto 1);
    rw_reg <= byte(0);
    --
    -- Transmit the acknowledge
    -- This exits after a falling edge.
    --
    tx_ack;
    --
    -- Determine which state has been
    -- commanded.
    -- rw_reg == high -- Master reads from DAC
    -- rw_reg == low -- Master writes to DAC
    -- rw_reg is byte(0) but cannot be used directly
    -- since it is an output port and may not be read
    from.
    --
    if (byte(0) = '1') then
    dac_state <= TX_DATA;
    else
    dac_state <= RX_DATA;
    end if;

    when RX_DATA =>
    --
    -- Receive the first 8 bits.
    --
    rx_byte(byte);
    --
    -- Parse the received byte.
    -- Note the two MSB's are don't care.
    --
    pd_reg_i := byte(5 downto 4);
    control_reg_i(11 downto 8) := byte(3 downto 0);
    --
    -- Transmit acknowledge
    --
    tx_ack;
    --
    -- Receive the second 8 bits.
    --
    rx_byte(byte);
    --
    -- Parse the received byte.
    --
    control_reg_i(7 downto 0) := byte;
    --
    -- Transmit acknowledge
    --
    tx_ack;
    --
    -- Wait for the stop condition and then return to
    -- idling. Also at this time, assert the
    reporting
    -- output ports.
    --
    wait until rising_edge(sda) and sclk = '1';
    pd_reg <= pd_reg_i;
    control_reg <= control_reg_i;
    dac_state <= IDLE;

    when TX_DATA =>
    --
    -- Commanded to report the contents of the control
    -- register back to Master. The last acknowledge
    -- left us just following the falling edge of sclk
    --
    -- Form up the byte to be transmitted and transmit
    --
    byte := "00" & pd_reg_i & control_reg_i(11 downto
    8);
    tx_byte(byte);
    --
    -- Wait for acknowledgement by Master
    -- Flag error by Master if acknowledgement does
    not occur.
    --
    rx_ack(ack);
    assert ack
    report "DAC Model: Master failed to
    acknowledge byte read-back."
    severity failure;
    --
    -- Form up next byte and transmit.
    --
    byte := control_reg_i(7 downto 0);
    tx_byte(byte);
    --
    -- The last byte is denoted by the Master as No
    Ack
    --
    rx_ack(ack);
    assert not ack
    report "DAC Model: Master failed to transmit
    no-ack for 2nd byte."
    severity failure;
    --
    -- Wait for the stop condition and return to idle.
    --
    wait until rising_edge(sda) and sclk = '1';
    dac_state <= IDLE;

    when others =>
    -- Kick state back into something known.
    dac_state <= IDLE;

    end case;
    end loop;
    end process;

    end behavioral;
     
    M. Norton, Oct 7, 2008
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. David Lamb
    Replies:
    1
    Views:
    695
  2. Weng Tianxiang
    Replies:
    7
    Views:
    1,132
    Mike Treseler
    Nov 25, 2003
  3. Weng Tianxiang
    Replies:
    3
    Views:
    1,468
    Weng Tianxiang
    Jul 25, 2006
  4. Grumps
    Replies:
    2
    Views:
    713
    Grumps
    Feb 13, 2008
  5. fenster
    Replies:
    3
    Views:
    1,177
    jeppe
    Dec 23, 2011
Loading...

Share This Page