---------------------------------------------------------------------------------- -- Company: -- Engineer: -- -- Create Date: -- Design Name: -- Module Name: LCTest_Base - Behavioral -- Project Name: -- Target Devices: -- Tool versions: -- Description: -- -- Dependencies: -- -- Revision: -- Revision 0.01 - File Created -- Additional Comments: -- ---------------------------------------------------------------------------------- -- =================================================================================================== -- =================================================================================================== library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.NUMERIC_STD.all; -- This module is responsible for carrying out a single path and sample test. It ALWAYS configures the -- REBEL row (launch rows may already be loaded) and sample number and is invoked by the LCTest_Driver -- Each time it is invoked, it configures the REBEL row and starts the LCSweep engine, which carries out -- a sequence of launch/capture tests each with the fine phase shift adjusted until the edge propagates -- to a target FF. Multiple samples are collected and averaged if requested. entity LCTest_Base is Generic( PHASE_SHIFT_LEN_NB: integer := 11; PHASE_SHIFT_RANGE: integer := 1120; PHASE_SHIFT_ZEROPHASE: integer := 0; MAX_NUM_SAMS_NB: integer := 8; XOR_INSPECTION_NUM_FFS: integer := 16; XOR_INSPECTION_NUM_FFS_NB: integer := 5); Port ( Clk : in std_logic; RESET: in std_logic; LFSR_ready: in std_logic; log_n_num_sams: in std_logic_vector(MAX_NUM_SAMS_NB-1 downto 0); num_sams: in std_logic_vector(MAX_NUM_SAMS_NB-1 downto 0); sam_num: out std_logic_vector(MAX_NUM_SAMS_NB-1 downto 0); REBEL_Config_start: out std_logic; REBEL_Config_ready: in std_logic; REBEL_num_result_trans: out std_logic_vector(XOR_INSPECTION_NUM_FFS_NB-1 downto 0); LC_start: out std_logic; LC_ready: in std_logic; start: in std_logic; ready: out std_logic; DEBUG_print_snapshots: in std_logic; DEBUG_snapshot_ready: out std_logic; DEBUG_processor_done_reading: in std_logic; DEBUG_print_PNs: in std_logic; DEBUG_PN_ready: out std_logic; PUT_init_val: in std_logic; target_FF: in std_logic_vector(XOR_INSPECTION_NUM_FFS_NB-1 downto 0); REBEL_QMOuts_Seg: in std_logic_vector(XOR_INSPECTION_NUM_FFS downto 0); LC_phaseshift_request: out std_logic_vector(PHASE_SHIFT_LEN_NB-1 downto 0); TOP_PUFNum: out std_logic_vector(PHASE_SHIFT_LEN_NB-1 downto 0); TOP_PUFNum_valid: out std_logic; TOP_PUFNum_range_threshold: in std_logic_vector(PHASE_SHIFT_LEN_NB-1 downto 0); TOP_PUFNum_range: out std_logic_vector(PHASE_SHIFT_LEN_NB-1 downto 0); TOP_back_or_forward_search: in std_logic; TOP_phase_shift_inc_dec: in std_logic_vector(PHASE_SHIFT_LEN_NB-1 downto 0); TOP_authentication: in std_logic; TOP_enrollment: in std_logic); end LCTest_Base; architecture beh of LCTest_Base is -- Don't allow the lower bound to be smaller than approx. 1.5 ns -- the LaunchCaptureEngine does NOT work for smaller -- values. -- See README.jim, 4/28/2014. OVERFLOW and UNDERFLOW cause bit flips for UNMD and DPNC, so we need to avoid them. The -- synthesis tool only guarantees that the path delays of all paths to the capturing row are less than the clock -- interval. We almost never set the insertion point to 0 but rather require paths to propagate signals along the -- delay chain to targets at 2-6 (each FF is approx. 800-900 ps). So if enrollment is done at room temperature, then -- the shortest and longest paths found valid during enrollment may underflow/overflow at worst case TV conditions. -- To prevent overflow and underflow during regeneration, I restrict the phase shift range to screen out the shortest -- and longest paths in the design to ensure the paths selected can propagate to the target during regeneration. -- Takes 15 clocks to move phase by 1. Each shift is 20ns/1120 = 17.86 ps (at 50 MHz), or 56 ticks/ns. -- Adjusting sweep range to allow regen at different temperature/voltages to work properly. If you use a large -- difference here, it creates lots of 'jumps' at the upper range because regen times paths using a much higher -- starting FPA value. -- constant PHASE_SHIFT_LOWER_BOUND_REGEN: integer := 150; -- constant PHASE_SHIFT_UPPER_BOUND_REGEN: integer := PHASE_SHIFT_RANGE - 50; -- This is the amount the phase shift lower and upper bounds are reduced by during enrollment. -- constant PHASE_SHIFT_ENROLL_LB_REDUCE: integer := 100; -- constant PHASE_SHIFT_ENROLL_HB_REDUCE: integer := 250; -- constant PHASE_SHIFT_LOWER_BOUND_ENROLL: integer := PHASE_SHIFT_LOWER_BOUND_REGEN + PHASE_SHIFT_ENROLL_LB_REDUCE; -- constant PHASE_SHIFT_UPPER_BOUND_ENROLL: integer := PHASE_SHIFT_UPPER_BOUND_REGEN - PHASE_SHIFT_ENROLL_HB_REDUCE; -- Keeping these the same for now -- may have to do something once I start TV experiments to prevent overflow. -- Probably best just to restrict valid upper and lower FPA value for a path and not play with the sweep range. constant PHASE_SHIFT_LOWER_BOUND_REGEN: integer := 250; constant PHASE_SHIFT_UPPER_BOUND_REGEN: integer := PHASE_SHIFT_RANGE - 250; constant PHASE_SHIFT_LOWER_BOUND_ENROLL: integer := PHASE_SHIFT_LOWER_BOUND_REGEN; constant PHASE_SHIFT_UPPER_BOUND_ENROLL: integer := PHASE_SHIFT_UPPER_BOUND_REGEN; type state_type is (idle, wait_LFSRConfig_REBEL, sweep_wait, compute_result, debug_dump_data, debug_dump_data_MS); signal state_reg, state_next: state_type; signal LC_sweep_start, LC_sweep_ready: std_logic; signal ready_reg, ready_next: std_logic; signal target_index_reg, target_index_next: unsigned(XOR_INSPECTION_NUM_FFS_NB-1 downto 0); -- Make this big enough to hold 128 copies (when MAX_NUM_SAMS_NB is 8 -> 0b1000000) of the maximum phase shift -- (PHASE_SHIFT_LEN_NB) signal PUFNum_sum_reg, PUFNum_sum_next: unsigned(PHASE_SHIFT_LEN_NB + MAX_NUM_SAMS_NB - 1 downto 0); signal PUFNum_valid_reg, PUFNum_valid_next: std_logic; signal current_phaseshift_request: std_logic_vector(PHASE_SHIFT_LEN_NB-1 downto 0); signal sam_num_reg, sam_num_next: unsigned(MAX_NUM_SAMS_NB-1 downto 0); signal sam_upper_reg, sam_upper_next: unsigned(PHASE_SHIFT_LEN_NB-1 downto 0); signal sam_lower_reg, sam_lower_next: unsigned(PHASE_SHIFT_LEN_NB-1 downto 0); signal range_fail: std_logic; signal success_flag: std_logic; signal phase_shift_lower_bound: std_logic_vector(PHASE_SHIFT_LEN_NB-1 downto 0); signal phase_shift_upper_bound: std_logic_vector(PHASE_SHIFT_LEN_NB-1 downto 0); signal REBEL_num_result_trans_int: std_logic_vector(XOR_INSPECTION_NUM_FFS_NB-1 downto 0); begin with TOP_enrollment select phase_shift_lower_bound <= std_logic_vector(to_unsigned(PHASE_SHIFT_LOWER_BOUND_ENROLL, PHASE_SHIFT_LEN_NB)) when '1', std_logic_vector(to_unsigned(PHASE_SHIFT_LOWER_BOUND_REGEN, PHASE_SHIFT_LEN_NB)) when others; with TOP_enrollment select phase_shift_upper_bound <= std_logic_vector(to_unsigned(PHASE_SHIFT_UPPER_BOUND_ENROLL, PHASE_SHIFT_LEN_NB)) when '1', std_logic_vector(to_unsigned(PHASE_SHIFT_UPPER_BOUND_REGEN, PHASE_SHIFT_LEN_NB)) when others; -- This module is responsible for sweeping across all LC intervals until an edge propagates to the target FF. LCSweep: entity work.LCSweep(beh) generic map (PHASE_SHIFT_LEN_NB=>PHASE_SHIFT_LEN_NB, PHASE_SHIFT_RANGE=>PHASE_SHIFT_RANGE, PHASE_SHIFT_ZEROPHASE=>PHASE_SHIFT_ZEROPHASE, XOR_INSPECTION_NUM_FFS=>XOR_INSPECTION_NUM_FFS, XOR_INSPECTION_NUM_FFS_NB=>XOR_INSPECTION_NUM_FFS_NB) port map (Clk=>Clk, RESET=>RESET, REBEL_num_result_trans=>REBEL_num_result_trans_int, LC_start=>LC_start, LC_ready=>LC_ready, start=>LC_sweep_start, ready=>LC_sweep_ready, DEBUG_print_snapshots=>DEBUG_print_snapshots, DEBUG_snapshot_ready=>DEBUG_snapshot_ready, DEBUG_processor_done_reading=>DEBUG_processor_done_reading, PUT_init_val=>PUT_init_val, REBEL_QMOuts_Seg=>REBEL_QMOuts_Seg, target_index=>std_logic_vector(target_index_reg), phase_shift_lower_bound=>phase_shift_lower_bound, phase_shift_upper_bound=>phase_shift_upper_bound, back_or_forward_search=>TOP_back_or_forward_search, LC_phaseshift_request=>current_phaseshift_request, phase_shift_inc_dec=>TOP_phase_shift_inc_dec, success_flag=>success_flag, TOP_authentication=>TOP_authentication, TOP_enrollment=>TOP_enrollment); -- State and register logic process(Clk, RESET) begin if (RESET = '1' ) then state_reg <= idle; ready_reg <= '1'; target_index_reg <= (others => '0'); PUFNum_sum_reg <= (others => '0'); PUFNum_valid_reg <= '0'; sam_num_reg <= (others => '0'); sam_upper_reg <= (others => '0'); sam_lower_reg <= (others => '0'); elsif (Clk'event and Clk = '1') then state_reg <= state_next; ready_reg <= ready_next; target_index_reg <= target_index_next; PUFNum_sum_reg <= PUFNum_sum_next; PUFNum_valid_reg <= PUFNum_valid_next; sam_num_reg <= sam_num_next; sam_upper_reg <= sam_upper_next; sam_lower_reg <= sam_lower_next; end if; end process; -- range_fail is set to '1' when the current range is greater than the threshold set by the user and the user has not -- disabled this check by setting it to the max value of 15. range_fail <= '1' when ((sam_upper_next - sam_lower_next) > unsigned(TOP_PUFNum_range_threshold) and unsigned(TOP_PUFNum_range_threshold) /= to_unsigned(15, 4)) else '0'; process (state_reg, ready_reg, start, LC_sweep_ready, LFSR_ready, REBEL_Config_ready, target_FF, target_index_reg, current_phaseshift_request, PUFNum_sum_reg, PUFNum_valid_reg, sam_num_reg, log_n_num_sams, num_sams, success_flag, DEBUG_print_PNs, DEBUG_processor_done_reading, sam_upper_reg, sam_lower_reg, range_fail, REBEL_num_result_trans_int, TOP_authentication, TOP_enrollment) begin state_next <= state_reg; ready_next <= ready_reg; LC_sweep_start <= '0'; REBEL_Config_start <= '0'; target_index_next <= target_index_reg; PUFNum_sum_next <= PUFNum_sum_reg; PUFNum_valid_next <= PUFNum_valid_reg; sam_num_next <= sam_num_reg; sam_upper_next <= sam_upper_reg; sam_lower_next <= sam_lower_reg; DEBUG_PN_ready <= '0'; case state_reg is -- ===================== when idle => -- For each pulse of the start signal, carry out a LC test (LaunchCaptureEngine) if ( start = '1' ) then ready_next <= '0'; -- Compute the relative target FF index in REBEL row based on the current insertion point. This is used in LCSweep to decide -- when we've met the edge propagation requirement. The insertion point is ALWAYS at position 16. If the target_FF is 4, -- then we want to set the target_index to 16-4 = 12. target_index_next <= XOR_INSPECTION_NUM_FFS - unsigned(target_FF); -- Note: The REBEL controller must be in idle so no need to check. REBEL_Config_start <= '1'; -- Initialize the valid PUFNum flag to 1 and the number of samples to 0. Initialize the PUFNum to zero since we keep adding samples -- to it for averaging. PUFNum_valid_next <= '1'; sam_num_next <= (others => '0'); PUFNum_sum_next <= (others => '0'); state_next <= wait_LFSRConfig_REBEL; end if; -- ===================== -- LFSR started in LCTest_Driver, REBEL controller started above -- wait for both ready signals to be asserted. when wait_LFSRConfig_REBEL => if ( LFSR_ready = '1' and REBEL_Config_ready = '1' ) then -- LC Sweep must be ready -- no need to check LC_sweep_start <= '1'; state_next <= sweep_wait; end if; -- ===================== -- Wait for the sweep to finish when sweep_wait => -- For backward search, we start with long LC tests (which allows the edge to enter the scan chain easily) and work -- backwards, pushing the edge backwards toward the target FF -- stop when the edge is pushed JUST before the target FF. -- For the forward/backward search, we exit the first phase (forward search) when the edge just appears in the target FF -- (assuming there is only one transition). if ( LC_sweep_ready = '1' ) then PUFNum_sum_next <= PUFNum_sum_reg + resize(unsigned(current_phaseshift_request), PHASE_SHIFT_LEN_NB + MAX_NUM_SAMS_NB); sam_num_next <= sam_num_reg + 1; -- Record the range of values across multiple samples. if ( sam_num_reg = 0 ) then sam_lower_next <= unsigned(current_phaseshift_request); sam_upper_next <= unsigned(current_phaseshift_request); elsif ( sam_lower_reg > unsigned(current_phaseshift_request) ) then sam_lower_next <= unsigned(current_phaseshift_request); elsif ( sam_upper_reg < unsigned(current_phaseshift_request) ) then sam_upper_next <= unsigned(current_phaseshift_request); end if; -- 'success_flag' is set to '0' by LCSweep when 1) we have more than 1 transistion during enrollment, 2) we reached the last -- possible phase shift (UNDERFLOW), 3) we find the initial value in the target (the termination condition) but there are -- no transitions or 4) we have a single transistion but we return with the max phase shift value (which indicates that the -- transistion has not made it to the target on the first trial of the sweep) -- OVERFLOW, and 5) when the range of the samples -- exceeds the threshold. All these are considered 'fail cases' during enrollment. Regeneration has NO fail cases (value is -- always valid). Authentication should set valid to '0' when there are no transitions or an even number of them. if ( ((success_flag = '0' or range_fail = '1') and TOP_enrollment = '1') or (REBEL_num_result_trans_int(0) = '0' and TOP_authentication = '1') ) then -- For DEBUG -- clear the PUFNum if it becomes invalid during any of the samples. PUFNum_sum_next <= (others => '0'); PUFNum_valid_next <= '0'; state_next <= debug_dump_data; -- If only 1 sample requested, we're done. Zero samples is invalid but handled. Number of samples MUST be a power of 2. elsif ( unsigned(num_sams) <= 1 ) then state_next <= debug_dump_data; -- If we collected all samples, then compute average. elsif ( sam_num_reg = unsigned(num_sams) - 1 ) then state_next <= compute_result; -- Otherwise, keep getting samples. REBEL row encoding values are preserved so just start another LCSweep. else state_next <= debug_dump_data_MS; end if; end if; -- ===================== -- Multiple samples requested. Compute average by right shifting (division), which depends on num_sams being a power of 2. when compute_result => PUFNum_sum_next <= PUFNum_sum_reg srl to_integer(unsigned(log_n_num_sams)); state_next <= debug_dump_data; -- ===================== -- DEBUG: Allow processor to fetch PN. Signal processor that data is ready to be ready with DEBUG_PN_ready. Wait for processor to -- indicate that it has read the data. when debug_dump_data => if ( DEBUG_print_PNs = '1' ) then DEBUG_PN_ready <= '1'; if ( DEBUG_processor_done_reading = '1' ) then ready_next <= '1'; state_next <= idle; end if; else ready_next <= '1'; state_next <= idle; end if; -- ===================== -- DEBUG: special case with multiple samples when debug_dump_data_MS => if ( DEBUG_print_PNs = '1' ) then DEBUG_PN_ready <= '1'; if ( DEBUG_processor_done_reading = '1' ) then state_next <= wait_LFSRConfig_REBEL; end if; else state_next <= wait_LFSRConfig_REBEL; end if; end case; end process; ready <= ready_reg; sam_num <= std_logic_vector(sam_num_reg); REBEL_num_result_trans <= REBEL_num_result_trans_int; TOP_PUFNum <= std_logic_vector(PUFNum_sum_reg(PHASE_SHIFT_LEN_NB-1 downto 0)); TOP_PUFNum_valid <= PUFNum_valid_reg; LC_phaseshift_request <= current_phaseshift_request; TOP_PUFNum_range <= std_logic_vector(sam_upper_reg - sam_lower_reg); end beh;