entity EntityName
{
Entity_contents;
}
THDL++ entities use the same semantics as VHDL entities. They can contain ports, signals, explicit/implicit processes and instances of other entities (for more details, see the following pages:
entity Inverter
{
typedef logic[8] DataType;
port in DataType X;
port out DataType Y;
Y = ~X;
}
entity Something
{
signal Inverter.DataType someSig;
Inverter uut(
X = someSig,
Y = auto
);
}
Here the signal someSig was declared with a type "DataType as declared in Inverter". Such constructs allow describing type dependencies explicitly and making the code more generic. Moreover, it is possible to describe a more generic construct "DataType as declared in the entity used to instantiate uut":
signal typeof(uut).DataType someSig;
For more information, see the Unlike VHDL, the order of declarations inside an entity can be arbitrary and will be resolved via two-pass compilation. E.g. the following code is correct:
entity Test
{
port in DataType X;
typedef logic[8] DataType;
}
However, it is recommended to put the declarations before the code using them to improve readability.
THDL++ entities can be made generic using the
template <Type1 arg1, ..., TypeN argN = defaultN> entity Something
{
}
You can use entity inheritance. All contents of the parent entity will be copied to the child one during VHDL generation. The parent entity can be explicitly referenced using "base" keyword.
entity Parent {}
entity Child : Parent
{
...
}
EntityName InstanceName(
port1 = assignment1,
port2 = assignment2,
...,
portN = assignmentN);
The port assignment here is similar to VHDL port map. However, THDL++ offers one timesaving extension to that model. Assume you want to create 2 instances and connect them through a signal:
entity Delayer
{
port in Inverter.DataType X;
port out Inverter.DataType Y;
signal Inverter.DataType tmp;
Inverter inv1(
X = X,
Y = tmp
);
Inverter inv2(
X = tmp,
Y = Y
);
}
To connect the input of inv2 to the output of inv1 you had to define a signal tmp and specify it in both port maps. This is a typical solution for VHDL, however increasing the amount of instances would make it more complicated. In THDL++ you can simplify the code by removing the "tmp" signal and using the "auto" keyword in the port map:
Inverter inv1(
X = X,
Y = auto
);
Inverter inv2(
X = inv1.Y,
Y = Y
);
When THDL++ compiler encounters the "auto" keyword in the port map of an instance "inst" it automatically creates a new local signal and connects it to the corresponding port. Other instances and processes can refer to the signal as "inst.PortName".
VHDL signals can have initial values. To provide an initial value for an "auto" signal, specify it in brackets. See examples for more details.
entity Multiplier
{
port
{
in logic[8] A, B;
out logic[8] Y;
}
Y = A * B;
}
entity Squarer
{
port
{
in logic[8] X;
out logic[8] Y;
}
Multiplier mul(
A = X,
B = X,
Y = Y
);
}
In this example, 1 line describes the actual behavior of the "Multiplier" entity and 4 more lines describe the entity itself and the ports. This can be simplified using the compact entity declaration syntax:
entity Multiplier(in logic[8] A, in logic[8] B, out logic[8] Y)
{
Y = A * B;
}
entity Squarer(in logic[8] X, out logic[8] Y)
{
Multiplier mul(X, X, Y);
}
The generated VHDL code will be the same for both examples. Note that you can also use normal port maps (port = assignment) while instantiating entities declared with compact syntax. However, please avoid using this syntax for complex entities with long port lists, where doing so will decrease readability.
You can also declare entities inside other entities. This only affects the naming used in instantiation (Entity1.Subentity2) and does not automatically cause any instantiation.
Note that THDL++ does not have an architecture statement. The entity contents should be declared directly inside the entity block. When generating VHDL, one architecture block will be generated for every compiled entity.
template<any _Type> entity AddOne
{
port in _Type I;
port out _Type O;
O = I + 1;
}
generate_vhdl(AddOne<logic[8]>);
generate_vhdl(AddOne<int>);
This code will generate 2 VHDL entities. One for std_logic_vector:
entity Add_One_logic_8 is
Port (
I : in std_logic_vector(7 downto 0);
O : out std_logic_vector(7 downto 0)
);
end entity Add_One_logic_8;
architecture Behavioral of Add_One_logic_8 is
begin
O <= (I + X"01");
end architecture Behavioral;
And another one for integer:
entity Add_One_int is
Port (
I : in integer;
O : out integer
);
end entity Add_One_int;
architecture Behavioral of Add_One_int is
begin
O <= (I + 1);
end architecture Behavioral;
template<any _Type> entity OneInputBase
{
port in _Type X;
port out _Type Y;
}
template <any _Type> entity Inverter : OneInputBase<_Type>
{
Y = ~X;
}
generate_vhdl(Inverter<logic[8]>);
When you compile the code to VHDL, the contents of OneInputBase will be copied to Inverter:
entity Inverter_logic_8 is
Port (
X : in std_logic_vector(7 downto 0);
Y : out std_logic_vector(7 downto 0)
);
end entity Inverter_logic_8;
architecture Behavioral of Inverter_logic_8 is
begin
Y <= (not X);
end architecture Behavioral;
entity Adder
{
port in logic[8] X, Y;
port out logic[8] Result;
Result = X + Y;
}
entity Testbench
{
Adder uut(
X = auto(0),
Y = 1,
Result = auto
);
process test()
{
uut.X += 3;
wait(10ns);
}
}
simulate_entity(Testbench);
The entity Testbench is
end entity Testbench;
architecture Behavioral of Testbench is
signal thp_uut_autosig_X : std_logic_vector(7 downto 0) := X"00";
signal thp_uut_autosig_Result : std_logic_vector(7 downto 0);
component Adder is
Port (
X : in std_logic_vector(7 downto 0);
Y : in std_logic_vector(7 downto 0);
Result : out std_logic_vector(7 downto 0)
);
end component Adder;
begin
uut : Adder
port map (
X => thp_uut_autosig_X,
Y => X"01",
Result => thp_uut_autosig_Result
);
test : process is
begin
thp_uut_autosig_X <= (thp_uut_autosig_X + X"03");
wait for 10ns;
end process test;
end architecture Behavioral;