Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > VHDL > Event Driven State Machine

Reply
Thread Tools

Event Driven State Machine

 
 
M. Norton
Guest
Posts: n/a
 
      10-06-2008
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
 
Reply With Quote
 
 
 
 
M. Norton
Guest
Posts: n/a
 
      10-07-2008
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 <= 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 ;
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;
 
Reply With Quote
 
 
 
 
Pieter Hulshoff
Guest
Posts: n/a
 
      10-07-2008

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
 
Reply With Quote
 
M. Norton
Guest
Posts: n/a
 
      10-07-2008
On Oct 7, 4:45 am, Brian Drummond <(E-Mail Removed)>
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 := 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
;
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;
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
State machine - Vending machine - strange behaviour fenster VHDL 3 12-23-2011 09:53 AM
How to print a state flow graph for a state machine using Xilinx ISE or ModelSim Weng Tianxiang VHDL 3 07-25-2006 01:19 PM
What is the state of state machine after power-up without reset conditions Weng Tianxiang VHDL 7 11-25-2003 06:24 PM
State machine: how to stay in a state? David Lamb VHDL 1 09-15-2003 05:24 PM



Advertisments