In any language, abstraction used for separating of system behavioural description from its level implementation. The code architecture of the power supply shown above is build so that a higher level module instantiates lower level modules and wraps the lower level module interface from any modules higher in the hierarchy.
The way this is achieved is by creating module hierarchy through the use of module specific header files or packages and module specific record types. This type of abstraction of the module interface allows the application code to have very concise signal declarations which are not bound to the internal implementation of the record type signals present in the module interface. As long as the names of the interface signal record types do not change, the entire module and module signals inside the records can be completely rewritten without affecting the application code using a module.
The module top is the highest part of the hierarchy and it instantiates the whole design which is wrapped inside a system control module. The top module entity has a port with inputs to the clock geneartors and the system control fpga input and output records. The system control modules records are defined in the package system_control_pkg.
library work;
use work.system_control_pkg.all;
entity top is
port (
enet_clk_125MHz : in std_logic;
pll_input_clock : in std_logic;
system_control_FPGA_in : in system_control_FPGA_input_group;
system_control_FPGA_out : out system_control_FPGA_output_group
);
end entity ;
In the system control package, the FPGA input and output groups are defined to contain system components FPGA input group and ouput groups respectively.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library work;
use work.system_components_pkg.all;
package system_control_pkg is
type system_control_clock_group is record
clock : std_logic;
reset_n : std_logic;
end record;
type system_control_FPGA_input_group is record
system_components_FPGA_in : system_components_FPGA_input_group;
end record;
type system_control_FPGA_output_group is record
system_components_FPGA_out : system_components_FPGA_output_group;
end record;
type system_control_data_input_group is record
system_components_data_in : system_components_data_input_group;
end record;
type system_control_data_output_group is record
system_components_data_out : system_components_data_output_group;
end record;
component system_control is
port (
system_control_clocks : in system_control_clock_group;
system_control_FPGA_in : in system_control_FPGA_input_group;
system_control_FPGA_out : out system_control_FPGA_output_group;
system_control_data_in : in system_control_data_input_group;
system_control_data_out : out system_control_data_output_group
);
end component system_control;
Defined this way, instantiating a the system control module instantiates the whole design and the interface wraps the entire operation inside it. The FPGA input and output groups are passed through the entire hierarchy this way also and the driver that actually defines the state of an IO is at some lower part of the code architecture. For example, a uart TX IO signal in the pin Pin Planner looks like this
ystem_control_FPGA_out.system_components_FPGA_out.uart_FPGA_out.uart_transreceiver_FPGA_out.uart_tx_FPGA_out.uart_tx
The source files are also stored in folder that are named according to the code architecture. This makes it easy to find a specific IO from a module when needed as the name of the IO signal follow the folder structure.
The IO names are long, but there is rarely a need to address them in anywhere else than at the level of the IO driver, which uses the uart_tx as the name of the IO. Great benefit of this type of IO encapsulation is that it allows very incremental building the of system as the top module does not need to know how many and what kind of modules are below the system control module. The pin planner definitions and pin mapping can be added when the corresponding module is ready to be programmed and tested with the FPGA by only adding the module and instantiating it. This also makes the code architecture loosely coupled, since moving a module to another part of the hierarchy only requires changes in the pin planner and in the modules where the changes are made. If the uart_transreceiver was deemed unnecessary, removing it would only make a difference in the uart module which would then instantiate the rx and tx modules and in the pin planner where the pins would be paired with physical location in the FPGA package.
The same encapsulation principle is also followed with the code that actually does the actions requred by the operation of the system. The module interfaces are written using a init, request, ready pattern where the application code initializes a module, requests a module service and waits for the module ready output at which point the data acquired from a module is acted upon. This design pattern allows the interactions with a module to be tolerant towards unspecified latencies in the module operation due to the request-ready handshaking mechanism and hides the internal workings from the application code.