Skip to content
Snippets Groups Projects
Commit b991d33e authored by Mikael Henriksson's avatar Mikael Henriksson :runner:
Browse files

codegen: add synchronous write address generation to memory based HDL generation

parent f6ad95ac
No related branches found
No related tags found
1 merge request!250Changes to code generation as a result of FPL-2023
...@@ -2,16 +2,12 @@ ...@@ -2,16 +2,12 @@
Module for code generation of VHDL architectures. Module for code generation of VHDL architectures.
""" """
from io import TextIOWrapper from io import TextIOWrapper
from typing import Dict, Optional, Set, cast from typing import Set, cast
from b_asic.codegen import vhdl from b_asic.codegen import vhdl
from b_asic.codegen.vhdl import VHDL_TAB from b_asic.codegen.vhdl import VHDL_TAB
from b_asic.process import MemoryVariable, PlainMemoryVariable from b_asic.process import MemoryVariable, PlainMemoryVariable
from b_asic.resources import ( from b_asic.resources import ProcessCollection, _ForwardBackwardTable
ProcessCollection,
_ForwardBackwardEntry,
_ForwardBackwardTable,
)
def write_memory_based_storage( def write_memory_based_storage(
...@@ -22,6 +18,7 @@ def write_memory_based_storage( ...@@ -22,6 +18,7 @@ def write_memory_based_storage(
read_ports: int, read_ports: int,
write_ports: int, write_ports: int,
total_ports: int, total_ports: int,
input_sync: bool = True,
): ):
""" """
Generate the VHDL architecture for a memory based architecture from a process collection of memory variables. Generate the VHDL architecture for a memory based architecture from a process collection of memory variables.
...@@ -44,6 +41,10 @@ def write_memory_based_storage( ...@@ -44,6 +41,10 @@ def write_memory_based_storage(
Number of write ports. Number of write ports.
total_ports : int total_ports : int
Total concurrent memory accesses possible. Total concurrent memory accesses possible.
input_sync : bool, default: True
Add registers to the input signals (enable signal and data input signals).
Adding registers to the inputs allow pipelining of address generation (which is added automatically).
For large interleavers, this can improve timing significantly.
""" """
# Code settings # Code settings
...@@ -67,7 +68,9 @@ def write_memory_based_storage( ...@@ -67,7 +68,9 @@ def write_memory_based_storage(
vhdl.common.write_type_decl( vhdl.common.write_type_decl(
f, 'mem_type', 'array(0 to MEM_DEPTH-1) of std_logic_vector(MEM_WL-1 downto 0)' f, 'mem_type', 'array(0 to MEM_DEPTH-1) of std_logic_vector(MEM_WL-1 downto 0)'
) )
vhdl.common.write_signal_decl(f, 'memory', 'mem_type', name_pad=14) vhdl.common.write_signal_decl(
f, name='memory', type='mem_type', name_pad=14, vivado_ram_style='distributed'
)
for i in range(read_ports): for i in range(read_ports):
vhdl.common.write_signal_decl( vhdl.common.write_signal_decl(
f, f'read_port_{i}', 'std_logic_vector(MEM_WL-1 downto 0)', name_pad=14 f, f'read_port_{i}', 'std_logic_vector(MEM_WL-1 downto 0)', name_pad=14
...@@ -86,40 +89,63 @@ def write_memory_based_storage( ...@@ -86,40 +89,63 @@ def write_memory_based_storage(
vhdl.common.write_signal_decl(f, f'write_en_{i}', 'std_logic', name_pad=14) vhdl.common.write_signal_decl(f, f'write_en_{i}', 'std_logic', name_pad=14)
# Schedule time counter # Schedule time counter
f.write('\n') f.write(f'\n{VHDL_TAB}-- Schedule counter\n')
f.write(f'{VHDL_TAB}-- Schedule counter\n')
vhdl.common.write_signal_decl( vhdl.common.write_signal_decl(
f, f,
name='schedule_cnt', name='schedule_cnt',
type=f'integer range 0 to {schedule_time}-1', type=f'integer range 0 to {schedule_time}-1',
name_pad=14, name_pad=14,
) )
f.write('\n')
# Input sync signals
if input_sync:
f.write(f'\n{VHDL_TAB}-- Input synchronization\n')
for i in range(read_ports):
vhdl.common.write_signal_decl(
f, f'p_{i}_in_sync', 'std_logic_vector(WL-1 downto 0)', name_pad=14
)
# #
# Architecture body begin # Architecture body begin
# #
f.write(f'begin\n\n') f.write(f'\nbegin\n\n')
f.write(f'{VHDL_TAB}-- Schedule counter\n') f.write(f'{VHDL_TAB}-- Schedule counter\n')
vhdl.common.write_synchronous_process( vhdl.common.write_synchronous_process(
f=f, f=f,
name='schedule_cnt_proc', name='schedule_cnt_proc',
clk='clk', clk='clk',
indent=len(1 * VHDL_TAB),
body=( body=(
f'{0*VHDL_TAB}if en = \'1\' then\n' f'{0*VHDL_TAB}if rst = \'1\' then\n'
f'{1*VHDL_TAB}if schedule_cnt = {schedule_time}-1 then\n' f'{1*VHDL_TAB}schedule_cnt <= 0;\n'
f'{2*VHDL_TAB}schedule_cnt <= 0;\n' f'{0*VHDL_TAB}else\n'
f'{1*VHDL_TAB}else\n' f'{1*VHDL_TAB}if en = \'1\' then\n'
f'{2*VHDL_TAB}schedule_cnt <= schedule_cnt + 1;\n' f'{2*VHDL_TAB}if schedule_cnt = {schedule_time-1} then\n'
f'{3*VHDL_TAB}schedule_cnt <= 0;\n'
f'{2*VHDL_TAB}else\n'
f'{3*VHDL_TAB}schedule_cnt <= schedule_cnt + 1;\n'
f'{2*VHDL_TAB}end if;\n'
f'{1*VHDL_TAB}end if;\n' f'{1*VHDL_TAB}end if;\n'
f'{0*VHDL_TAB}end if;\n' f'{0*VHDL_TAB}end if;\n'
), ),
) )
if input_sync:
f.write(f'\n{VHDL_TAB}-- Input synchronization\n')
vhdl.common.write_synchronous_process_prologue(
f=f,
name='input_sync_proc',
clk='clk',
)
for i in range(read_ports):
f.write(f'{3*VHDL_TAB}p_{i}_in_sync <= p_{i}_in;\n')
vhdl.common.write_synchronous_process_epilogue(
f=f,
name='input_sync_proc',
clk='clk',
)
# Infer memory # Infer memory
f.write('\n') f.write(f'\n{VHDL_TAB}-- Memory\n')
f.write(f'{VHDL_TAB}-- Memory\n')
vhdl.common.write_asynchronous_read_memory( vhdl.common.write_asynchronous_read_memory(
f=f, f=f,
clk='clk', clk='clk',
...@@ -134,73 +160,96 @@ def write_memory_based_storage( ...@@ -134,73 +160,96 @@ def write_memory_based_storage(
}, },
) )
f.write(f'\n{VHDL_TAB}-- Memory writes\n') # Write address generation
f.write(f'{VHDL_TAB}process(schedule_cnt)\n') f.write(f'\n{VHDL_TAB}-- Memory write address generation\n')
f.write(f'{VHDL_TAB}begin\n') if input_sync:
vhdl.common.write_synchronous_process_prologue(
f.write(f'{2*VHDL_TAB}case schedule_cnt is\n') f, clk="clk", name="mem_write_address_proc"
)
else:
vhdl.common.write_process_prologue(
f, sensitivity_list="schedule_cnt", name="mem_write_address_proc"
)
f.write(f'{3*VHDL_TAB}case schedule_cnt is\n')
for i, collection in enumerate(assignment): for i, collection in enumerate(assignment):
for mv in collection: for mv in collection:
mv = cast(MemoryVariable, mv) mv = cast(MemoryVariable, mv)
if mv.execution_time: if mv.execution_time:
f.write(f'{3*VHDL_TAB}-- {mv!r}\n') f.write(f'{4*VHDL_TAB}-- {mv!r}\n')
f.write(f'{3*VHDL_TAB}when {mv.start_time} =>\n') f.write(f'{4*VHDL_TAB}when {(mv.start_time) % schedule_time} =>\n')
f.write(f'{4*VHDL_TAB}write_adr_0 <= {i};\n') f.write(f'{5*VHDL_TAB}write_adr_0 <= {i};\n')
f.write(f'{4*VHDL_TAB}write_en_0 <= \'1\';\n') f.write(f'{5*VHDL_TAB}write_en_0 <= \'1\';\n')
f.write(f'{3*VHDL_TAB}when others =>\n') f.write(f'{4*VHDL_TAB}when others =>\n')
f.write(f'{4*VHDL_TAB}write_adr_0 <= 0;\n') f.write(f'{5*VHDL_TAB}write_adr_0 <= 0;\n')
f.write(f'{4*VHDL_TAB}write_en_0 <= \'0\';\n') f.write(f'{5*VHDL_TAB}write_en_0 <= \'0\';\n')
f.write(f'{2*VHDL_TAB}end case;\n') f.write(f'{3*VHDL_TAB}end case;\n')
if input_sync:
f.write(f'{1*VHDL_TAB}end process;\n') vhdl.common.write_synchronous_process_epilogue(
f, clk="clk", name="mem_write_address_proc"
f.write(f'\n{VHDL_TAB}-- Memory reads\n') )
f.write(f'{VHDL_TAB}process(schedule_cnt)\n') else:
f.write(f'{VHDL_TAB}begin\n') vhdl.common.write_process_epilogue(
f, sensitivity_list="clk", name="mem_write_address_proc"
)
f.write(f'{2*VHDL_TAB}case schedule_cnt is\n') # Read address generation
f.write(f'\n{VHDL_TAB}-- Memory read address generation\n')
vhdl.common.write_synchronous_process_prologue(
f, clk="clk", name="mem_read_address_proc"
)
f.write(f'{3*VHDL_TAB}case schedule_cnt is\n')
for i, collection in enumerate(assignment): for i, collection in enumerate(assignment):
for mv in collection: for mv in collection:
mv = cast(PlainMemoryVariable, mv) mv = cast(PlainMemoryVariable, mv)
f.write(f'{3*VHDL_TAB}-- {mv!r}\n') f.write(f'{4*VHDL_TAB}-- {mv!r}\n')
for read_time in mv.reads.values(): for read_time in mv.reads.values():
f.write( f.write(
f'{3*VHDL_TAB}when' f'{4*VHDL_TAB}when'
f' {(mv.start_time + read_time) % schedule_time} =>\n' f' {(mv.start_time+read_time-int(not(input_sync))) % schedule_time} =>\n'
) )
f.write(f'{4*VHDL_TAB}read_adr_0 <= {i};\n') f.write(f'{5*VHDL_TAB}read_adr_0 <= {i};\n')
f.write(f'{4*VHDL_TAB}read_en_0 <= \'1\';\n') f.write(f'{5*VHDL_TAB}read_en_0 <= \'1\';\n')
f.write(f'{3*VHDL_TAB}when others =>\n') f.write(f'{4*VHDL_TAB}when others =>\n')
f.write(f'{4*VHDL_TAB}read_adr_0 <= 0;\n') f.write(f'{5*VHDL_TAB}read_adr_0 <= 0;\n')
f.write(f'{4*VHDL_TAB}read_en_0 <= \'0\';\n') f.write(f'{5*VHDL_TAB}read_en_0 <= \'0\';\n')
f.write(f'{2*VHDL_TAB}end case;\n') f.write(f'{3*VHDL_TAB}end case;\n')
f.write(f'{1*VHDL_TAB}end process;\n\n') vhdl.common.write_synchronous_process_epilogue(
f, clk="clk", name="mem_read_address_proc"
)
f.write(f'{1*VHDL_TAB}-- Input and output assignment\n') f.write(f'\n{1*VHDL_TAB}-- Input and output assignment\n')
f.write(f'{1*VHDL_TAB}write_port_0 <= p_0_in;\n') if input_sync:
f.write(f'{1*VHDL_TAB}write_port_0 <= p_0_in_sync;\n')
else:
f.write(f'{1*VHDL_TAB}write_port_0 <= p_0_in;\n')
p_zero_exec = filter( p_zero_exec = filter(
lambda p: p.execution_time == 0, (p for pc in assignment for p in pc) lambda p: p.execution_time == 0, (p for pc in assignment for p in pc)
) )
vhdl.common.write_synchronous_process_prologue( vhdl.common.write_synchronous_process_prologue(
f, f,
clk='clk', clk='clk',
indent=len(VHDL_TAB),
name='output_reg_proc', name='output_reg_proc',
) )
f.write(f'{3*VHDL_TAB}case schedule_cnt is\n') f.write(f'{3*VHDL_TAB}case schedule_cnt is\n')
for p in p_zero_exec: for p in p_zero_exec:
f.write(f'{4*VHDL_TAB}when {p.start_time} => p_0_out <= p_0_in;\n') if input_sync:
f.write(
f'{4*VHDL_TAB}when {(p.start_time+1)%schedule_time} => p_0_out <='
' p_0_in_sync;\n'
)
else:
f.write(
f'{4*VHDL_TAB}when {(p.start_time)%schedule_time} => p_0_out <='
' p_0_in;\n'
)
f.write(f'{4*VHDL_TAB}when others => p_0_out <= read_port_0;\n') f.write(f'{4*VHDL_TAB}when others => p_0_out <= read_port_0;\n')
f.write(f'{3*VHDL_TAB}end case;\n') f.write(f'{3*VHDL_TAB}end case;\n')
vhdl.common.write_synchronous_process_epilogue( vhdl.common.write_synchronous_process_epilogue(
f, f,
clk='clk', clk='clk',
indent=len(VHDL_TAB),
name='output_reg_proc', name='output_reg_proc',
) )
f.write(f'\nend architecture {architecture_name};')
f.write('\n')
f.write(f'end architecture {architecture_name};')
def write_register_based_storage( def write_register_based_storage(
...@@ -231,10 +280,9 @@ def write_register_based_storage( ...@@ -231,10 +280,9 @@ def write_register_based_storage(
name_pad=14, name_pad=14,
default_value='0', default_value='0',
) )
f.write('\n')
# Shift register # Shift register
f.write(f'{VHDL_TAB}-- Shift register\n') f.write(f'\n{VHDL_TAB}-- Shift register\n')
vhdl.common.write_type_decl( vhdl.common.write_type_decl(
f, f,
name='shift_reg_type', name='shift_reg_type',
...@@ -247,6 +295,16 @@ def write_register_based_storage( ...@@ -247,6 +295,16 @@ def write_register_based_storage(
name_pad=14, name_pad=14,
) )
# Output mux selector
f.write(f'\n{VHDL_TAB}-- Output mux select signal\n')
output_regs = {entry.outputs_from for entry in forward_backward_table.table}
vhdl.common.write_signal_decl(
f,
name='out_mux_sel',
type=f'integer range 0 to {len(output_regs)-1}',
name_pad=14,
)
# #
# Architecture body begin # Architecture body begin
# #
...@@ -257,7 +315,6 @@ def write_register_based_storage( ...@@ -257,7 +315,6 @@ def write_register_based_storage(
f=f, f=f,
name='schedule_cnt_proc', name='schedule_cnt_proc',
clk='clk', clk='clk',
indent=len(1 * VHDL_TAB),
body=( body=(
f'{0*VHDL_TAB}if en = \'1\' then\n' f'{0*VHDL_TAB}if en = \'1\' then\n'
f'{1*VHDL_TAB}if schedule_cnt = {schedule_time}-1 then\n' f'{1*VHDL_TAB}if schedule_cnt = {schedule_time}-1 then\n'
...@@ -269,15 +326,13 @@ def write_register_based_storage( ...@@ -269,15 +326,13 @@ def write_register_based_storage(
), ),
) )
# Shift register multiplexer logic
f.write(f'\n{VHDL_TAB}-- Multiplexers for shift register\n') f.write(f'\n{VHDL_TAB}-- Multiplexers for shift register\n')
vhdl.common.write_synchronous_process_prologue( vhdl.common.write_synchronous_process_prologue(
f, f,
clk='clk', clk='clk',
name='shift_reg_proc', name='shift_reg_proc',
indent=len(VHDL_TAB),
) )
# Default for all register
f.write(f'{3*VHDL_TAB}-- Default case\n') f.write(f'{3*VHDL_TAB}-- Default case\n')
f.write(f'{3*VHDL_TAB}shift_reg(0) <= p_0_in;\n') f.write(f'{3*VHDL_TAB}shift_reg(0) <= p_0_in;\n')
for reg_idx in range(1, reg_cnt): for reg_idx in range(1, reg_cnt):
...@@ -296,17 +351,17 @@ def write_register_based_storage( ...@@ -296,17 +351,17 @@ def write_register_based_storage(
f, f,
clk='clk', clk='clk',
name='shift_reg_proc', name='shift_reg_proc',
indent=len(VHDL_TAB),
) )
# Output multiplexer logic
f.write(f'\n{VHDL_TAB}-- Output muliplexer\n') f.write(f'\n{VHDL_TAB}-- Output muliplexer\n')
f.write(f'\n{VHDL_TAB}-- {output_regs}\n')
f.write(f'\n{VHDL_TAB}-- { list(range(len(output_regs))) }\n')
vhdl.common.write_synchronous_process_prologue( vhdl.common.write_synchronous_process_prologue(
f, f,
clk='clk', clk='clk',
name='out_mux_proc', name='out_mux_proc',
indent=len(VHDL_TAB),
) )
f.write(f'{3*VHDL_TAB}-- Default case\n') f.write(f'{3*VHDL_TAB}-- Default case\n')
f.write(f'{3*VHDL_TAB}p_0_out <= shift_reg({reg_cnt-1});\n') f.write(f'{3*VHDL_TAB}p_0_out <= shift_reg({reg_cnt-1});\n')
f.write(f'{3*VHDL_TAB}case schedule_cnt is\n') f.write(f'{3*VHDL_TAB}case schedule_cnt is\n')
...@@ -322,12 +377,10 @@ def write_register_based_storage( ...@@ -322,12 +377,10 @@ def write_register_based_storage(
) )
f.write(f'{4*VHDL_TAB}when others => null;\n') f.write(f'{4*VHDL_TAB}when others => null;\n')
f.write(f'{3*VHDL_TAB}end case;\n') f.write(f'{3*VHDL_TAB}end case;\n')
vhdl.common.write_synchronous_process_epilogue( vhdl.common.write_synchronous_process_epilogue(
f, f,
clk='clk', clk='clk',
name='out_mux_proc', name='out_mux_proc',
indent=len(VHDL_TAB),
) )
f.write(f'end architecture {architecture_name};') f.write(f'end architecture {architecture_name};')
...@@ -66,6 +66,8 @@ def write_signal_decl( ...@@ -66,6 +66,8 @@ def write_signal_decl(
type: str, type: str,
default_value: Optional[str] = None, default_value: Optional[str] = None,
name_pad: Optional[int] = None, name_pad: Optional[int] = None,
vivado_ram_style: Optional[str] = None,
quartus_ram_style: Optional[str] = None,
): ):
""" """
Create a VHDL signal declaration: :: Create a VHDL signal declaration: ::
...@@ -80,10 +82,16 @@ def write_signal_decl( ...@@ -80,10 +82,16 @@ def write_signal_decl(
Signal name. Signal name.
type : str type : str
Signal type. Signal type.
default_value : str, optional default_value : string, optional
An optional default value to the signal. An optional default value to the signal.
name_pad : int, optional name_pad : int, optional
An optional left padding value applied to the name. An optional left padding value applied to the name.
vivado_ram_style : string, optional
An optional Xilinx Vivado RAM style attribute to apply to this signal delcaration.
If set, exactly one of: "block", "distributed", "registers", "ultra", "mixed" or "auto".
quartus_ram_style : string, optional
An optional Quartus Prime RAM style attribute to apply to this signal delcaration.
If set, exactly one of: "M4K", "M9K", "M10K", "M20K", "M144K", "MLAB" or "logic".
""" """
# Spacing of VHDL signals declaration always with a single tab # Spacing of VHDL signals declaration always with a single tab
name_pad = 0 if name_pad is None else name_pad name_pad = 0 if name_pad is None else name_pad
...@@ -91,6 +99,18 @@ def write_signal_decl( ...@@ -91,6 +99,18 @@ def write_signal_decl(
if default_value is not None: if default_value is not None:
f.write(f' := {default_value}') f.write(f' := {default_value}')
f.write(f';\n') f.write(f';\n')
if vivado_ram_style:
f.write(f'{VHDL_TAB}attribute ram_style : string;\n')
f.write(
f'{VHDL_TAB}attribute ram_style of {name} : signal is'
f' "{vivado_ram_style}";\n'
)
if quartus_ram_style:
f.write(f'{VHDL_TAB}attribute ramstyle : string;\n')
f.write(
f'{VHDL_TAB}attribute ramstyle of {name} : signal is'
f' "{quartus_ram_style}";\n'
)
def write_constant_decl( def write_constant_decl(
...@@ -141,42 +161,63 @@ def write_type_decl( ...@@ -141,42 +161,63 @@ def write_type_decl(
f.write(f'{VHDL_TAB}type {name} is {alias};\n') f.write(f'{VHDL_TAB}type {name} is {alias};\n')
def write_synchronous_process( def write_process_prologue(
f: TextIOWrapper, f: TextIOWrapper,
clk: str, sensitivity_list: str,
body: str, indent: str = VHDL_TAB,
indent: Optional[int] = 0,
name: Optional[str] = None, name: Optional[str] = None,
): ):
""" """
Write a regular VHDL synchronous process with a single clock object in the sensitivity list triggering Write only the prologue of a regular VHDL process with a user provided sensitivity list.
a rising edge block by some body of VHDL code. This method should almost always guarantely be followed by a write_asynchronous_process_epilogue.
Parameters Parameters
---------- ----------
f : :class:`io.TextIOWrapper` f : :class:`io.TextIOWrapper`
The TextIOWrapper to write the VHDL code onto. The TextIOWrapper object to write the type declaration to.
clk : str sensitivity_list : str
Name of the clock. Content of the process sensitivity list.
body : str indent : str, default: 1*VHDL_TAB
Body of the `if rising_edge(clk) then` block. Indentation used in the process. This string is applied to the first written line of all output.
indent : Optional[int]
Indent this process block with `indent` columns
name : Optional[str] name : Optional[str]
An optional name for the process An optional name for the process.
""" """
space = '' if indent is None else ' ' * indent if name is not None:
write_synchronous_process_prologue(f, clk, indent, name) f.write(f'{indent}{name}: process({sensitivity_list})\n')
for line in body.split('\n'): else:
if len(line): f.write(f'{indent}process({sensitivity_list})\n')
f.write(f'{space}{2*VHDL_TAB}{line}\n') f.write(f'{indent}begin\n')
write_synchronous_process_epilogue(f, clk, indent, name)
def write_process_epilogue(
f: TextIOWrapper,
sensitivity_list: Optional[str] = None,
indent: str = VHDL_TAB,
name: Optional[str] = None,
):
"""
Parameters
----------
f : :class:`io.TextIOWrapper`
The TextIOWrapper object to write the type declaration to.
sensitivity_list : str
Content of the process sensitivity list. Not needed when writing the epligoue.
indent : str, default: 1*VHDL_TAB
Indentation used in the process. This string is applied to the first written line of all output.
name : Optional[str]
An optional name of the ending process.
"""
_ = sensitivity_list
f.write(f'{indent}end process')
if name is not None:
f.write(' ' + name)
f.write(';\n')
def write_synchronous_process_prologue( def write_synchronous_process_prologue(
f: TextIOWrapper, f: TextIOWrapper,
clk: str, clk: str,
indent: Optional[int] = 0, indent: str = VHDL_TAB,
name: Optional[str] = None, name: Optional[str] = None,
): ):
""" """
...@@ -190,24 +231,19 @@ def write_synchronous_process_prologue( ...@@ -190,24 +231,19 @@ def write_synchronous_process_prologue(
The TextIOWrapper to write the VHDL code onto. The TextIOWrapper to write the VHDL code onto.
clk : str clk : str
Name of the clock. Name of the clock.
indent : Optional[int] indent : str, default: VHDL_TAB
Indent this process block with `indent` columns Indentation used in the process. This string is applied to the first written line of all output.
name : Optional[str] name : Optional[str]
An optional name for the process An optional name for the process.
""" """
space = '' if indent is None else ' ' * indent write_process_prologue(f, sensitivity_list=clk, indent=indent, name=name)
if name is not None: f.write(f'{indent}{VHDL_TAB}if rising_edge(clk) then\n')
f.write(f'{space}{name}: process({clk})\n')
else:
f.write(f'{space}process({clk})\n')
f.write(f'{space}begin\n')
f.write(f'{space}{VHDL_TAB}if rising_edge(clk) then\n')
def write_synchronous_process_epilogue( def write_synchronous_process_epilogue(
f: TextIOWrapper, f: TextIOWrapper,
clk: Optional[str], clk: Optional[str],
indent: Optional[int] = 0, indent: str = VHDL_TAB,
name: Optional[str] = None, name: Optional[str] = None,
): ):
""" """
...@@ -221,18 +257,45 @@ def write_synchronous_process_epilogue( ...@@ -221,18 +257,45 @@ def write_synchronous_process_epilogue(
The TextIOWrapper to write the VHDL code onto. The TextIOWrapper to write the VHDL code onto.
clk : str clk : str
Name of the clock. Name of the clock.
indent : Optional[int] indent : str, default: VHDL_TAB
Indent this process block with `indent` columns Indent this process block with `indent` columns
name : Optional[str] name : Optional[str]
An optional name for the process An optional name for the process
""" """
_ = clk _ = clk
space = '' if indent is None else ' ' * indent f.write(f'{indent}{VHDL_TAB}end if;\n')
f.write(f'{space}{VHDL_TAB}end if;\n') write_process_epilogue(f, sensitivity_list=clk, indent=indent, name=name)
f.write(f'{space}end process')
if name is not None:
f.write(' ' + name) def write_synchronous_process(
f.write(';\n') f: TextIOWrapper,
clk: str,
body: str,
indent: str = VHDL_TAB,
name: Optional[str] = None,
):
"""
Write a regular VHDL synchronous process with a single clock object in the sensitivity list triggering
a rising edge block by some body of VHDL code.
Parameters
----------
f : :class:`io.TextIOWrapper`
The TextIOWrapper to write the VHDL code onto.
clk : str
Name of the clock.
body : str
Body of the `if rising_edge(clk) then` block.
indent : int, default: VHDL_TAB
Indent this process block with `indent` columns
name : Optional[str]
An optional name for the process
"""
write_synchronous_process_prologue(f, clk, indent, name)
for line in body.split('\n'):
if len(line):
f.write(f'{indent}{2*VHDL_TAB}{line}\n')
write_synchronous_process_epilogue(f, clk, indent, name)
def write_synchronous_memory( def write_synchronous_memory(
...@@ -260,7 +323,7 @@ def write_synchronous_memory( ...@@ -260,7 +323,7 @@ def write_synchronous_memory(
""" """
assert len(read_ports) >= 1 assert len(read_ports) >= 1
assert len(write_ports) >= 1 assert len(write_ports) >= 1
write_synchronous_process_prologue(f, clk=clk, name=name, indent=len(VHDL_TAB)) write_synchronous_process_prologue(f, clk=clk, name=name)
for read_name, address, re in read_ports: for read_name, address, re in read_ports:
f.write(f'{3*VHDL_TAB}if {re} = \'1\' then\n') f.write(f'{3*VHDL_TAB}if {re} = \'1\' then\n')
f.write(f'{4*VHDL_TAB}{read_name} <= memory({address});\n') f.write(f'{4*VHDL_TAB}{read_name} <= memory({address});\n')
...@@ -269,7 +332,7 @@ def write_synchronous_memory( ...@@ -269,7 +332,7 @@ def write_synchronous_memory(
f.write(f'{3*VHDL_TAB}if {we} = \'1\' then\n') f.write(f'{3*VHDL_TAB}if {we} = \'1\' then\n')
f.write(f'{4*VHDL_TAB}memory({address}) <= {write_name};\n') f.write(f'{4*VHDL_TAB}memory({address}) <= {write_name};\n')
f.write(f'{3*VHDL_TAB}end if;\n') f.write(f'{3*VHDL_TAB}end if;\n')
write_synchronous_process_epilogue(f, clk=clk, name=name, indent=len(VHDL_TAB)) write_synchronous_process_epilogue(f, clk=clk, name=name)
def write_asynchronous_read_memory( def write_asynchronous_read_memory(
...@@ -280,7 +343,7 @@ def write_asynchronous_read_memory( ...@@ -280,7 +343,7 @@ def write_asynchronous_read_memory(
name: Optional[str] = None, name: Optional[str] = None,
): ):
""" """
Infer a VHDL synchronous reads and writes. Infer a VHDL memory with synchronous writes and asynchronous reads.
Parameters Parameters
---------- ----------
...@@ -297,11 +360,11 @@ def write_asynchronous_read_memory( ...@@ -297,11 +360,11 @@ def write_asynchronous_read_memory(
""" """
assert len(read_ports) >= 1 assert len(read_ports) >= 1
assert len(write_ports) >= 1 assert len(write_ports) >= 1
write_synchronous_process_prologue(f, clk=clk, name=name, indent=len(VHDL_TAB)) write_synchronous_process_prologue(f, clk=clk, name=name)
for write_name, address, we in write_ports: for write_name, address, we in write_ports:
f.write(f'{3*VHDL_TAB}if {we} = \'1\' then\n') f.write(f'{3*VHDL_TAB}if {we} = \'1\' then\n')
f.write(f'{4*VHDL_TAB}memory({address}) <= {write_name};\n') f.write(f'{4*VHDL_TAB}memory({address}) <= {write_name};\n')
f.write(f'{3*VHDL_TAB}end if;\n') f.write(f'{3*VHDL_TAB}end if;\n')
write_synchronous_process_epilogue(f, clk=clk, name=name, indent=len(VHDL_TAB)) write_synchronous_process_epilogue(f, clk=clk, name=name)
for read_name, address, _ in read_ports: for read_name, address, _ in read_ports:
f.write(f'{1*VHDL_TAB}{read_name} <= memory({address});\n') f.write(f'{1*VHDL_TAB}{read_name} <= memory({address});\n')
...@@ -931,6 +931,7 @@ class ProcessCollection: ...@@ -931,6 +931,7 @@ class ProcessCollection:
read_ports: int = 1, read_ports: int = 1,
write_ports: int = 1, write_ports: int = 1,
total_ports: int = 2, total_ports: int = 2,
input_sync: bool = True,
): ):
""" """
Generate VHDL code for memory based storage of processes (MemoryVariables). Generate VHDL code for memory based storage of processes (MemoryVariables).
...@@ -960,6 +961,10 @@ class ProcessCollection: ...@@ -960,6 +961,10 @@ class ProcessCollection:
total_ports : int, default: 2 total_ports : int, default: 2
The total number of ports used when splitting process collection based on The total number of ports used when splitting process collection based on
memory variable access. memory variable access.
input_sync : bool, default: True
Add registers to the input signals (enable signal and data input signals).
Adding registers to the inputs allow pipelining of address generation (which is added automatically).
For large interleavers, this can improve timing significantly.
""" """
# Check that this is a ProcessCollection of (Plain)MemoryVariables # Check that this is a ProcessCollection of (Plain)MemoryVariables
is_memory_variable = all( is_memory_variable = all(
...@@ -1024,6 +1029,7 @@ class ProcessCollection: ...@@ -1024,6 +1029,7 @@ class ProcessCollection:
read_ports=read_ports, read_ports=read_ports,
write_ports=write_ports, write_ports=write_ports,
total_ports=total_ports, total_ports=total_ports,
input_sync=input_sync,
) )
def generate_register_based_storage_vhdl( def generate_register_based_storage_vhdl(
......
...@@ -62,7 +62,7 @@ class TestProcessCollectionPlainMemoryVariable: ...@@ -62,7 +62,7 @@ class TestProcessCollectionPlainMemoryVariable:
def test_generate_register_based_vhdl(self): def test_generate_register_based_vhdl(self):
for rows in [2, 3, 4, 5, 7]: for rows in [2, 3, 4, 5, 7]:
generate_matrix_transposer( generate_matrix_transposer(
rows, min_lifetime=1 rows, min_lifetime=0
).generate_register_based_storage_vhdl( ).generate_register_based_storage_vhdl(
filename=f'b_asic/codegen/testbench/streaming_matrix_transposition_register_{rows}x{rows}.vhdl', filename=f'b_asic/codegen/testbench/streaming_matrix_transposition_register_{rows}x{rows}.vhdl',
entity_name=f'streaming_matrix_transposition_register_{rows}x{rows}', entity_name=f'streaming_matrix_transposition_register_{rows}x{rows}',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment