Create one "entity" per file. Entity has the list of ports, and the implemtation. It's your basic building block.
Then, you can use them as "components" in other entities. Any entity can be instantiated in any other entity, as many times you want.
Often, it makes sense to make a "top level" that only instantiates other entities, but does not implement a lot of its own actual logic. This is not required, just a cleanliness thing.
Many sources (such as
https://www.doulos.com/knowhow/vhdl_designers_guide/components_and_port_maps/ ) teach you the "old way" that you need to declare a component first, then instantiate it separately - this is a lot of copy paste work where you need to reformat the text to look a bit different. Emacs text editor, for example, has automated copypaste functionality to copy a ENTITY declaration, then paste it as a component declaration.
Now just create one top-level entity - its port declaration will be the actual physical FPGA pins. Then set this entity as the top level entity, IIRC by second-clicking the file and doing the magic on that menu. Then compile the design, then you use the Quartus Pin Planner or constraint editor to actually map your top level entity port names (they can be anything, such as LED_OUT) to actual pin numbers (such as PIN_123 on a QFP package or PIN_AG67 on a BGA package).
Then instantiate other entities as components within that toplevel entity. Using all that copypaste magic. Then, for the connecting wires, you need to generate signals, then connect these signals to the PORT MAPS. So, some more copypasting.
Connecting blocks in VHDL is a lot of tedious copy paste work. A lot of more or less usable tools exist, trying to make this easier.
All this copypasting makes little sense. AFAIK, most if not all VHDL compilers support direct instantiation (VHDL-93 addition) of an entity without needing to first declare a component. For reference:
http://www.ics.uci.edu/~jmoorkan/vhdlref/compinst.htmlThis reduces the error-prone and ugly copypaste work somewhat.
VHDL is tedious to write and suffers from many historical brainfarts. Another example is the totally insane "sensitivity list" concept, which is just simply a concept of the programmer making a super-simple compiler optimization step for the compiler, manually writing a list of things the compiler only need to look at - this was to speed up simulation in 1980's! It's not a way to describe a design; but if you get it wrong, you get mismatch between simulation and real world! Modern compilers check this list and warn you if you have typed it wrong. It's still insane. Luckily, most of the time, we end up writing synchronous logic where the sensitivity list is limited to clk and reset, but this is even more a reason you need to be careful when writing that rare asynchronous process...