-- EMACS settings: -*-  tab-width: 2; indent-tabs-mode: t -*-
-- vim: tabstop=2:shiftwidth=2:noexpandtab
-- kate: tab-width 2; replace-tabs off; indent-width 2;
-- =============================================================================
-- Authors:					Patrick Lehmann
--
-- Package:					Simulation constants, functions and utilities.
--
-- Description:
-- 
-- .. TODO:: No documentation available.
--
-- License:
-- =============================================================================
-- Copyright 2007-2016 Technische Universitaet Dresden - Germany
--										 Chair of VLSI-Design, Diagnostics and Architecture
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--		http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- =============================================================================

use			STD.TextIO.all;

library IEEE;
use			IEEE.STD_LOGIC_1164.all;

library PoC;
use			PoC.utils.all;
use			PoC.strings.all;
use			PoC.vectors.all;
use			PoC.physical.all;

use			PoC.sim_types.all;
use			PoC.sim_global.all;


package [docs]sim_unprotected is
  -- Simulation Task and Status Management
	-- ===========================================================================
	-- Initializer and Finalizer
	procedure				[docs]initialize(MaxAssertFailures : natural := natural'high; MaxSimulationRuntime : TIME := TIME'high);
	procedure				[docs]finalize;

	-- Assertions
	procedure				[docs]fail(Message : string := "");
	procedure				[docs]assertion(Condition : boolean; Message : string := "");
	procedure				[docs]writeMessage(Message : string);
	procedure				[docs]writeReport;

	-- Process Management
	impure function	[docs]registerProcess(Name : string; IsLowPriority : boolean := FALSE) return T_SIM_PROCESS_ID;
	impure function [docs]registerProcess(TestID : T_SIM_TEST_ID; Name : string; IsLowPriority : boolean := FALSE) return T_SIM_PROCESS_ID;
	procedure				[docs]deactivateProcess(procID : T_SIM_PROCESS_ID);
	procedure				[docs]stopAllProcesses;
	procedure				[docs]stopProcesses(TestID	: T_SIM_TEST_ID := C_SIM_DEFAULT_TEST_ID);

	-- Test Management
	procedure				[docs]createDefaultTest;
	impure function [docs]createTest(Name : string) return T_SIM_TEST_ID;
	procedure				[docs]activateDefaultTest;
	impure function	[docs]finalizeDefaultTest return boolean;
	procedure				[docs]finalizeTest(TestID : T_SIM_TEST_ID);

	-- Clock Management
	procedure				[docs]stopAllClocks;
	procedure				[docs]stopClocks(TestID		: T_SIM_TEST_ID := C_SIM_DEFAULT_TEST_ID);

	impure function	[docs]isStopped(TestID		: T_SIM_TEST_ID := C_SIM_DEFAULT_TEST_ID)	return boolean;
	impure function [docs]isFinalized(TestID	: T_SIM_TEST_ID := C_SIM_DEFAULT_TEST_ID)	return boolean;
	impure function [docs]isAllFinalized return boolean;
end package;


package body sim_unprotected is

	-- Simulation process and Status Management
	-- ===========================================================================
	procedure init is
	begin
		if not globalSim_StateIsInitialized then
			if C_SIM_VERBOSE then		report "init:" severity NOTE;			end if;
			globalSim_StateIsInitialized		:= TRUE;
			createDefaultTest;
		end if;
	end procedure;

	procedure initialize(MaxAssertFailures : natural := natural'high; MaxSimulationRuntime : TIME := TIME'high) is
	begin
		if C_SIM_VERBOSE then		report "initialize:" severity NOTE;			end if;
		init;
		globalSim_MaxAssertFailures			:= MaxAssertFailures;
		globalSim_MaxSimulationRuntime	:= MaxSimulationRuntime;
		-- if (MaxSimulationRuntime /= TIME'high) then
			-- wait until (globalSim_StateIsFinalized = TRUE) for MaxSimulationRuntime;
			-- report "initialize: TIMEOUT" severity ERROR;
			-- finalize;
		-- end if;
	end procedure;

	procedure finalize is
		variable Dummy	: boolean;
	begin
		if not globalSim_StateIsFinalized then
			if C_SIM_VERBOSE then		report "finalize: " severity NOTE;		end if;
			globalSim_StateIsFinalized		:= TRUE;
			for i in 0 to globalSim_TestCount - 1 loop
				finalizeTest(i);
			end loop;
			Dummy		:= finalizeDefaultTest;
			writeReport;
		end if;
	end procedure;

	procedure writeReport_Header is
		variable LineBuffer : LINE;
	begin
		write(LineBuffer,		(			string'("========================================")));
		write(LineBuffer,		(LF & string'("POC TESTBENCH REPORT")));
		write(LineBuffer,		(LF & string'("========================================")));
		writeline(output, LineBuffer);
	end procedure;

	procedure writeReport_TestReport(Prefix : string := "") is
		variable LineBuffer : LINE;
	begin
		if (globalSim_Tests(C_SIM_DEFAULT_TEST_ID).Status /= SIM_TEST_STATUS_CREATED) then
			write(LineBuffer,				 Prefix & "Tests          " & integer'image(globalSim_TestCount + 1));
			write(LineBuffer,		LF & Prefix & " " & str_ralign("-1", log10ceilnz(globalSim_TestCount + 1) + 1) & ": " & C_SIM_DEFAULT_TEST_NAME);
		else
			write(LineBuffer,				 Prefix & "Tests          " & integer'image(globalSim_TestCount));
		end if;
		for i in 0 to globalSim_TestCount - 1 loop
			write(LineBuffer,		LF & Prefix & "  " & str_ralign(integer'image(i), log10ceilnz(globalSim_TestCount)) & ": " & str_trim(globalSim_Tests(i).Name));
		end loop;
		writeline(output, LineBuffer);
	end procedure;

	procedure writeReport_AssertReport(Prefix : string := "") is
		variable LineBuffer : LINE;
	begin
		write(LineBuffer,					 Prefix & "Assertions   " & integer'image(globalSim_AssertCount));
		write(LineBuffer,			LF & Prefix & "  failed     " & integer'image(globalSim_FailedAssertCount) & ite((globalSim_FailedAssertCount >= globalSim_MaxAssertFailures), " Too many failed asserts!", ""));
		writeline(output, LineBuffer);
	end procedure;

	procedure writeReport_ProcessReport(Prefix : string := "") is
		variable LineBuffer : LINE;
	begin
		write(LineBuffer,					 Prefix & "Processes    " & integer'image(globalSim_ProcessCount));
		write(LineBuffer,			LF & Prefix & "  active     " & integer'image(globalSim_ActiveProcessCount));
		-- report killed processes
		for i in 0 to globalSim_ProcessCount - 1 loop
			if ((globalSim_Processes(i).Status = SIM_PROCESS_STATUS_ACTIVE) and (globalSim_Processes(i).IsLowPriority = FALSE)) then
				write(LineBuffer,	LF & Prefix & "    " & str_ralign(integer'image(i), log10ceilnz(globalSim_ProcessCount)) & ": " & str_trim(globalSim_Processes(i).Name));
			end if;
		end loop;
		writeline(output, LineBuffer);
	end procedure;

	procedure writeReport_RuntimeReport(Prefix : string := "") is
		variable LineBuffer : LINE;
	begin
		write(LineBuffer,					 Prefix & "Runtime      " & to_string(now, 1));
		writeline(output, LineBuffer);
	end procedure;

	procedure writeReport_SimulationResult is
		variable LineBuffer : LINE;
	begin
		write(LineBuffer,																				(			string'("========================================")));
		if globalSim_AssertCount = 0 then	  write(LineBuffer, (LF & string'("SIMULATION RESULT = NO ASSERTS")));
		elsif globalSim_Passed then  write(LineBuffer, (LF & string'("SIMULATION RESULT = PASSED")));
		else										 						 	write(LineBuffer, (LF & string'("SIMULATION RESULT = FAILED")));
		end if;
		write(LineBuffer,																				(LF & string'("========================================")));
		writeline(output, LineBuffer);
	end procedure;

	procedure writeReport is
		variable LineBuffer : LINE;
	begin
		writeReport_Header;
		writeReport_TestReport("");
		write(LineBuffer, LF & "Overall");
		writeline(output, LineBuffer);
		writeReport_AssertReport("  ");
		writeReport_ProcessReport("  ");
		writeReport_RuntimeReport("  ");
		writeReport_SimulationResult;
	end procedure;

	procedure assertion(condition : boolean; Message : string := "") is
	begin
		globalSim_AssertCount := globalSim_AssertCount + 1;
		if not condition then
			fail(Message);
			globalSim_FailedAssertCount := globalSim_FailedAssertCount + 1;
			if (globalSim_FailedAssertCount >= globalSim_MaxAssertFailures) then
				stopAllProcesses;
			end if;
		end if;
	end procedure;

	procedure fail(Message : string := "") is
	begin
		if (Message'length > 0) then
			report Message severity ERROR;
		end if;
		globalSim_Passed := FALSE;
	end procedure;

	procedure writeMessage(Message : string) is
		variable LineBuffer : LINE;
	begin
		write(LineBuffer, Message);
		writeline(output, LineBuffer);
	end procedure;

	procedure createDefaultTest is
		variable Test							: T_SIM_TEST;
	begin
		if not globalSim_StateIsInitialized then
			init;
		end if;
		if C_SIM_VERBOSE then		report "createDefaultTest(" & C_SIM_DEFAULT_TEST_NAME & "): => " & T_SIM_TEST_ID'image(C_SIM_DEFAULT_TEST_ID) severity NOTE;		end if;
		Test.ID										:= C_SIM_DEFAULT_TEST_ID;
		Test.Name									:= resize(C_SIM_DEFAULT_TEST_NAME, T_SIM_TEST_NAME'length);
		Test.Status								:= SIM_TEST_STATUS_CREATED;
		Test.ProcessIDs						:= (others => 0);
		Test.ProcessCount					:= 0;
		Test.ActiveProcessCount		:= 0;
		-- add to the internal structure
		globalSim_Tests(Test.ID)	:= Test;
	end procedure;

	impure function createTest(Name : string) return T_SIM_TEST_ID is
		variable Test							: T_SIM_TEST;
	begin
		if not globalSim_StateIsInitialized then
			init;
		end if;
		if C_SIM_VERBOSE then		report "createTest(" & Name & "): => " & T_SIM_TEST_ID'image(globalSim_TestCount) severity NOTE;		end if;
		Test.ID										:= globalSim_TestCount;
		Test.Name									:= resize(Name, T_SIM_TEST_NAME'length);
		Test.Status								:= SIM_TEST_STATUS_ACTIVE;
		Test.ProcessIDs						:= (others => 0);
		Test.ProcessCount					:= 0;
		Test.ActiveProcessCount		:= 0;
		-- add to the internal structure
		globalSim_Tests(Test.ID)	:= Test;
		globalSim_TestCount				:= globalSim_TestCount + 1;
		globalSim_ActiveTestCount	:= globalSim_ActiveTestCount + 1;
		-- return TestID for finalizeTest
		return Test.ID;
	end function;

	procedure activateDefaultTest is
	begin
		if (globalSim_Tests(C_SIM_DEFAULT_TEST_ID).Status = SIM_TEST_STATUS_CREATED) then
			globalSim_Tests(C_SIM_DEFAULT_TEST_ID).Status := SIM_TEST_STATUS_ACTIVE;
			globalSim_ActiveTestCount											:= globalSim_ActiveTestCount + 1;
		end if;
	end procedure;

	impure function finalizeDefaultTest return boolean is
	begin
		if (globalSim_Tests(C_SIM_DEFAULT_TEST_ID).Status = SIM_TEST_STATUS_CREATED) then
			if C_SIM_VERBOSE then		report "finalizeDefaultTest: inactive" severity NOTE;		end if;
			globalSim_Tests(C_SIM_DEFAULT_TEST_ID).Status	:= SIM_TEST_STATUS_ENDED;
			stopProcesses(C_SIM_DEFAULT_TEST_ID);
			return TRUE;
		elsif (globalSim_Tests(C_SIM_DEFAULT_TEST_ID).Status = SIM_TEST_STATUS_ACTIVE) then
			if C_SIM_VERBOSE then		report "finalizeDefaultTest: active" severity NOTE;		end if;
			globalSim_Tests(C_SIM_DEFAULT_TEST_ID).Status	:= SIM_TEST_STATUS_ENDED;
			globalSim_ActiveTestCount											:= globalSim_ActiveTestCount - 1;
			stopProcesses(C_SIM_DEFAULT_TEST_ID);
			if globalSim_ActiveTestCount = 0 then
				finalize;
			end if;
			return TRUE;
		end if;
		return FALSE;
	end function;

	procedure finalizeTest(TestID : T_SIM_TEST_ID) is
		variable Dummy	: boolean;
	begin
		if TestID = C_SIM_DEFAULT_TEST_ID then
			if globalSim_ActiveTestCount = 1 then
				if finalizeDefaultTest then
					finalize;
				end if;
			end if;
		elsif TestID < globalSim_TestCount then
			if (globalSim_Tests(TestID).Status /= SIM_TEST_STATUS_ENDED) then
				if C_SIM_VERBOSE then		report "finalizeTest(TestID=" & T_SIM_TEST_ID'image(TestID) & "): " severity NOTE;		end if;
				globalSim_Tests(TestID).Status	:= SIM_TEST_STATUS_ENDED;
				globalSim_ActiveTestCount				:= globalSim_ActiveTestCount - 1;
				stopProcesses(TestID);

				if globalSim_ActiveTestCount = 0 then
					finalize;
				elsif globalSim_ActiveTestCount = 1 then
					if finalizeDefaultTest then
						finalize;
					end if;
				end if;
			end if;
		else
			report "TestID (" & T_SIM_TEST_ID'image(TestID) & ") is unknown." severity FAILURE;
		end if;
	end procedure;

	impure function registerProcess(Name : string; IsLowPriority : boolean := FALSE) return T_SIM_PROCESS_ID is
	begin
		return registerProcess(C_SIM_DEFAULT_TEST_ID, Name, IsLowPriority);
	end function;

	impure function registerProcess(TestID : T_SIM_TEST_ID; Name : string; IsLowPriority : boolean := FALSE) return T_SIM_PROCESS_ID is
		variable Proc						: T_SIM_PROCESS;
		variable TestProcID			: T_SIM_TEST_ID;
	begin
		if not globalSim_StateIsInitialized then
			init;
		end if;
		if TestID = C_SIM_DEFAULT_TEST_ID then
			activateDefaultTest;
		end if;
		if TestID < globalSim_TestCount then
			if C_SIM_VERBOSE then		report "registerProcess(TestID=" & T_SIM_TEST_ID'image(TestID) & ", " & Name & "): => " & T_SIM_PROCESS_ID'image(globalSim_ProcessCount) severity NOTE;		end if;
			Proc.ID									:= globalSim_ProcessCount;
			Proc.TestID							:= TestID;
			Proc.Name								:= resize(Name, T_SIM_PROCESS_NAME'length);
			Proc.Status							:= SIM_PROCESS_STATUS_ACTIVE;
			Proc.IsLowPriority			:= IsLowPriority;

			-- add process to list
			globalSim_Processes(Proc.ID)	:= Proc;
			globalSim_ProcessCount				:= globalSim_ProcessCount + 1;
			globalSim_ActiveProcessCount	:= inc_if(not IsLowPriority, globalSim_ActiveProcessCount);
			-- add process to test
			TestProcID																			:= globalSim_Tests(TestID).ProcessCount;
			globalSim_Tests(TestID).ProcessIDs(TestProcID)	:= Proc.ID;
			globalSim_Tests(TestID).ProcessCount						:= TestProcID + 1;
			globalSim_Tests(TestID).ActiveProcessCount			:= inc_if(not IsLowPriority, globalSim_Tests(TestID).ActiveProcessCount);
			-- return the process ID
			return Proc.ID;
		else
			report "TestID (" & T_SIM_TEST_ID'image(TestID) & ") is unknown." severity FAILURE;
			return T_SIM_PROCESS_ID'high;
		end if;
	end function;

	procedure deactivateProcess(ProcID : T_SIM_PROCESS_ID) is
		variable TestID		: T_SIM_TEST_ID;
	begin
		if ProcID < globalSim_ProcessCount then
			TestID	:= globalSim_Processes(ProcID).TestID;
			-- deactivate process
			if (globalSim_Processes(ProcID).Status = SIM_PROCESS_STATUS_ACTIVE) then
				if C_SIM_VERBOSE then		report "deactivateProcess(ProcID=" & T_SIM_PROCESS_ID'image(ProcID) & "): TestID=" & T_SIM_TEST_ID'image(TestID) & "  Name=" & str_trim(globalSim_Processes(ProcID).Name) severity NOTE;		end if;
				globalSim_Processes(ProcID).Status					:= SIM_PROCESS_STATUS_ENDED;
				globalSim_ActiveProcessCount								:= dec_if(not globalSim_Processes(ProcID).IsLowPriority, globalSim_ActiveProcessCount);
				globalSim_Tests(TestID).ActiveProcessCount	:= dec_if(not globalSim_Processes(ProcID).IsLowPriority, globalSim_Tests(TestID).ActiveProcessCount);
				if (globalSim_Tests(TestID).ActiveProcessCount = 0) then
					finalizeTest(TestID);
				end if;
			end if;
		else
			report "ProcID (" & T_SIM_PROCESS_ID'image(ProcID) & ") is unknown." severity FAILURE;
		end if;
	end procedure;

	procedure stopAllProcesses is
	begin
		if C_SIM_VERBOSE then		report "stopAllProcesses:" severity NOTE;		end if;
		for i in C_SIM_DEFAULT_TEST_ID to globalSim_TestCount - 1 loop
			stopProcesses(i);
		end loop;
	end procedure;

	procedure stopProcesses(TestID : T_SIM_TEST_ID := C_SIM_DEFAULT_TEST_ID) is
	begin
		if TestID < globalSim_TestCount then
			if C_SIM_VERBOSE then		report "stopProcesses(TestID=" & T_SIM_TEST_ID'image(TestID) & "): Name=" & str_trim(globalSim_Tests(TestID).Name) severity NOTE;		end if;
			globalSim_MainProcessEnables(TestID)	:= FALSE;
			stopClocks(TestID);
		else
			report "TestID (" & T_SIM_TEST_ID'image(TestID) & ") is unknown." severity FAILURE;
		end if;
	end procedure;

	procedure stopAllClocks is
	begin
		if C_SIM_VERBOSE then		report "stopAllClocks:" severity NOTE;		end if;
		for i in C_SIM_DEFAULT_TEST_ID to globalSim_TestCount - 1 loop
			stopClocks(i);
		end loop;
	end procedure;

	procedure stopClocks(TestID : T_SIM_TEST_ID := C_SIM_DEFAULT_TEST_ID) is
	begin
		if TestID < globalSim_TestCount then
			if C_SIM_VERBOSE then		report "stopClocks(TestID=" & T_SIM_TEST_ID'image(TestID) & "): Name=" & str_trim(globalSim_Tests(TestID).Name) severity NOTE;		end if;
			globalSim_MainClockEnables(TestID)		:= FALSE;
		else
			report "TestID (" & T_SIM_TEST_ID'image(TestID) & ") is unknown." severity FAILURE;
		end if;
	end procedure;

	impure function isStopped(TestID : T_SIM_TEST_ID := C_SIM_DEFAULT_TEST_ID) return boolean is
	begin
		return not globalSim_MainClockEnables(TestID);
	end function;

	impure function isFinalized(TestID : T_SIM_TEST_ID := C_SIM_DEFAULT_TEST_ID) return boolean is
	begin
		return (globalSim_Tests(TestID).Status = SIM_TEST_STATUS_ENDED);
	end function;

	impure function isAllFinalized return boolean is
	begin
		return (globalSim_ActiveTestCount = 0);
	end function;
end package body;