Anatomy of a Component split a component into an interface and an implementation. This lesson is where that split earns its keep: one interface can front several implementations, and you pick which one runs without touching anything that uses the component.
Why would you want two implementations of the same thing?
This lesson's example does the third: an alarm that fires when two redundant door sensors disagree. It is implemented once with the XOR operator and once with the gates that build it.
The interface is the contract both implementations honor.
In VHDL it's declared exactly once, as the entity:
entity mismatch_alarm is
port (
sensor_a : in std_logic;
sensor_b : in std_logic;
alarm : out std_logic
);
end entity mismatch_alarm;
Now the two implementations.
In VHDL they are two architectures of that one entity, each tied to it by name (of mismatch_alarm) and carrying its own name (direct, gates):
architecture direct of mismatch_alarm is
begin
alarm <= sensor_a xor sensor_b;
end architecture direct;
architecture gates of mismatch_alarm is
begin
alarm <= (sensor_a and not sensor_b)
or (not sensor_a and sensor_b);
end architecture gates;
Any number of architectures can coexist for one entity; same file or separate files, the library keeps them all.
Whoever uses a component decides which implementation runs. Using a component inside another (instantiation) gets its full treatment in Hierarchy and Reuse. But you've already been on the receiving end of one: every exercise testbench instantiates your design under the label dut. Here is how this lesson's testbench requests the direct implementation; read it as "build this implementation, wired to these signals":
dut_direct : entity work.mismatch_alarm(direct)
port map (
sensor_a => sensor_a,
sensor_b => sensor_b,
alarm => alarm_direct
);
The selector is the architecture name in parentheses. Change (direct) to (gates) and a different circuit gets built, while every connection stays the same.
VHDL can take the choice one step further: a configuration declaration is a separate design unit whose only job is to record which architecture an instance uses. This is a preview, not part of this lesson's example. Imagine some other testbench, some_alarm_tb, holding a single instance of the alarm under the label dut:
configuration pick_gates of some_alarm_tb is
for tb
for dut : mismatch_alarm
use entity work.mismatch_alarm(gates);
end for;
end for;
end configuration pick_gates;
Read it top to bottom: "inside testbench some_alarm_tb's architecture tb, the instance dut of mismatch_alarm shall use architecture gates." Nobody edits the design or the testbench; the choice lives in its own swappable unit. The example panel's testbench has no use for one: it already names each architecture directly at the instantiation, as you saw above. Configurations pair with a different instantiation style (component instantiation) that you haven't met yet. Both arrive together in Hierarchy and Reuse, where you'll write a configuration yourself. For now, remember only that the binding can live outside the design files.
| VHDL | SystemVerilog | |
|---|---|---|
| Interface lives in | the entity, declared once | every module header, repeated per sibling |
| Implementation lives in | an architecture (many per entity) | the module body (one per module) |
| Adding an implementation | another architecture of the same entity | a sibling module with the same port list |
| Selecting one | architecture name at instantiation, or a configuration | module name at instantiation |
The example panel on the right holds the complete alarm.
It's a single file: the entity plus both architectures.
The testbench instantiates both implementations side by side and walks through all four sensor combinations. In the waveform, compare alarm_direct with alarm_gates: they match on every input, high exactly when the two sensors disagree. Two different circuits, one observable behavior. That interchangeability is the whole point of keeping the interface fixed.
sync-content --examples to generate.