The automatic insertion of IOBs into the intended isolated hierarchy is a feature of the Vivado Design Suite. Before the Vivado tools, you had to manually instantiate IOBs inside the HDL for each isolated module. Vivado tools, however, do this for you provided you have not already instantiated the IOB themselves. At initial inference, all IOB are at the top level. Wherever the IDF can see a clear usage for the IOB, it automatically moves this IOB from the top level into the wanted isolated module. This movement happens in the Vivado tools synthesis and/or the opt_design step after synthesis but before placement.
There are some limitations to this feature because not all possible cases allow for such movement. These cases are as follows:
Case 1
Input with multiple destinations (that includes the top level). See the following figure.
In this case, a "reset" input to the top-level MMCM also connects to at least on isolated region.
Case 2
Output whose input drives the IOB in question and at least one other region. See the following figure.
Case 3
Output whose input comes directly from an input port of the isolated module in question. See the following figure.
Case 4
Bidirectional IOB where input and output do not go to the same isolated region. See the following figure.
Case 5
IOB with connections only to top-level logic. See the following figure.
Case 6
IOB was manually instantiated by the user on their IP.
Directly instantiated IOB with the DONT_TOUCH property set on the buffer is not moved. The DONT_TOUCH property can be inherited from the IP that instantiates the IOB or you can manually add the property.