.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "tutorial/plot_4_codegen.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_tutorial_plot_4_codegen.py: =============== Code Generation =============== Once an :class:`~b_asic.architecture.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. .. GENERATED FROM PYTHON SOURCE LINES 12-15 Recreating the Architecture --------------------------- We start by reproducing the architecture derived in the previous tutorial. .. GENERATED FROM PYTHON SOURCE LINES 15-87 .. code-block:: Python 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", ) .. GENERATED FROM PYTHON SOURCE LINES 88-98 Generating VHDL --------------- HDL is generated by a code printer. Currently, VHDL is supported, which is done through :class:`~b_asic.code_printer.vhdl.vhdl_printer.VhdlPrinter`. The printer needs to know which :class:`~b_asic.data_type.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. .. GENERATED FROM PYTHON SOURCE LINES 98-109 .. code-block:: Python 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) .. GENERATED FROM PYTHON SOURCE LINES 110-111 Generating the VHDL files describing the architecture is then as simple as .. GENERATED FROM PYTHON SOURCE LINES 111-113 .. code-block:: Python printer.print(arch, path="generated") .. GENERATED FROM PYTHON SOURCE LINES 114-118 This writes one file per processing element, I/O unit, and memory, along with a top-level file describing ``arch`` itself. :meth:`~b_asic.code_printer.vhdl.vhdl_printer.VhdlPrinter.get_compile_order` returns the resulting file names in the order in which they can be compiled. .. GENERATED FROM PYTHON SOURCE LINES 118-120 .. code-block:: Python print(printer.get_compile_order(arch)) .. rst-class:: sphx-glr-script-out .. code-block:: none ['m0.vhdl', 'm1.vhdl', 'a.vhdl', 'x.vhdl', 'y.vhdl', 'wdf.vhdl'] .. GENERATED FROM PYTHON SOURCE LINES 121-123 Let's have a look at the generated code, by reading back the file that was written. .. GENERATED FROM PYTHON SOURCE LINES 123-125 .. code-block:: Python from pathlib import Path .. GENERATED FROM PYTHON SOURCE LINES 126-128 Top-level file, containing all units found below, top-level muxes, and the schedule counter. .. GENERATED FROM PYTHON SOURCE LINES 128-130 .. code-block:: Python print(Path("generated", "wdf.vhdl").read_text()) .. rst-class:: sphx-glr-script-out .. code-block:: none -- -- 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; .. GENERATED FROM PYTHON SOURCE LINES 131-132 Memories .. GENERATED FROM PYTHON SOURCE LINES 132-135 .. code-block:: Python print(Path("generated", "m0.vhdl").read_text()) print(Path("generated", "m1.vhdl").read_text()) .. rst-class:: sphx-glr-script-out .. code-block:: none -- -- 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, , {: 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, , {: 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, , {: 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, , {: 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, , {: 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, , {: 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, , {: 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, , {: 8}, 'sym2p0.1') when "1011" => read_adr_0_0_0 <= to_unsigned(1, read_adr_0_0_0'length); -- MemoryVariable(5, , {: 8}, 'sym2p7.1') when "0000" => read_adr_0_0_0 <= to_unsigned(2, read_adr_0_0_0'length); -- MemoryVariable(11, , {: 3}, 'sym2p1.0') when "0001" => read_adr_0_0_0 <= to_unsigned(4, read_adr_0_0_0'length); -- MemoryVariable(7, , {: 8}, 'sym2p2.1') when "0010" => read_adr_0_0_0 <= to_unsigned(3, read_adr_0_0_0'length); -- MemoryVariable(1, , {: 3}, 'sym2p4.1') when "0011" => read_adr_0_0_0 <= to_unsigned(1, read_adr_0_0_0'length); -- MemoryVariable(0, , {: 5}, 'in0.0') when "0100" => read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length); -- MemoryVariable(8, , {: 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, , {: 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, , {: 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, , {: 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, , {: 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, , {: 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, , {: 4}, 'sym2p5.1') when "0000" => read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length); -- MemoryVariable(1, , {: 1}, 'sym2p4.0') when "0001" => read_adr_0_0_0 <= to_unsigned(0, read_adr_0_0_0'length); -- MemoryVariable(11, , {: 4}, 'sym2p1.1') when "0010" => read_adr_0_0_0 <= to_unsigned(2, read_adr_0_0_0'length); -- MemoryVariable(8, , {: 8}, 'sym2p6.1') when "0011" => read_adr_0_0_0 <= to_unsigned(1, read_adr_0_0_0'length); -- MemoryVariable(4, , {: 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; .. GENERATED FROM PYTHON SOURCE LINES 136-137 Adaptor PE .. GENERATED FROM PYTHON SOURCE LINES 137-139 .. code-block:: Python print(Path("generated", "a.vhdl").read_text()) .. rst-class:: sphx-glr-script-out .. code-block:: none -- -- 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; .. GENERATED FROM PYTHON SOURCE LINES 140-141 I/O units .. GENERATED FROM PYTHON SOURCE LINES 141-144 .. code-block:: Python print(Path("generated", "x.vhdl").read_text()) print(Path("generated", "y.vhdl").read_text()) .. rst-class:: sphx-glr-script-out .. code-block:: none -- -- 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; .. GENERATED FROM PYTHON SOURCE LINES 145-149 A number of keyword arguments can be passed to :meth:`~b_asic.code_printer.vhdl.vhdl_printer.VhdlPrinter.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. .. GENERATED FROM PYTHON SOURCE LINES 151-162 Generating a Testbench ---------------------- To verify that the generated VHDL behaves as expected, a testbench can be generated from a finite-wordlength :class:`~b_asic.simulation.Simulation` of the SFG, using the same :class:`~b_asic.data_type.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 :attr:`~b_asic.schedule.Schedule.sfg` here, which is the recreated SFG after scheduling. This is because, the scheduling pipelined/retimed the algorithm. .. GENERATED FROM PYTHON SOURCE LINES 162-168 .. code-block:: Python from b_asic.signal_generator import Uniform from b_asic.simulation import Simulation sim = Simulation(schedule.sfg, [Uniform()], dt) sim.run_for(10 * schedule.schedule_time) .. GENERATED FROM PYTHON SOURCE LINES 169-174 The simulation results are then handed to a :class:`~b_asic.tb_printer.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, :class:`~b_asic.tb_printer.VhdlTbPrinter` is also available. .. GENERATED FROM PYTHON SOURCE LINES 174-179 .. code-block:: Python from b_asic.tb_printer import CocotbPrinter cocotb_printer = CocotbPrinter(sim.results) cocotb_printer.print(arch, schedule, path="generated", simulator = "ghdl", waves=True) .. GENERATED FROM PYTHON SOURCE LINES 180-182 This writes a ``tb.py`` file containing the testbench. Let's have a look at the generated testbench. .. GENERATED FROM PYTHON SOURCE LINES 182-184 .. code-block:: Python print(Path("generated", "tb.py").read_text()) .. rst-class:: sphx-glr-script-out .. code-block:: none """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:]])) .. GENERATED FROM PYTHON SOURCE LINES 185-195 Assuming that GHDL is correctly set up, verification should be as simple as running .. code-block:: bash 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 `_. .. GENERATED FROM PYTHON SOURCE LINES 197-206 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 :meth:`~b_asic.code_printer.vhdl.vhdl_printer.VhdlPrinter.print_vhdl_ls_toml`. .. GENERATED FROM PYTHON SOURCE LINES 206-208 .. code-block:: Python printer.print_vhdl_ls_toml(arch, path="generated") .. GENERATED FROM PYTHON SOURCE LINES 209-210 Let's have a look at the generated file. .. GENERATED FROM PYTHON SOURCE LINES 210-212 .. code-block:: Python print(Path("generated", "vhdl_ls.toml").read_text()) .. rst-class:: sphx-glr-script-out .. code-block:: none standard = "1993" [libraries] lib.files = [ 'm0.vhdl', 'm1.vhdl', 'a.vhdl', 'x.vhdl', 'y.vhdl', 'wdf.vhdl', ] .. GENERATED FROM PYTHON SOURCE LINES 213-219 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. .. GENERATED FROM PYTHON SOURCE LINES 221-228 Next Steps ---------- Take a look at :ref:`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 `_. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 0.083 seconds) .. _sphx_glr_download_tutorial_plot_4_codegen.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_4_codegen.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_4_codegen.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_4_codegen.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_