library ieee;
use ieee.std_logic_1164.all;

/**
 * rules for port groups
 * - Simple abstraction over port lists
 *   - idea: a port group contains port declarations; port declarations can contain port groups
 *   - you can only make a group by composing them out of signals, ports and/or other port groups
 *   - you can't declare a signal/variable as group
 *     - signal s : group_a; -- Not allowed, signal declarations don't have port directions
 *   - port group declarations can only be used in port lists and in subprogram parameter lists
 *   - in subprogram parameter lists they can be used as reusable conversion functions for groups
 *     - they can also be used to merge multiple port groups into 1 port group
 *     - they can be used to decompose a port group in a smaller port group
 *     - these run @ elaboration time 
 *   - groups have direction & this makes it hard for them to be a data type
 *     - data types compose
 *       - if we define port groups as a new composite type you need to support
 *         - arrays of port groups
 *         - records with port group fields
 *         - access types with port group
 *         - etc
 *       - makes no sense => port directions don't compose!
 *   - individual elements can be used in signal assignment
 *     - group_a.element <= ...
 *     - some_signal <= group_b.subgroup.z(1);
 *   - usage in port association list instantiations
 *     - direct assignment: group_a => group_a
 *     - through aggregate: (a, b) => group_a or group_a => (a, b)
 *     - with conversion functions: convF(group_a) => group_b or group_a => convF(group_b)
 *     - with advanced conversion functions: convF(group_a, 3) => group_b or group_a => convF(group_b, 3)
 *       - these conversion functions must be able to run during elaboration 
 *         so the extra parameters must be constant at elaboration time 
 * 
 * - Generic groups could be considered although record types cover most of what can be achieved
 *   - Issue: records can't hold packages or types
 *   - Solution: I believe this issue can be avoided by having the generics on the package that defines the port group & 
 *     passing that package around as a generic on the entity
 * 
 * - Extensions
 *   - It may make sense to support some 'port group instance' declaration to avoid repetition in some cases
 *     - If the designer feels like he needs a 'port group signal' (those make no sense btw)
 *   - Convenience attributes
 *     - port_group'inverse => inverted port_group, in->out, out->in, inout->inout
 *       - if the port group contains an element with the mode 'buffer' this will result in a compilation error
 *       - nested port groups are inverted with the same rules
 *     - port_group'record_type => create record out of a port group
 *     - port_group'(in|out|inout|buffer)_ports => bundle ports of a certain mode
 *       - usage foo_rec <= port_group'in_ports; 
 *   - Implicit record conversion
 *     - allow to assign a signal of a record type to a port group if the individual elements & types are the same 
 */

/*
 * - Prototype implementation remarks
 *   - Minimal grammar change
 *     - Added port_group_declaration as a declarative item in package_declaration
 *     - Added group_declaration to port_declaration
 *     - Added group_declaration to function arguments
 *   - Aggregate assignment for groups is more complex
 *     - similar to records but you need to check directions 
 *   - General port-mode validation is more complex
 *     - x.y <= a.b used to purely rely on x & a, now you need to take groups into account
 *     - port mode validation inside aggregates
 *   - Prototype issues
 *     - what is the signature of a subprogram with a port group?
 *       - current suggestion procedure foo(group g: some_group) is equivalent to procedure foo(signal a, b : in std_logic; signal c: out std_logic)
 *       - so the arguments are "exploded" in the argument list
 *       - return type is weird, may need a special 'return port group' syntax
 *   - Do we need group arrays? We probably do.
 *     - proposal: group a, b : simple_group(0 to 10, ..)
 *     - you don't need to predefine the array 
 *     - indexing with an integer subtype
 *     - full array size must be known at elaboration time
 */

package example_package is

	port group simple_group is
		-- port declarations (same rules as port declaration in entity, component)
		a, b : in std_logic := '1';
		c, d : out std_logic;
	end port group;
	
	port group simple_alt is
		-- prefixing a port declaration with signal has always been legal
		signal a, b : in std_logic := '1';
		signal c, d : out std_logic;
	end port group;

	port group nested_group is
		-- group declarations in port lists are preceded by the 'group' keyword
		group a, b : simple_group;
	end port group;
	
	component example_component
		port(
			clk     : in std_logic;
			rst     : in std_logic;
			group s : simple_group
		);
	end component example_component;
	
	-- subprograms can use groups
	procedure select_a(group s : simple_group; signal x : out std_logic);
	
	-- subprogram can be used to combine groups into bigger groups
	-- maybe we need special syntax in this case?
	-- subprogram selection is based on base types but port groups don't have types :s
	-- note: can we have functions with port maps?
	function combine1(group s1, s2 : simple_group) return /* group */ nested_group;
	function combine2(
		group s1 : simple_group; a, b : in std_logic;  c, d : out std_logic
	) return nested_group;
	
	-- you can invert the port directions of a group
	alias simple_group_inverted is simple_group'inverse;
	
	procedure assign(group s1 : simple_group; group s2 : simple_group'inverse);
		
	port group simple_sub_group is
		a : in std_logic;
		c : out std_logic;
	end port group;
	
	function select_slave(group master : simple_group; idx : integer) return simple_sub_group;
		
end package example_package;

package body example_package is
	
	procedure select_a(group s : simple_group; signal x : out std_logic) is
	begin
		x <= s.a;
	end;
	
	function combine(group s1, s2 : simple_group) return nested_group is
	begin
		return (s1, s2);
	end;
	
	procedure assign(group s1 : simple_group; group s2 : simple_group'inverse) is
	begin
		s2 <= s1;
	end;
	
	function select_slave(group master : simple_group; idx : integer) return simple_sub_group is
	begin
		case idx is
			when 0 => return (master.a, master.c);
			when 1 => return (master.b, master.d);
		end case;
	end;

end package body example_package;

library ieee;
use ieee.std_logic_1164.all;
use work.example_package.all;

entity example_entity is
	port(
		clk     : in std_logic;
		rst     : in std_logic;
		group s : simple_group
	);
end entity example_entity;

library ieee;
use ieee.std_logic_1164.all;
use work.example_package.all;

entity example_entity_inverse is
	port(
		clk     : in std_logic;
		rst     : in std_logic;
		group s : simple_group'inverse
	);
end entity example_entity_inverse;

library ieee;
use ieee.std_logic_1164.all;

entity top is end;
	
architecture example_instantiation1 of top is
	signal clk, rst : std_logic;
	signal a, b, c, d: std_logic;
	signal vec: std_logic_vector(3 downto 0);
begin
	e1 : entity work.example_entity
		port map(
			clk => clk,
			rst => rst,
			s   => (a, b, c, d)
		);
		
	e2 : entity work.example_entity
		port map(
			clk => clk,
			rst => rst,
			s.a => a,
			s.b => b,
			s.c => c,
			s.d => d
		);
		
	e3 : entity work.example_entity
		port map(
			clk => clk,
			rst => rst,
			s   => (vec(0), vec(1), vec(2), vec(3))
		);
end architecture example_instantiation1;

library ieee;
use ieee.std_logic_1164.all;
use work.example_package.all;

entity nested is
	port (
		clk     : in std_logic;
		rst     : in std_logic
	);
end;

architecture example of nested is
	signal a, b, c, d: std_logic;
begin
	
	-- inst.s is internally connected to inst_inv.s
	inst : entity work.example_entity
		port map(
			clk => clk,
			rst => rst,
			s   => (a, b, c, d)
		);
	
	inst_inv : entity work.example_entity_inverse
		port map(
			clk => clk,
			rst => rst,
			s   => (a, b, c, d)
		);
		
end architecture example;

library ieee;
use ieee.std_logic_1164.all;
use work.example_package.all;

entity slaves is
	port (
		clk     : in std_logic;
		rst     : in std_logic;
		group s : simple_group
	);
end;

architecture example of slaves is
	component slave_comp is
		port(
			clk     : in std_logic;
			rst     : in std_logic;
			group s : simple_sub_group
		);
	end component;
begin
	generate_label : for i in 0 to 1 generate
		inst : component slave_comp
			port map(
				clk => clk,
				rst => rst,
				s   => select_slave(s, i)
			);
	end generate generate_label;
end;