Verilog and VHDL in one design
RTL Bench elaborates VHDL with GHDL and Verilog with Yosys, then stitches the two netlists into a single hierarchy. You can mix languages freely in one project — a SystemVerilog top instantiating a VHDL entity, or the other way round.
When you actually need this
Most new designs are single-language. You pick Verilog or VHDL and stay there. Mixed-language comes up when:
- You are integrating third-party IP. A vendor ships a VHDL block, your project is Verilog, and rewriting it is not an option.
- You inherited a legacy design. The old blocks are VHDL, the new ones are SystemVerilog, and both have to compile together.
- Your team is split. One group writes VHDL for safety-critical blocks, another uses SystemVerilog for verification-heavy logic.
- You are learning. You want to see how the same design looks in both languages side by side.
How RTL Bench handles it
When you upload a folder with mixed files, RTL Bench sorts them by extension and routes each group to the right backend:
| Extension | Parser | Output |
|---|---|---|
.v |
Yosys read_verilog -sv |
Yosys RTLIL netlist |
.sv |
Yosys read_verilog -sv |
Yosys RTLIL netlist |
.vhd |
GHDL via ghdl --std=08 |
Converted to RTLIL through the Yosys GHDL plugin |
.vhdl |
GHDL via ghdl --std=08 |
Converted to RTLIL through the Yosys GHDL plugin |
Once both are RTLIL, Yosys sees one design. Cross-language instantiation works both ways because the hierarchy is resolved by name, not by language.
A working example
Three files. Two VHDL, one SystemVerilog. Upload all three at once, and the SystemVerilog top instantiates the VHDL entity.
1. VHDL package — alu_pkg.vhd
library ieee;
use ieee.std_logic_1164.all;
package alu_pkg is
constant WIDTH : integer := 8;
end package alu_pkg;
2. VHDL entity — alu_core.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.alu_pkg.all;
entity alu_core is
port (
a : in std_logic_vector(WIDTH-1 downto 0);
b : in std_logic_vector(WIDTH-1 downto 0);
op : in std_logic_vector(1 downto 0);
y : out std_logic_vector(WIDTH-1 downto 0)
);
end entity alu_core;
architecture rtl of alu_core is
begin
with op select
y <= std_logic_vector(unsigned(a) + unsigned(b)) when "00",
std_logic_vector(unsigned(a) - unsigned(b)) when "01",
a and b when "10",
a or b when others;
end architecture rtl;
3. SystemVerilog top — top.sv
module top (
input logic [7:0] a,
input logic [7:0] b,
input logic [1:0] op,
output logic [7:0] y
);
// VHDL entity instantiated from Verilog
alu_core u_alu (
.a (a),
.b (b),
.op(op),
.y (y)
);
endmodule
Upload all three files, click Generate. The schematic shows
top at the root and u_alu as a submodule
with ports matching the VHDL entity. The TOP: dropdown
auto-selects top because it is the only module that
nothing else instantiates.
Type mapping across languages
When a Verilog module instantiates a VHDL entity, RTL Bench maps the types on the port list. The rules are simple but worth memorising:
| VHDL | Verilog / SystemVerilog | Notes |
|---|---|---|
std_logic |
logic |
1-bit signal. Both are 4-state (0, 1, X, Z). |
std_logic_vector(N-1 downto 0) |
logic [N-1:0] |
Bit order must match. downto maps to big-endian bits. |
unsigned(N-1 downto 0) |
logic [N-1:0] |
Only a type hint in VHDL. On the port list it is still a vector. |
signed(N-1 downto 0) |
logic [N-1:0] |
Same as unsigned — signedness is not tracked at the port boundary. |
integer |
— | Not legal as a port type. Use std_logic_vector or a generic. |
boolean |
— | Not legal as a port type. Use std_logic. |
integer or
boolean fail to elaborate in a mixed-language project.
Convert them to std_logic_vector in the VHDL source
before mixing.
Case sensitivity — the #1 bug
VHDL is case-insensitive. Verilog is case-sensitive. When the two meet, casing matters on the Verilog side and must exactly match what GHDL reports.
This is fine:
-- VHDL
entity ALU_Core is ... end entity;
// Verilog — same casing
ALU_Core u_alu ( ... );
// Verilog — all lowercase also works
alu_core u_alu ( ... );
This fails silently until elaboration:
// Verilog — mixed casing that matches neither
Alu_Core u_alu ( ... );
Yosys looks for a module called Alu_Core, does not find
it, and reports "module not found". GHDL does not know about
Verilog's case rules, so the VHDL side has already been elaborated
into a netlist with whatever casing the entity was declared with.
lower_snake_case (matching Verilog habits),
or declare them in PascalCase and use that exact casing
in every Verilog instantiation. Do not mix within a project.
Generics and parameters
A VHDL generic and a Verilog parameter are
the same idea — a value passed at elaboration. But they are not
interchangeable across the language boundary in RTL Bench.
- VHDL generic with a default. Works. The default is used when the entity is instantiated from Verilog.
- VHDL generic without a default. Fails to elaborate. GHDL cannot resolve the value, and the Verilog side has no syntax to pass a generic anyway.
- Verilog parameter overridden from VHDL. Not supported. VHDL has no direct syntax for Verilog's parameter-override mechanism.
-- Good: default value
entity alu_core is
generic ( WIDTH : integer := 8 );
port ( ... );
end entity;
-- Bad: no default — mixed-language instantiation fails
entity alu_core is
generic ( WIDTH : integer );
port ( ... );
end entity;
What does not work
Honest limitations of the mixed-language flow in RTL Bench:
- Passing generics across the boundary. Verilog cannot override a VHDL generic. Every mixed-language generic must have a default.
-
Sharing a
packagewith Verilog. VHDL packages exist only inside VHDL. Verilog has no way tousethem. -
VHDL
recordports. Records cannot cross into Verilog. Flatten them into separate ports or astd_logic_vector. -
VHDL
integerandbooleanports. Not legal across the boundary. Convert tostd_logicorstd_logic_vector. - Signals and variables shared across languages. Only ports cross the boundary. Internal signals stay inside their language.
-
Verification constructs. VHDL
assertand SystemVerilogassertare ignored by the synthesis flow. They only run in a simulator, and the auto-generated testbench does not exercise them.
Uploading the files
The upload flow is identical to a single-language project:
- Click 📁 Folder in the top bar.
- Select a folder containing every file —
.v,.sv,.vhd,.vhdl, and any.hexor.memdata files. - RTL Bench preserves the folder structure and the relative paths.
`includedirectives and$readmemhcalls resolve against the working directory. - Click Generate. The TOP dropdown auto-selects the module or entity that nothing else instantiates.
See Multi-file projects for the full upload and top-detection walkthrough.
When to not mix languages
Mixed-language works but costs you:
- Two syntaxes to debug. Every build failure could be a Verilog parse error or a VHDL elaboration error.
- Casing bugs. The most common failure mode, described above.
- Type mapping friction. Every VHDL port must use a subset of types that Verilog can understand.
- Readability. A new engineer reading the file tree has to context-switch between two languages.
If you have a choice, pick one language and stick with it. If you do not — because the IP is fixed, or the team is split — RTL Bench's mixed-language flow is genuinely usable for small and medium designs.
Frequently asked questions
Yes. RTL Bench parses VHDL with GHDL and Verilog or SystemVerilog with Yosys, then synthesizes both into a unified internal netlist. You can instantiate a VHDL entity inside a Verilog module or instantiate a Verilog module inside a VHDL architecture.
std_logic maps to a 1-bit logic signal, std_logic_vector maps to a packed vector ([N-1:0]), and integer generics map to Verilog parameters. Unconstrained arrays and user-defined records should be flattened to std_logic_vector before crossing language boundaries.
Case sensitivity is the most common cause. VHDL is case-insensitive, so an entity named my_core will be exported in lowercase. Verilog is case-sensitive — instantiating My_Core will result in an unresolved reference. Always use lowercase port and module names across the boundary.