One of the things I do a lot of design high reliability systems for space and other mission critical applications. For years I have been meaning to create a simple demonstrator which demonstrates the power of designing your Finite State Machine safely.
To do this of course we need to be able to corrupt the state machine during run time of the FPGA. For this I am going to use the Trenz/Digilent TE0802 board to implement a demonstration which I can outline to clients and students just how FSMs can lock up and how we can code to prevent this.
To demonstrate this project is going to implement two state machines in the programmable logic.
- State Machine One - no mitigation if an illegal state is entered
- State Machine Two - mitigation for an illegal state being entered
This state machine will be able to be reset or the faults injected via the switches on the board. While the number of errors injected in the state machine will be displayed on the seven segment display.
TheoryTo provide context, state machines store thier current state within registers. In electrically noisy environments, or high radiation environments the value stores in these registers can change. This can lead to the FSM entering an unmapped state, resulting in the state machine locking up. Should the state machine lock up the only recovery mechanism is to be reset the design or state machine.
This is concept is shown in the figure below.
There are several techniques which can be used to implement the mitigation within a FSM. This depends upon if you want to detect an error or complete the operation seamlessly despite the error. Xilinx Synthesis user guide 901 outlines several of the FSM mitigation schemes which can be implemented by Vivado during synthesis. This includes some very complex mitigation strategies such as Hamming Three.
As always this project is going to operate under software control to allow us to change the design and test at run time.
Vivado DesignThe first thing we want to do with this project is to get started with creating a new Vivado project.
In to this project we are going to create three files, for inclusion on our block diagram. These files are
- Seven Segment display this will drive the anode and cathode of the seven segment display to display four numbers.
- State Machine No Mitigation
- State Machine Mitigation
Both state machines will be implemented in the same manner and same function it is just mitigation version will be protected against error.
Seven Segment Display
The seven segment display indicates a number or A-F letter by illuminationing one of 7 led elements arranged as a figured of eight.
Each of the four bytes needs its power applying to it in sequence, looking at the circuit diagrams (see below) we need to switch on a FET to power the digit, we turn on the signal by driving the signal low.
We then need to drive the pattern of LED we desire to represent the number we wish to represent. We do this by driving a logic one out on the elements we wish to illuminate, again guided by the schematics.
The seven segement code operates in a simple manner we use the following approach
- Shift Register which loops around at 20ms rate enabling and disabling the digit
- Digit selection method, this will output the digit depending upon the phase of the shift register to output the desired digit. The design takes in a 16 bit Binary Coded Decimal word to output the four digits.
- Decoding element which converts from BCD to the seven bit led illumination pattern for the selected digit.
- Counter to generate the refresh timer for the shifter register
The code for the seven segment display can be seen below.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
--! This is a description
--! of the entity.
entity seven_segment is port(
clk: in std_logic; --20 MHz clock
number : in std_logic_vector(15 downto 0);
seven_segement : out std_logic_vector(6 downto 0); --drives the cathode
seven_anode : out std_logic_vector(4 downto 0) --drives the anode
);
end entity;
architecture test of seven_segment is
constant refresh_count : integer := 200000;
signal refresh : unsigned(17 downto 0):=(others=>'0');
signal update : std_logic;
signal anode : std_logic_vector(3 downto 0) :="1110";
signal seven_seg : std_logic_vector(3 downto 0);
begin
seven_anode(3 downto 0) <= anode;
seven_anode(4) <= '1';
process(clk)
begin
if rising_edge(clk) then
if update = '1' then
anode <= anode(anode'high-1 downto anode'low) & anode(anode'high);
end if;
end if;
end process;
process(clk)
begin
if rising_edge(clk) then
case anode is
when "1110" =>
seven_seg <= number(3 downto 0);
when "1101" =>
seven_seg <= number(7 downto 4);
when "1011" =>
seven_seg <= number(11 downto 8);
when "0111" =>
seven_seg <= number(15 downto 12);
when others => null;
end case;
end if;
end process;
process(clk)
begin
if rising_edge(clk) then
if (refresh = refresh_count) then
refresh <= (others =>'0');
update <= '1';
else
refresh <= refresh + 1;
update <= '0';
end if;
end if;
end process;
process(seven_seg)
begin
case seven_seg is
when "0000" => seven_segement <= "0111111"; -- "0"
when "0001" => seven_segement <= "0000110"; -- "1"
when "0010" => seven_segement <= "1011011"; -- "2"
when "0011" => seven_segement <= "1001111"; -- "3"
when "0100" => seven_segement <= "1100110"; -- "4"
when "0101" => seven_segement <= "1101101"; -- "5"
when "0110" => seven_segement <= "1111100"; -- "6"
when "0111" => seven_segement <= "0000111"; -- "7"
when "1000" => seven_segement <= "1111111"; -- "8"
when "1001" => seven_segement <= "1100111"; -- "9"
when "1010" => seven_segement <= "0000010"; -- a
when "1011" => seven_segement <= "1100000"; -- b
when "1100" => seven_segement <= "0110001"; -- C
when "1101" => seven_segement <= "1000010"; -- d
when "1110" => seven_segement <= "0110000"; -- E
when others => seven_segement <= "0111000"; -- F
end case;
end process;
end architecture;
State Machine
The state machine will wait in the idle state until the system is asked to start and perform either a read or a write. When doing a read it will read a 16 bit word from the input to the state machine. When requested to write the stored input value, previously written will be output. This is demonstrated in the state diagram above.
As there are only three states in the state machine it leaves a unmapped state which can result in lock up.
The code for the un-mitigated state machine is below
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.all;
-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;
-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;
entity sm is port (
rst: in std_logic;
clk: in std_logic;
start: in std_logic;
r_w : in std_logic;
--state_in : in std_logic_vector(1 downto 0);
state_out: out std_logic_vector(1 downto 0);
inject_err : in std_logic_vector(1 downto 0);
read_data : in std_logic_vector(15 downto 0);
output_data : out std_logic_vector(15 downto 0)
);
end sm;
architecture Behavioral of sm is
constant idle : std_logic_vector(1 downto 0) := "00";
constant read : std_logic_vector(1 downto 0) := "01";
constant write : std_logic_vector(1 downto 0) := "10";
signal current_state : std_logic_vector(1 downto 0);
signal transfer_word : std_logic_vector(15 downto 0);
attribute fsm_encoding : string;
attribute fsm_encoding of current_state : signal is "sequential";
--alias current_state: std_logic_vector(1 downto 0) is state_out;
begin
state_out <= current_state;
process(rst, clk)
begin
if rst = '1' then
current_state <= idle;
output_data <= (others=>'0');
elsif rising_edge(clk) then
case current_state is
when idle =>
if start = '1' then
if r_w = '1' then
current_state <= (write or inject_err) ;
else
current_state <= (read or inject_err);
end if;
end if;
when read =>
transfer_word <= read_data ;
current_state <= (idle or inject_err);
when write =>
output_data <= transfer_word;
current_state <= (idle or inject_err);
when others => null;
end case;
end if;
end process;
end Behavioral;
The mitigated state machine offers the same functionality except that the design has protection for the unmapped state.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.std_logic_unsigned.all;
-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;
-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;
entity sm_safe is port (
rst: in std_logic;
clk: in std_logic;
start: in std_logic;
r_w : in std_logic;
state_out: out std_logic_vector(1 downto 0);
inject_err : in std_logic_vector(1 downto 0);
read_data : in std_logic_vector(15 downto 0);
output_data : out std_logic_vector(15 downto 0)
);
end sm_safe;
architecture Behavioral of sm_safe is
constant idle : std_logic_vector(1 downto 0) := "00";
constant read : std_logic_vector(1 downto 0) := "01";
constant write : std_logic_vector(1 downto 0) := "10";
signal current_state : std_logic_vector(1 downto 0);
signal transfer_word : std_logic_vector(15 downto 0);
attribute fsm_encoding : string;
attribute fsm_encoding of current_state : signal is "sequential";
begin
process(rst, clk)
begin
if rst = '1' then
current_state <= idle;
output_data <= (others =>'0');
elsif rising_edge(clk) then
case current_state is
when idle =>
if start = '1' then
if r_w = '1' then
current_state <= write;
else
current_state <= read;
end if;
end if;
when read =>
transfer_word <= read_data;
current_state <= idle;
when write =>
output_data <= transfer_word;
current_state <= idle;
when others =>
output_data <= transfer_word;
current_state <= idle;
end case;
end if;
end process;
end Behavioral;
Once these modules have been completed we can create a new block diagram and add the three modules to the block diagrams.
Before we add the three blocks we first need to add and configure a Zynq MPSoC processing system.
Once this has been added we need to run the block automation to configure the Zynq MPSoC for the TE0802 board
With the processing system configured, we need to set the clock to work at 20 MHz.
Once this has been created we need to add in the following IP to create the design as we want.
- AXI GPIO - Read the buttons to reset and inject faults
- AXI GPIO - Drive the 16 bit BCD word to the seven segment module
- AXI GPIO - Provide the control, rst, r/w and error injection commands to the state machines - to split the vector slices are used to create single bits from the bus
- AXI GPIO - To provide the input vector and capture output vector for the state machine which is not mitigated
- AXI GPIO - to provide the input vector and capture output vector for the state machine which is protected.
The completed block diagram is shown below
Once this has been completed we can create a wrapper and the XDC file for the constraints which define the IO mapping.
In the constraints file add the following below
# SEG_C[0] = SEG_CA
set_property PACKAGE_PIN E4 [get_ports {seven_segement_0[0]}]
set_property PACKAGE_PIN D3 [get_ports {seven_segement_0[1]}]
set_property PACKAGE_PIN N5 [get_ports {seven_segement_0[2]}]
set_property PACKAGE_PIN P5 [get_ports {seven_segement_0[3]}]
set_property PACKAGE_PIN N4 [get_ports {seven_segement_0[4]}]
set_property PACKAGE_PIN C3 [get_ports {seven_segement_0[5]}]
set_property PACKAGE_PIN R5 [get_ports {seven_segement_0[6]}]
set_property PACKAGE_PIN N3 [get_ports {seven_segement_0[7]}]
set_property IOSTANDARD LVCMOS18 [get_ports seven_segement_0*]
set_property PACKAGE_PIN A8 [get_ports {seven_anode_0[4]}]
set_property PACKAGE_PIN A9 [get_ports {seven_anode_0[3]}]
set_property PACKAGE_PIN B9 [get_ports {seven_anode_0[2]}]
set_property PACKAGE_PIN A7 [get_ports {seven_anode_0[1]}]
set_property PACKAGE_PIN B6 [get_ports {seven_anode_0[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports seven_anode_0*]
set_property PACKAGE_PIN P3 [get_ports {GPIO_0_tri_i[0]}]
set_property PACKAGE_PIN P2 [get_ports {GPIO_0_tri_i[1]}]
set_property IOSTANDARD LVCMOS18 [get_ports GPIO_0_tri_i*]
Once the design is built, we can export the design to Vitis.
VitisIn Vitis select a workspace and then create a new application project. Point the platform information to the exported design
Select the first processor
Create a new standalone domain
Select the hello world project template and finish the project creation wizard.
The software application is going to do the following
- Initialize the AXI GPIO connections
- Read the switch positions for reset and error injections
- Control the state machines to perform a read operation
- Control the state machines to perform a write operation
- Check the results of the two state machines
- Identifiy if there are any errors
- Update the Seven segment display
The software created for the application is below
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpio.h"
#include <stdbool.h>
#define seven_seg_id XPAR_AXI_GPIO_0_DEVICE_ID //seven segement op
#define r_w_fsm0 XPAR_AXI_GPIO_1_DEVICE_ID //r w data for unsafe fsm
#define control XPAR_AXI_GPIO_2_DEVICE_ID //cNTRl for both and fault injection
#define r_w_fsm1 XPAR_AXI_GPIO_3_DEVICE_ID //r w data for safe fsm
#define err_sw XPAR_AXI_GPIO_4_DEVICE_ID //error switch
XGpio Gpio0;
XGpio Gpio1;
XGpio Gpio2;
XGpio Gpio3;
XGpio Gpio4;
int main()
{
init_platform();
print("Adiuvo FSM effects demonstrator\n\r");
XGpio_Initialize(&Gpio0, seven_seg_id);
XGpio_Initialize(&Gpio1, r_w_fsm0);
XGpio_Initialize(&Gpio2, control);
XGpio_Initialize(&Gpio3, r_w_fsm1);
XGpio_Initialize(&Gpio4, err_sw);
uint16_t read_1,response_1;
uint16_t response_2;
uint8_t err_count1=0;
uint8_t err_count2 =0;
uint8_t err_inj =0;
uint8_t sw = 0;
uint8_t rst = 0;
read_1 =0;
while(1){
if((sw & 1) == 1 ){
err_inj = 0x03;
} else {
err_inj =0x00;
}
if((sw &2)== 2){
rst = 0x1;
err_count2 = 0;
err_count1 = 0;
read_1 =0;
} else
{
rst=0x0;
}
XGpio_DiscreteWrite(&Gpio1,1,read_1);
XGpio_DiscreteWrite(&Gpio3,1,read_1);
XGpio_DiscreteWrite(&Gpio2,1,(err_inj<<8)|0x02|rst); //write information
XGpio_DiscreteWrite(&Gpio2,1,0x6); //read the information
sw = XGpio_DiscreteRead(&Gpio4,1);
//printf("sw val %x\n\r", sw);
response_1 = XGpio_DiscreteRead(&Gpio1,2);
response_2 = XGpio_DiscreteRead(&Gpio3,2);
if((response_1 == read_1) )
{
printf("FSM 1 is OK\n\r");
} else
if ((sw &2)!= 2)
{
printf("FSM 1 is has faulted\n\r");
err_count1++;
} else
{
printf("FSM 1 in reset\n\r");
}
if((response_2 == read_1))
{
printf("FSM 2 is OK\n\r");
} else
if ((sw &2)!= 2)
{
printf("FSM 2 is has faulted\n\r");
err_count2++;
} else
{
printf("FSM 2 in reset\n\r");
}
usleep(1000000);
read_1 ++;
XGpio_DiscreteWrite(&Gpio0,1,(err_count2<<8)|err_count1);
}
cleanup_platform();
return 0;
}
TestingWhen the design is tested, we can observe the results over the serial terminal and we can see the count on the seven segment of any errors as they are introduced.
The video below also shows the design in action - although there is an issue between the refresh of the seven segment and the my camera.
Wrap UpWhile the method used to inject the fault can only simulate that which can occur this project well demonstrates the benefits of designing the state machine safely as the properly designed one does not error when the fault is injected to both of them.
Comments