Note
Go to the end to download the full example code.
Code Generation¶
Once an Architecture has been derived, the final
step is to generate synthesizable HDL that describes it, along with a testbench
that can be used to verify the implementation through simulation.
Recreating the Architecture¶
We start by reproducing the architecture derived in the previous tutorial.
from scipy.signal import iirfilter
from b_asic.architecture import Architecture, Memory, ProcessingElement
from b_asic.schedule import Schedule
from b_asic.sfg_generators.wave_digital_filters import lattice_wdf
from b_asic.special_operations import Input, Output
from b_asic.wdf import lattice_coeffs_from_tf
from b_asic.wdf_operations import SymmetricTwoportAdaptor
from b_asic.quantization import quantize
b, a = iirfilter(N=7, Wn=0.3, rp=0.1, rs=60, btype="low", ftype="ellip")
adaptor_coeffs = lattice_coeffs_from_tf(a)
quantized_coeffs = [quantize(c, fractional_bits=14) for c in adaptor_coeffs]
wdf_sfg = lattice_wdf(quantized_coeffs, only_adaptors=True)
wdf_sfg.set_latency_of_type(SymmetricTwoportAdaptor, 4)
wdf_sfg.set_execution_time_of_type(SymmetricTwoportAdaptor, 1)
schedule = Schedule(wdf_sfg, cyclic=True)
schedule.move_operation('out0', 9)
schedule.move_operation('sym2p3', 4)
schedule.move_operation('sym2p3', 5)
schedule.move_operation('sym2p4', 1)
schedule.move_operation('sym2p6', 4)
schedule.move_operation('sym2p4', 7)
schedule.move_operation('sym2p1', 1)
schedule.set_schedule_time(12)
schedule.move_operation('sym2p1', 1)
schedule.move_operation('sym2p6', 1)
schedule.move_operation('sym2p5', 2)
schedule.move_operation('sym2p2', 2)
schedule.move_operation('sym2p7', 1)
schedule.move_operation('sym2p6', -1)
schedule.move_operation('sym2p4', -2)
schedule.move_operation('sym2p5', -1)
schedule.move_operation('sym2p4', -1)
schedule.move_operation('sym2p3', -4)
schedule.move_operation('sym2p6', -1)
schedule.move_operation('out0', -4)
schedule.move_operation('sym2p1', 1)
schedule.move_operation('sym2p2', 1)
schedule.move_operation('sym2p6', 1)
schedule.move_y_location('sym2p3', 9, True)
schedule.move_operation('out0', 1)
schedule.move_operation('sym2p3', 1)
schedule.move_operation('sym2p4', 1)
schedule.move_operation('sym2p4', -1)
schedule.move_operation('sym2p6', -3)
schedule.move_operation('sym2p6', 3)
schedule.move_operation('sym2p6', -2)
schedule.move_y_location('sym2p6', 3, True)
schedule.move_operation('sym2p6', 2)
schedule.move_y_location('sym2p6', 5, True)
variables = schedule.get_memory_variables()
direct, variables = variables.split_on_length()
variable_groups = variables.split_on_ports(write_ports=1, read_ports=1, total_ports=2)
mem0 = Memory(variable_groups[0], assign=True, entity_name="m0")
mem1 = Memory(variable_groups[1], assign=True, entity_name="m1")
ops = schedule.get_operations()
adaptor = ProcessingElement(ops.get_by_type(SymmetricTwoportAdaptor), entity_name="a")
input_pe = ProcessingElement(ops.get_by_type(Input), entity_name="x")
output_pe = ProcessingElement(ops.get_by_type(Output), entity_name="y")
arch = Architecture(
processing_elements=[adaptor, input_pe, output_pe],
memories=[mem0, mem1],
direct_interconnects=direct,
entity_name="wdf",
)
Generating VHDL¶
HDL is generated by a code printer.
Currently, VHDL is supported, which is done through
VhdlPrinter.
The printer needs to know which DataType to use
for the generated arithmetic.
Here, we use 10 fractional bits, and two integer bits.
Furthermore, we use magnitude truncation for quantization,
and saturation for overflow handling.
from b_asic.code_printer import VhdlPrinter
from b_asic.data_type import DataType
from b_asic.quantization import QuantizationMode, OverflowMode
dt = DataType(
wl=(2, 10),
quantization_mode=QuantizationMode.MAGNITUDE_TRUNCATION,
overflow_mode=OverflowMode.SATURATION,
)
printer = VhdlPrinter(dt)
Generating the VHDL files describing the architecture is then as simple as
printer.print(arch, path="generated")
This writes one file per processing element, I/O unit, and memory, along with a
top-level file describing arch itself.
get_compile_order() returns the
resulting file names in the order in which they can be compiled.
print(printer.get_compile_order(arch))
['m0.vhdl', 'm1.vhdl', 'a.vhdl', 'x.vhdl', 'y.vhdl', 'wdf.vhdl']
Let’s have a look at the generated code, by reading back the file that was written.
from pathlib import Path
Top-level file, containing all units found below, top-level muxes, and the schedule counter.
print(Path("generated", "wdf.vhdl").read_text())
--
-- This code was automatically generated by the B-ASIC toolbox.
-- Code generation timestamp: (2026-06-25 08:19:29.392463)
-- B-ASIC version: 0.1.0.dev983+unknown.gc02664293
-- URL: https://github.com/b-asic-eda/b-asic
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity wdf is
port (
clk : in std_logic;
rst : in std_logic;
en : in std_logic;
x_0_in : in std_logic_vector(11 downto 0);
y_0_out : out std_logic_vector(11 downto 0)
);
end entity wdf;
architecture rtl of wdf is
-- Component declaration
component a
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in signed(11 downto 0);
p_1_in : in signed(11 downto 0);
p_0_out : out signed(11 downto 0);
p_1_out : out signed(11 downto 0)
);
end component;
component x
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in std_logic_vector(11 downto 0);
p_0_out : out signed(11 downto 0)
);
end component;
component y
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in signed(11 downto 0);
p_0_out : out std_logic_vector(11 downto 0)
);
end component;
component m0
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in signed(11 downto 0);
p_0_out : out signed(11 downto 0)
);
end component;
component m1
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in signed(11 downto 0);
p_0_out : out signed(11 downto 0)
);
end component;
-- a signals
signal a_0_in : signed(11 downto 0);
signal a_1_in : signed(11 downto 0);
signal a_0_out : signed(11 downto 0);
signal a_1_out : signed(11 downto 0);
-- x signals
signal x_0_out : signed(11 downto 0);
-- y signals
signal y_0_in : signed(11 downto 0);
-- m0 signals
signal m0_0_in : signed(11 downto 0);
signal m0_0_out : signed(11 downto 0);
-- m1 signals
signal m1_0_in : signed(11 downto 0);
signal m1_0_out : signed(11 downto 0);
signal schedule_cnt : unsigned(3 downto 0) := (others => '0');
-- Multiplexer control signals
signal a_0_sel : std_logic_vector(1 downto 0);
signal a_1_sel : std_logic_vector(1 downto 0);
signal m0_0_sel : std_logic_vector(1 downto 0);
signal m1_0_sel : std_logic_vector(0 downto 0);
begin
-- Component instantiation
a_inst: a
port map (
clk => clk,
en => en,
schedule_cnt => schedule_cnt,
p_0_in => a_0_in,
p_1_in => a_1_in,
p_0_out => a_0_out,
p_1_out => a_1_out
);
x_inst: x
port map (
clk => clk,
en => en,
schedule_cnt => schedule_cnt,
p_0_in => x_0_in,
p_0_out => x_0_out
);
y_inst: y
port map (
clk => clk,
en => en,
schedule_cnt => schedule_cnt,
p_0_in => y_0_in,
p_0_out => y_0_out
);
m0_inst: m0
port map (
clk => clk,
en => en,
schedule_cnt => schedule_cnt,
p_0_in => m0_0_in,
p_0_out => m0_0_out
);
m1_inst: m1
port map (
clk => clk,
en => en,
schedule_cnt => schedule_cnt,
p_0_in => m1_0_in,
p_0_out => m1_0_out
);
-- Schedule counter
schedule_cnt_proc: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
schedule_cnt <= (others => '0');
elsif en = '1' then
if schedule_cnt = 11 then
schedule_cnt <= (others => '0');
else
schedule_cnt <= schedule_cnt + 1;
end if;
end if;
end if;
end process schedule_cnt_proc;
-- Multiplexer control signal generation
with schedule_cnt select
a_0_sel <=
"00" when "0000",
"01" when "0001",
"10" when "0010",
"01" when "0011",
"10" when "0100",
"10" when "0101",
"01" when "0111",
"11" when "1001",
"--" when others;
with schedule_cnt select
a_1_sel <=
"00" when "0000",
"00" when "0001",
"01" when "0010",
"00" when "0011",
"01" when "0100",
"10" when "0101",
"10" when "0111",
"00" when "1001",
"--" when others;
with schedule_cnt select
m0_0_sel <=
"00" when "0000",
"01" when "0001",
"01" when "0100",
"01" when "0101",
"01" when "0111",
"10" when "1000",
"10" when "1011",
"--" when others;
with schedule_cnt select
m1_0_sel <=
"0" when "0001",
"0" when "0100",
"1" when "1000",
"1" when "1001",
"1" when "1011",
"-" when others;
-- Interconnect
with a_0_sel select
a_0_in <=
x_0_out when "00",
m1_0_out when "01",
m0_0_out when "10",
a_0_out when "11",
(others => '-') when others;
with a_1_sel select
a_1_in <=
m0_0_out when "00",
m1_0_out when "01",
a_0_out when "10",
(others => '-') when others;
y_0_in <= a_0_out;
with m0_0_sel select
m0_0_in <=
x_0_out when "00",
a_1_out when "01",
a_0_out when "10",
(others => '-') when others;
with m1_0_sel select
m1_0_in <=
a_0_out when "0",
a_1_out when "1",
(others => '-') when others;
end architecture rtl;
Memories
--
-- This code was automatically generated by the B-ASIC toolbox.
-- Code generation timestamp: (2026-06-25 08:19:29.391860)
-- B-ASIC version: 0.1.0.dev983+unknown.gc02664293
-- URL: https://github.com/b-asic-eda/b-asic
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity m0 is
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in signed(11 downto 0);
p_0_out : out signed(11 downto 0)
);
end entity m0;
architecture rtl of m0 is
-- HDL memory description
type mem_type is array(0 to 4) of signed(11 downto 0);
signal memory : mem_type := (others => (others => '0'));
-- Memory address generation
signal read_port_0 : signed(11 downto 0);
signal read_adr_0 : unsigned(2 downto 0);
signal write_port_0 : signed(11 downto 0);
signal write_adr_0 : unsigned(2 downto 0);
signal write_en_0 : std_logic;
-- Address generation multiplexing signals
signal write_adr_0_0_0 : unsigned(2 downto 0);
signal write_en_0_0_0 : std_logic;
signal read_adr_0_0_0 : unsigned(2 downto 0);
-- Type conversion for interface
signal p_0_in_internal : signed(11 downto 0) := (others => '0');
signal p_0_out_internal : signed(11 downto 0) := (others => '0');
signal forward_ctrl : std_logic;
begin
-- Type conversions
p_0_in_internal <= p_0_in;
p_0_out <= p_0_out_internal;
-- Memory
mem_0_proc: process(clk)
begin
if rising_edge(clk) then
if en = '1' then
if write_en_0 = '1' then
memory(to_integer(write_adr_0)) <= write_port_0;
end if;
end if;
end if;
end process mem_0_proc;
read_port_0 <= memory(to_integer(read_adr_0));
read_adr_0 <= read_adr_0_0_0;
write_adr_0 <= write_adr_0_0_0;
write_en_0 <= write_en_0_0_0;
write_port_0 <= p_0_in_internal;
-- Input and output assignments
output_reg_proc: process(clk)
begin
if rising_edge(clk) then
if forward_ctrl = '1' then
p_0_out_internal <= p_0_in_internal;
else
p_0_out_internal <= read_port_0;
end if;
end if;
end process output_reg_proc;
with schedule_cnt select
forward_ctrl <=
'0' when "0000",
'0' when "0001",
'0' when "0100",
'0' when "0101",
'0' when "0111",
'1' when "1000",
'0' when "1011",
'-' when others;
--
-- Memory write address generation
--
mem_write_address_proc: process(schedule_cnt)
begin
case schedule_cnt is
-- MemoryVariable(0, <b_asic.port.OutputPort object at 0x7fc9dc3cabc0>, {<b_asic.port.InputPort object at 0x7fc9dc125ae0>: 5}, 'in0.0')
when "0000" =>
write_adr_0_0_0 <= to_unsigned(0, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(1, <b_asic.port.OutputPort object at 0x7fc9dc127480>, {<b_asic.port.InputPort object at 0x7fc9dc06ee40>: 3}, 'sym2p4.1')
when "0001" =>
write_adr_0_0_0 <= to_unsigned(1, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(4, <b_asic.port.OutputPort object at 0x7fc9dc3ca7b0>, {<b_asic.port.InputPort object at 0x7fc9dc3c93b0>: 8}, 'sym2p0.1')
when "0100" =>
write_adr_0_0_0 <= to_unsigned(1, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(5, <b_asic.port.OutputPort object at 0x7fc9dc06fac0>, {<b_asic.port.InputPort object at 0x7fc9dc06fbb0>: 8}, 'sym2p7.1')
when "0101" =>
write_adr_0_0_0 <= to_unsigned(2, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(7, <b_asic.port.OutputPort object at 0x7fc9dc1e0730>, {<b_asic.port.InputPort object at 0x7fc9dc3c8910>: 8}, 'sym2p2.1')
when "0111" =>
write_adr_0_0_0 <= to_unsigned(3, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(8, <b_asic.port.OutputPort object at 0x7fc9dc06c5f0>, {<b_asic.port.InputPort object at 0x7fc9dc1e3660>: 1}, 'sym2p6.0')
when "1000" =>
write_adr_0_0_0 <= to_unsigned(0, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(11, <b_asic.port.OutputPort object at 0x7fc9dc3ca120>, {<b_asic.port.InputPort object at 0x7fc9dc1e1ef0>: 3}, 'sym2p1.0')
when "1011" =>
write_adr_0_0_0 <= to_unsigned(4, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
when others =>
write_adr_0_0_0 <= (others => '-');
write_en_0_0_0 <= '0';
end case;
end process mem_write_address_proc;
--
-- Memory read address generation
--
mem_read_address_proc: process(schedule_cnt)
begin
case schedule_cnt is
-- MemoryVariable(4, <b_asic.port.OutputPort object at 0x7fc9dc3ca7b0>, {<b_asic.port.InputPort object at 0x7fc9dc3c93b0>: 8}, 'sym2p0.1')
when "1011" =>
read_adr_0_0_0 <= to_unsigned(1, read_adr_0_0_0'length);
-- MemoryVariable(5, <b_asic.port.OutputPort object at 0x7fc9dc06fac0>, {<b_asic.port.InputPort object at 0x7fc9dc06fbb0>: 8}, 'sym2p7.1')
when "0000" =>
read_adr_0_0_0 <= to_unsigned(2, read_adr_0_0_0'length);
-- MemoryVariable(11, <b_asic.port.OutputPort object at 0x7fc9dc3ca120>, {<b_asic.port.InputPort object at 0x7fc9dc1e1ef0>: 3}, 'sym2p1.0')
when "0001" =>
read_adr_0_0_0 <= to_unsigned(4, read_adr_0_0_0'length);
-- MemoryVariable(7, <b_asic.port.OutputPort object at 0x7fc9dc1e0730>, {<b_asic.port.InputPort object at 0x7fc9dc3c8910>: 8}, 'sym2p2.1')
when "0010" =>
read_adr_0_0_0 <= to_unsigned(3, read_adr_0_0_0'length);
-- MemoryVariable(1, <b_asic.port.OutputPort object at 0x7fc9dc127480>, {<b_asic.port.InputPort object at 0x7fc9dc06ee40>: 3}, 'sym2p4.1')
when "0011" =>
read_adr_0_0_0 <= to_unsigned(1, read_adr_0_0_0'length);
-- MemoryVariable(0, <b_asic.port.OutputPort object at 0x7fc9dc3cabc0>, {<b_asic.port.InputPort object at 0x7fc9dc125ae0>: 5}, 'in0.0')
when "0100" =>
read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length);
-- MemoryVariable(8, <b_asic.port.OutputPort object at 0x7fc9dc06c5f0>, {<b_asic.port.InputPort object at 0x7fc9dc1e3660>: 1}, 'sym2p6.0')
when "1000" =>
read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length);
when others =>
read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length);
end case;
end process mem_read_address_proc;
end architecture rtl;
--
-- This code was automatically generated by the B-ASIC toolbox.
-- Code generation timestamp: (2026-06-25 08:19:29.392193)
-- B-ASIC version: 0.1.0.dev983+unknown.gc02664293
-- URL: https://github.com/b-asic-eda/b-asic
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity m1 is
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in signed(11 downto 0);
p_0_out : out signed(11 downto 0)
);
end entity m1;
architecture rtl of m1 is
-- HDL memory description
type mem_type is array(0 to 2) of signed(11 downto 0);
signal memory : mem_type := (others => (others => '0'));
-- Memory address generation
signal read_port_0 : signed(11 downto 0);
signal read_adr_0 : unsigned(1 downto 0);
signal write_port_0 : signed(11 downto 0);
signal write_adr_0 : unsigned(1 downto 0);
signal write_en_0 : std_logic;
-- Address generation multiplexing signals
signal write_adr_0_0_0 : unsigned(1 downto 0);
signal write_en_0_0_0 : std_logic;
signal read_adr_0_0_0 : unsigned(1 downto 0);
-- Type conversion for interface
signal p_0_in_internal : signed(11 downto 0) := (others => '0');
signal p_0_out_internal : signed(11 downto 0) := (others => '0');
signal forward_ctrl : std_logic;
begin
-- Type conversions
p_0_in_internal <= p_0_in;
p_0_out <= p_0_out_internal;
-- Memory
mem_0_proc: process(clk)
begin
if rising_edge(clk) then
if en = '1' then
if write_en_0 = '1' then
memory(to_integer(write_adr_0)) <= write_port_0;
end if;
end if;
end if;
end process mem_0_proc;
read_port_0 <= memory(to_integer(read_adr_0));
read_adr_0 <= read_adr_0_0_0;
write_adr_0 <= write_adr_0_0_0;
write_en_0 <= write_en_0_0_0;
write_port_0 <= p_0_in_internal;
-- Input and output assignments
output_reg_proc: process(clk)
begin
if rising_edge(clk) then
if forward_ctrl = '1' then
p_0_out_internal <= p_0_in_internal;
else
p_0_out_internal <= read_port_0;
end if;
end if;
end process output_reg_proc;
with schedule_cnt select
forward_ctrl <=
'1' when "0001",
'0' when "0100",
'0' when "1000",
'0' when "1001",
'0' when "1011",
'-' when others;
--
-- Memory write address generation
--
mem_write_address_proc: process(schedule_cnt)
begin
case schedule_cnt is
-- MemoryVariable(1, <b_asic.port.OutputPort object at 0x7fc9dc1277a0>, {<b_asic.port.InputPort object at 0x7fc9dc1e0d70>: 1}, 'sym2p4.0')
when "0001" =>
write_adr_0_0_0 <= to_unsigned(0, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(4, <b_asic.port.OutputPort object at 0x7fc9dc3cb0c0>, {<b_asic.port.InputPort object at 0x7fc9dc3c8d20>: 3}, 'sym2p0.0')
when "0100" =>
write_adr_0_0_0 <= to_unsigned(0, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(8, <b_asic.port.OutputPort object at 0x7fc9dc06e7b0>, {<b_asic.port.InputPort object at 0x7fc9dc06eb70>: 8}, 'sym2p6.1')
when "1000" =>
write_adr_0_0_0 <= to_unsigned(1, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(9, <b_asic.port.OutputPort object at 0x7fc9dc1240f0>, {<b_asic.port.InputPort object at 0x7fc9dc06f8e0>: 4}, 'sym2p5.1')
when "1001" =>
write_adr_0_0_0 <= to_unsigned(0, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
-- MemoryVariable(11, <b_asic.port.OutputPort object at 0x7fc9dc3c8370>, {<b_asic.port.InputPort object at 0x7fc9dc3ca030>: 4}, 'sym2p1.1')
when "1011" =>
write_adr_0_0_0 <= to_unsigned(2, write_adr_0_0_0'length);
write_en_0_0_0 <= '1';
when others =>
write_adr_0_0_0 <= (others => '-');
write_en_0_0_0 <= '0';
end case;
end process mem_write_address_proc;
--
-- Memory read address generation
--
mem_read_address_proc: process(schedule_cnt)
begin
case schedule_cnt is
-- MemoryVariable(9, <b_asic.port.OutputPort object at 0x7fc9dc1240f0>, {<b_asic.port.InputPort object at 0x7fc9dc06f8e0>: 4}, 'sym2p5.1')
when "0000" =>
read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length);
-- MemoryVariable(1, <b_asic.port.OutputPort object at 0x7fc9dc1277a0>, {<b_asic.port.InputPort object at 0x7fc9dc1e0d70>: 1}, 'sym2p4.0')
when "0001" =>
read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length);
-- MemoryVariable(11, <b_asic.port.OutputPort object at 0x7fc9dc3c8370>, {<b_asic.port.InputPort object at 0x7fc9dc3ca030>: 4}, 'sym2p1.1')
when "0010" =>
read_adr_0_0_0 <= to_unsigned(2, read_adr_0_0_0'length);
-- MemoryVariable(8, <b_asic.port.OutputPort object at 0x7fc9dc06e7b0>, {<b_asic.port.InputPort object at 0x7fc9dc06eb70>: 8}, 'sym2p6.1')
when "0011" =>
read_adr_0_0_0 <= to_unsigned(1, read_adr_0_0_0'length);
-- MemoryVariable(4, <b_asic.port.OutputPort object at 0x7fc9dc3cb0c0>, {<b_asic.port.InputPort object at 0x7fc9dc3c8d20>: 3}, 'sym2p0.0')
when "0110" =>
read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length);
when others =>
read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length);
end case;
end process mem_read_address_proc;
end architecture rtl;
Adaptor PE
print(Path("generated", "a.vhdl").read_text())
--
-- This code was automatically generated by the B-ASIC toolbox.
-- Code generation timestamp: (2026-06-25 08:19:29.390962)
-- B-ASIC version: 0.1.0.dev983+unknown.gc02664293
-- URL: https://github.com/b-asic-eda/b-asic
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity a is
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in signed(11 downto 0);
p_1_in : in signed(11 downto 0);
p_0_out : out signed(11 downto 0);
p_1_out : out signed(11 downto 0)
);
end entity a;
architecture rtl of a is
signal res_overflow_0_reg_0 : signed(11 downto 0) := (others => '0');
signal res_overflow_1_reg_0 : signed(11 downto 0) := (others => '0');
signal p_0_in_reg_0 : signed(11 downto 0) := (others => '0');
signal p_1_in_reg_0 : signed(11 downto 0) := (others => '0');
signal op_0 : signed(11 downto 0);
signal op_1 : signed(11 downto 0);
signal value : signed(14 downto 0) := (others => '0');
constant WL_VALUE_INT : integer := 1;
signal u0 : signed(12 downto 0);
signal mul_res : signed(26 downto 0);
signal res_arith_0_comb : signed(27 downto 0);
signal res_arith_1_comb : signed(27 downto 0);
signal res_arith_0 : signed(27 downto 0) := (others => '0');
signal res_arith_1 : signed(27 downto 0) := (others => '0');
signal mul_res_p : signed(26 downto 0) := (others => '0');
signal op_0_p : signed(11 downto 0) := (others => '0');
signal op_1_p : signed(11 downto 0) := (others => '0');
signal res_quant_0 : signed(13 downto 0);
signal res_overflow_0 : signed(11 downto 0);
signal pos_overflow_0, neg_overflow_0 : std_logic;
signal res_quant_1 : signed(13 downto 0);
signal res_overflow_1 : signed(11 downto 0);
signal pos_overflow_1, neg_overflow_1 : std_logic;
begin
process(clk)
begin
if rising_edge(clk) then
if en = '1' then
res_overflow_0_reg_0 <= res_overflow_0;
res_overflow_1_reg_0 <= res_overflow_1;
p_0_in_reg_0 <= p_0_in;
p_1_in_reg_0 <= p_1_in;
end if;
end if;
end process;
op_0 <= p_0_in_reg_0;
op_1 <= p_1_in_reg_0;
with schedule_cnt select
value <=
b"101111111101110" when "1010",
b"010010001011111" when "0010",
b"011000111110011" when "0101",
b"100010011101000" when "0110",
b"110000000000000" when "0011",
b"010011011011101" when "0001",
b"010100010101100" when "0100",
b"101000010110110" when "1000",
(others => '-') when others;
p_0_out <= res_overflow_0_reg_0;
p_1_out <= res_overflow_1_reg_0;
u0 <= resize(op_1, 13) - resize(op_0, 13);
mul_res <= resize(u0 * value, mul_res'length);
process(clk)
begin
if rising_edge(clk) then
if en = '1' then
mul_res_p <= mul_res;
op_0_p <= op_0;
op_1_p <= op_1;
end if;
end if;
end process;
res_arith_1_comb <= (resize(op_0_p, op_0_p'length + 2) & "00000000000000") + resize(mul_res_p, res_arith_0'length);
res_arith_0_comb <= (resize(op_1_p, op_1_p'length + 2) & "00000000000000") + resize(mul_res_p, res_arith_1'length);
process(clk)
begin
if rising_edge(clk) then
if en = '1' then
res_arith_0 <= res_arith_0_comb;
res_arith_1 <= res_arith_1_comb;
end if;
end if;
end process;
res_quant_0 <= res_arith_0(27 downto 14) + (to_signed(0, 13) & res_arith_0(27));
pos_overflow_0 <= '1' when res_quant_0(13 downto 12) /= (1 downto 0 => res_quant_0(11)) and res_quant_0(13) = '0' else '0';
neg_overflow_0 <= '1' when res_quant_0(13 downto 12) /= (1 downto 0 => res_quant_0(11)) and res_quant_0(13) = '1' else '0';
res_overflow_0 <= to_signed(2047, 12) when pos_overflow_0 = '1' else to_signed(-2048, 12) when neg_overflow_0 = '1' else res_quant_0(11 downto 0);
res_quant_1 <= res_arith_1(27 downto 14) + (to_signed(0, 13) & res_arith_1(27));
pos_overflow_1 <= '1' when res_quant_1(13 downto 12) /= (1 downto 0 => res_quant_1(11)) and res_quant_1(13) = '0' else '0';
neg_overflow_1 <= '1' when res_quant_1(13 downto 12) /= (1 downto 0 => res_quant_1(11)) and res_quant_1(13) = '1' else '0';
res_overflow_1 <= to_signed(2047, 12) when pos_overflow_1 = '1' else to_signed(-2048, 12) when neg_overflow_1 = '1' else res_quant_1(11 downto 0);
end architecture rtl;
I/O units
--
-- This code was automatically generated by the B-ASIC toolbox.
-- Code generation timestamp: (2026-06-25 08:19:29.391510)
-- B-ASIC version: 0.1.0.dev983+unknown.gc02664293
-- URL: https://github.com/b-asic-eda/b-asic
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity x is
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in std_logic_vector(11 downto 0);
p_0_out : out signed(11 downto 0)
);
end entity x;
architecture rtl of x is
signal res_arith_0 : signed(11 downto 0);
signal res_quant_0 : signed(11 downto 0);
signal res_overflow_0 : signed(11 downto 0);
begin
p_0_out <= res_overflow_0;
res_arith_0 <= resize(signed(p_0_in), 12);
res_quant_0 <= res_arith_0(11 downto 0);
res_overflow_0 <= res_quant_0(11 downto 0);
end architecture rtl;
--
-- This code was automatically generated by the B-ASIC toolbox.
-- Code generation timestamp: (2026-06-25 08:19:29.391703)
-- B-ASIC version: 0.1.0.dev983+unknown.gc02664293
-- URL: https://github.com/b-asic-eda/b-asic
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity y is
port (
clk : in std_logic;
en : in std_logic;
schedule_cnt : in unsigned(3 downto 0);
p_0_in : in signed(11 downto 0);
p_0_out : out std_logic_vector(11 downto 0)
);
end entity y;
architecture rtl of y is
signal op_0 : signed(11 downto 0);
signal res_arith_0 : signed(11 downto 0);
signal res_quant_0 : signed(11 downto 0);
signal res_overflow_0 : signed(11 downto 0);
begin
op_0 <= p_0_in;
res_arith_0 <= op_0;
p_0_out <= std_logic_vector(res_overflow_0);
res_quant_0 <= res_arith_0(11 downto 0);
res_overflow_0 <= res_quant_0(11 downto 0);
end architecture rtl;
A number of keyword arguments can be passed to
print() to alter the generated code,
e.g., to add registers on I/O ports, pipeline mux control signals for the top-level,
or forcing a certain register-placement for PEs.
Generating a Testbench¶
To verify that the generated VHDL behaves as expected, a testbench can be
generated from a finite-wordlength Simulation of
the SFG, using the same DataType as for the
code generation.
Here, we drive the filter with a uniform input sequence for a few
repetitions of the schedule period.
Note that we use sfg here,
which is the recreated SFG after scheduling.
This is because, the scheduling pipelined/retimed the algorithm.
The simulation results are then handed to a
CocotbPrinter, which generates a
cocotb testbench that drives the architecture
and asserts that its outputs match the simulated values.
If a VHDL testbench is preferred, VhdlTbPrinter is also available.
from b_asic.tb_printer import CocotbPrinter
cocotb_printer = CocotbPrinter(sim.results)
cocotb_printer.print(arch, schedule, path="generated", simulator = "ghdl", waves=True)
This writes a tb.py file containing the testbench.
Let’s have a look at the generated testbench.
print(Path("generated", "tb.py").read_text())
"""cocotb testbench generated by B-ASIC."""
import csv
import sys
from contextlib import nullcontext
from pathlib import Path
import cocotb
import pytest
from cocotb.clock import Clock
from cocotb.triggers import FallingEdge
from cocotb_tools.runner import get_runner
def test_start():
runner = get_runner(SIMULATOR)
runner.build(sources=SOURCES, hdl_toplevel=ENTITY_NAME)
runner.test(hdl_toplevel=ENTITY_NAME, test_module="tb", waves=WAVES, gui=GUI)
SOURCES = [Path("m0.vhdl"), Path("m1.vhdl"), Path("a.vhdl"), Path("x.vhdl"), Path("y.vhdl"), Path("wdf.vhdl")]
SIMULATOR = "ghdl"
WAVES = True
GUI = False
CSV = False
ASSERTS = True
ENABLE_PIN = True
ENTITY_NAME = "wdf"
SEQUENCE = { 0: {'x_0_in': 309},
6: {'y_0_out': 0},
12: {'x_0_in': 3440},
18: {'y_0_out': 2},
24: {'x_0_in': 3514},
30: {'y_0_out': 4},
36: {'x_0_in': 3206},
42: {'y_0_out': 0},
48: {'x_0_in': 3956},
54: {'y_0_out': 4069},
60: {'x_0_in': 615},
66: {'y_0_out': 3991},
72: {'x_0_in': 3286},
78: {'y_0_out': 3890},
84: {'x_0_in': 496},
90: {'y_0_out': 3683},
96: {'x_0_in': 3905},
102: {'y_0_out': 3646},
108: {'x_0_in': 3153},
114: {'y_0_out': 3711},
120: {'x_0_in': 3081},
126: {'y_0_out': 3846},
132: {'x_0_in': 467},
138: {'y_0_out': 3974},
144: {'x_0_in': 3906},
150: {'y_0_out': 4048},
156: {'x_0_in': 607},
162: {'y_0_out': 3939},
168: {'x_0_in': 843},
174: {'y_0_out': 3859},
180: {'x_0_in': 3770},
186: {'y_0_out': 3769},
192: {'x_0_in': 244},
198: {'y_0_out': 3761},
204: {'x_0_in': 11},
210: {'y_0_out': 4048},
216: {'x_0_in': 3089},
222: {'y_0_out': 137},
228: {'x_0_in': 3103},
234: {'y_0_out': 300},
240: {'x_0_in': 794},
246: {'y_0_out': 348},
252: {'x_0_in': 809},
258: {'y_0_out': 174},
264: {'x_0_in': 199},
270: {'y_0_out': 3967},
276: {'x_0_in': 546},
282: {'y_0_out': 3847},
288: {'x_0_in': 265},
294: {'y_0_out': 3825},
300: {'x_0_in': 3685},
306: {'y_0_out': 3977},
312: {'x_0_in': 3830},
318: {'y_0_out': 155},
324: {'x_0_in': 804},
330: {'y_0_out': 392},
336: {'x_0_in': 3926},
342: {'y_0_out': 512},
348: {'x_0_in': 3636},
354: {'y_0_out': 318},
360: {'x_0_in': 3331},
366: {'y_0_out': 181},
372: {'x_0_in': 723},
378: {'y_0_out': 30},
384: {'x_0_in': 3764},
390: {'y_0_out': 4000},
396: {'x_0_in': 4039},
402: {'y_0_out': 3954},
408: {'x_0_in': 3164},
414: {'y_0_out': 3937},
420: {'x_0_in': 730},
426: {'y_0_out': 3934},
432: {'x_0_in': 280},
438: {'y_0_out': 3937},
444: {'x_0_in': 3508},
450: {'y_0_out': 3945},
456: {'x_0_in': 765},
462: {'y_0_out': 3961},
468: {'x_0_in': 3751},
474: {'y_0_out': 3995},
480: {'x_0_in': 612},
486: {'y_0_out': 4051},
492: {'x_0_in': 969},
498: {'y_0_out': 29},
504: {'x_0_in': 206},
510: {'y_0_out': 114},
516: {'x_0_in': 3332},
522: {'y_0_out': 199},
528: {'x_0_in': 3241},
534: {'y_0_out': 263},
540: {'x_0_in': 378},
546: {'y_0_out': 329},
552: {'x_0_in': 42},
558: {'y_0_out': 278},
564: {'x_0_in': 850},
570: {'y_0_out': 102},
576: {'x_0_in': 3961},
582: {'y_0_out': 4070},
588: {'x_0_in': 3280},
594: {'y_0_out': 3957},
600: {'x_0_in': 3968},
606: {'y_0_out': 3898},
612: {'x_0_in': 235},
618: {'y_0_out': 3996},
624: {'x_0_in': 520},
630: {'y_0_out': 4091},
636: {'x_0_in': 4030},
642: {'y_0_out': 63},
648: {'x_0_in': 445},
654: {'y_0_out': 70},
660: {'x_0_in': 3506},
666: {'y_0_out': 4089},
672: {'x_0_in': 3428},
678: {'y_0_out': 4047},
684: {'x_0_in': 658},
690: {'y_0_out': 4059},
696: {'x_0_in': 3512},
702: {'y_0_out': 42},
708: {'x_0_in': 3088},
714: {'y_0_out': 54},
720: {'x_0_in': 484},
726: {'y_0_out': 31},
732: {'x_0_in': 486},
738: {'y_0_out': 4006},
744: {'x_0_in': 3887},
750: {'y_0_out': 3853},
756: {'x_0_in': 953},
762: {'y_0_out': 3755},
768: {'x_0_in': 3243},
774: {'y_0_out': 3783},
780: {'x_0_in': 3795},
786: {'y_0_out': 3929},
792: {'x_0_in': 3570},
798: {'y_0_out': 70},
804: {'x_0_in': 722},
810: {'y_0_out': 178},
816: {'x_0_in': 3405},
822: {'y_0_out': 199},
828: {'x_0_in': 3209},
834: {'y_0_out': 47},
840: {'x_0_in': 3093},
846: {'y_0_out': 4002},
852: {'x_0_in': 4017},
858: {'y_0_out': 3851},
864: {'x_0_in': 3361},
870: {'y_0_out': 3719},
876: {'x_0_in': 3134},
882: {'y_0_out': 3677},
888: {'x_0_in': 3680},
894: {'y_0_out': 3603},
900: {'x_0_in': 768},
906: {'y_0_out': 3504},
912: {'x_0_in': 385},
918: {'y_0_out': 3428},
924: {'x_0_in': 3305},
930: {'y_0_out': 3510},
936: {'x_0_in': 543},
942: {'y_0_out': 3569},
948: {'x_0_in': 536},
954: {'y_0_out': 3859},
960: {'x_0_in': 105},
966: {'y_0_out': 4016},
972: {'x_0_in': 3529},
978: {'y_0_out': 62},
984: {'x_0_in': 974},
990: {'y_0_out': 173},
996: {'x_0_in': 71},
1002: {'y_0_out': 230},
1008: {'x_0_in': 3270},
1014: {'y_0_out': 230},
1020: {'x_0_in': 3180},
1026: {'y_0_out': 189},
1032: {'x_0_in': 3396},
1038: {'y_0_out': 124},
1044: {'x_0_in': 861},
1050: {'y_0_out': 22},
1056: {'x_0_in': 3425},
1062: {'y_0_out': 4050},
1068: {'x_0_in': 660},
1074: {'y_0_out': 3786},
1080: {'x_0_in': 79},
1086: {'y_0_out': 3747},
1092: {'x_0_in': 4069},
1098: {'y_0_out': 3757},
1104: {'x_0_in': 287},
1110: {'y_0_out': 3835},
1116: {'x_0_in': 616},
1122: {'y_0_out': 3986},
1128: {'x_0_in': 3784},
1134: {'y_0_out': 79},
1140: {'x_0_in': 3284},
1146: {'y_0_out': 243},
1152: {'x_0_in': 117},
1158: {'y_0_out': 319},
1164: {'x_0_in': 3690},
1170: {'y_0_out': 281},
1176: {'x_0_in': 3843},
1182: {'y_0_out': 142},
1188: {'x_0_in': 3292},
1194: {'y_0_out': 4052},
1200: {'x_0_in': 811},
1206: {'y_0_out': 3872},
1212: {'x_0_in': 212},
1218: {'y_0_out': 3750},
1224: {'x_0_in': 101},
1230: {'y_0_out': 3710},
1236: {'x_0_in': 3995},
1242: {'y_0_out': 3759},
1248: {'x_0_in': 544},
1254: {'y_0_out': 3885},
1260: {'x_0_in': 714},
1266: {'y_0_out': 4057},
1272: {'x_0_in': 971},
1278: {'y_0_out': 142},
1284: {'x_0_in': 3857},
1290: {'y_0_out': 296},
1296: {'x_0_in': 1010},
1302: {'y_0_out': 407},
1308: {'x_0_in': 3689},
1314: {'y_0_out': 416},
1320: {'x_0_in': 3256},
1326: {'y_0_out': 443},
1332: {'x_0_in': 3223},
1338: {'y_0_out': 466},
1344: {'x_0_in': 3470},
1350: {'y_0_out': 422},
1356: {'x_0_in': 3223},
1362: {'y_0_out': 307},
1368: {'x_0_in': 739},
1374: {'y_0_out': 4070},
1380: {'x_0_in': 877},
1386: {'y_0_out': 3733},
1392: {'x_0_in': 3124},
1398: {'y_0_out': 3456},
1404: {'x_0_in': 3397},
1410: {'y_0_out': 3539},
1416: {'x_0_in': 3703},
1422: {'y_0_out': 3814},
1428: {'x_0_in': 3625},
1434: {'y_0_out': 3873}}
@cocotb.test()
async def test_one(dut):
clk = Clock(dut.clk, 2, unit="ns")
cocotb.start_soon(clk.start())
max_cycle = max(SEQUENCE.keys())
dut.rst.value = 1
if ENABLE_PIN:
dut.en.value = 0
await FallingEdge(dut.clk)
dut.rst.value = 0
if ENABLE_PIN:
dut.en.value = 1
# Context manager that does nothing if CSV is False
csv_context = Path("waveform.csv").open("w", newline="") if CSV else nullcontext() # noqa: SIM115
with csv_context as f:
writer = csv.writer(f) if CSV else None
if CSV:
writer.writerow(["port_name", "cycle", "value"])
for cycle in range(max_cycle + 1):
if cycle in SEQUENCE:
step = SEQUENCE[cycle]
# Drive inputs and check outputs based on the sequence map
for signal_name, value in step.items():
if CSV:
hw_val = getattr(dut, signal_name).value
if signal_name.endswith(("_in", "_in_re", "_in_im")):
writer.writerow([signal_name, cycle, value]) if CSV else None
getattr(dut, signal_name).value = value
else:
hw_val = getattr(dut, signal_name).value
if CSV and hw_val.is_resolvable:
writer.writerow([signal_name, cycle, int(hw_val)])
if hw_val.is_resolvable:
if ASSERTS:
assert hw_val == value, (
f"Cycle {cycle}: Expected {signal_name} to be {value}, "
f"but got {int(hw_val)}"
)
cocotb.log.info(
f"Cycle {cycle}: {signal_name} = {int(hw_val)} OK"
)
else:
# Skip assertion if value is 'x' (unknown/undefined)
cocotb.log.warning(
f"Cycle {cycle}: Skipping check for {signal_name} - "
f"value is unresolvable (x/z/u)"
)
await FallingEdge(dut.clk)
if __name__ == "__main__":
# forward command-line pytest options (e.g. -k, -q, -s) to allow running this file directly
sys.exit(pytest.main([__file__, *sys.argv[1:]]))
Assuming that GHDL is correctly set up, verification should be as simple as running
python tb.py
in the generated directory, which should run the testbench and report
that all assertions passed.
A waveform file is also generated, which can be rendered using a waveform viewer, e.g., Surfer.
Generating a VHDL LS Configuration¶
To get clickable references, completion, and other IDE features for the generated
VHDL code, e.g., through the
VHDL by VHDL-LS
VS Code extension, a vhdl_ls.toml configuration file for the
VHDL Language Server can be
generated with
print_vhdl_ls_toml().
printer.print_vhdl_ls_toml(arch, path="generated")
Let’s have a look at the generated file.
print(Path("generated", "vhdl_ls.toml").read_text())
standard = "1993"
[libraries]
lib.files = [
'm0.vhdl',
'm1.vhdl',
'a.vhdl',
'x.vhdl',
'y.vhdl',
'wdf.vhdl',
]
Conclusion¶
Congratualations, you have now successfully designed a wave digital filter, derived a time-multiplexed architecture for it, and generated synthesizable VHDL code, along with a testbench for it. The generated code can now be synthesized and implemented on an FPGA or ASIC.
Next Steps¶
Take a look at Examples to see B-ASIC used in more complex situations, play around with a different schedules and assignments for the current algorithm, or try building your own architecture for a different algorithm. If you encounter any issues, or have suggestions for improvements, please open an issue on GitHub.
Total running time of the script: (0 minutes 0.083 seconds)