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 @@
Module for code generation of VHDL architectures.
"""
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.vhdl import VHDL_TAB
from b_asic.process import MemoryVariable, PlainMemoryVariable
from b_asic.resources import (
ProcessCollection,
_ForwardBackwardEntry,
_ForwardBackwardTable,
)
from b_asic.resources import ProcessCollection, _ForwardBackwardTable
def write_memory_based_storage(
......@@ -22,6 +18,7 @@ def write_memory_based_storage(
read_ports: int,
write_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.
......@@ -44,6 +41,10 @@ def write_memory_based_storage(
Number of write ports.
total_ports : int
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
......@@ -67,7 +68,9 @@ def write_memory_based_storage(
vhdl.common.write_type_decl(
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):
vhdl.common.write_signal_decl(
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(
vhdl.common.write_signal_decl(f, f'write_en_{i}', 'std_logic', name_pad=14)
# Schedule time counter
f.write('\n')
f.write(f'{VHDL_TAB}-- Schedule counter\n')
f.write(f'\n{VHDL_TAB}-- Schedule counter\n')
vhdl.common.write_signal_decl(
f,
name='schedule_cnt',
type=f'integer range 0 to {schedule_time}-1',
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
#
f.write(f'begin\n\n')
f.write(f'\nbegin\n\n')
f.write(f'{VHDL_TAB}-- Schedule counter\n')
vhdl.common.write_synchronous_process(
f=f,
name='schedule_cnt_proc',
clk='clk',
indent=len(1 * VHDL_TAB),
body=(
f'{0*VHDL_TAB}if en = \'1\' then\n'
f'{1*VHDL_TAB}if schedule_cnt = {schedule_time}-1 then\n'
f'{2*VHDL_TAB}schedule_cnt <= 0;\n'
f'{1*VHDL_TAB}else\n'
f'{2*VHDL_TAB}schedule_cnt <= schedule_cnt + 1;\n'
f'{0*VHDL_TAB}if rst = \'1\' then\n'
f'{1*VHDL_TAB}schedule_cnt <= 0;\n'
f'{0*VHDL_TAB}else\n'
f'{1*VHDL_TAB}if en = \'1\' then\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'{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
f.write('\n')
f.write(f'{VHDL_TAB}-- Memory\n')
f.write(f'\n{VHDL_TAB}-- Memory\n')
vhdl.common.write_asynchronous_read_memory(
f=f,
clk='clk',
......@@ -134,73 +160,96 @@ def write_memory_based_storage(
},
)
f.write(f'\n{VHDL_TAB}-- Memory writes\n')
f.write(f'{VHDL_TAB}process(schedule_cnt)\n')
f.write(f'{VHDL_TAB}begin\n')
f.write(f'{2*VHDL_TAB}case schedule_cnt is\n')
# Write address generation
f.write(f'\n{VHDL_TAB}-- Memory write address generation\n')
if input_sync:
vhdl.common.write_synchronous_process_prologue(
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 mv in collection:
mv = cast(MemoryVariable, mv)
if mv.execution_time:
f.write(f'{3*VHDL_TAB}-- {mv!r}\n')
f.write(f'{3*VHDL_TAB}when {mv.start_time} =>\n')
f.write(f'{4*VHDL_TAB}write_adr_0 <= {i};\n')
f.write(f'{4*VHDL_TAB}write_en_0 <= \'1\';\n')
f.write(f'{3*VHDL_TAB}when others =>\n')
f.write(f'{4*VHDL_TAB}write_adr_0 <= 0;\n')
f.write(f'{4*VHDL_TAB}write_en_0 <= \'0\';\n')
f.write(f'{2*VHDL_TAB}end case;\n')
f.write(f'{1*VHDL_TAB}end process;\n')
f.write(f'\n{VHDL_TAB}-- Memory reads\n')
f.write(f'{VHDL_TAB}process(schedule_cnt)\n')
f.write(f'{VHDL_TAB}begin\n')
f.write(f'{4*VHDL_TAB}-- {mv!r}\n')
f.write(f'{4*VHDL_TAB}when {(mv.start_time) % schedule_time} =>\n')
f.write(f'{5*VHDL_TAB}write_adr_0 <= {i};\n')
f.write(f'{5*VHDL_TAB}write_en_0 <= \'1\';\n')
f.write(f'{4*VHDL_TAB}when others =>\n')
f.write(f'{5*VHDL_TAB}write_adr_0 <= 0;\n')
f.write(f'{5*VHDL_TAB}write_en_0 <= \'0\';\n')
f.write(f'{3*VHDL_TAB}end case;\n')
if input_sync:
vhdl.common.write_synchronous_process_epilogue(
f, clk="clk", name="mem_write_address_proc"
)
else:
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 mv in collection:
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():
f.write(
f'{3*VHDL_TAB}when'
f' {(mv.start_time + read_time) % schedule_time} =>\n'
f'{4*VHDL_TAB}when'
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'{4*VHDL_TAB}read_en_0 <= \'1\';\n')
f.write(f'{3*VHDL_TAB}when others =>\n')
f.write(f'{4*VHDL_TAB}read_adr_0 <= 0;\n')
f.write(f'{4*VHDL_TAB}read_en_0 <= \'0\';\n')
f.write(f'{2*VHDL_TAB}end case;\n')
f.write(f'{1*VHDL_TAB}end process;\n\n')
f.write(f'{5*VHDL_TAB}read_adr_0 <= {i};\n')
f.write(f'{5*VHDL_TAB}read_en_0 <= \'1\';\n')
f.write(f'{4*VHDL_TAB}when others =>\n')
f.write(f'{5*VHDL_TAB}read_adr_0 <= 0;\n')
f.write(f'{5*VHDL_TAB}read_en_0 <= \'0\';\n')
f.write(f'{3*VHDL_TAB}end case;\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'{1*VHDL_TAB}write_port_0 <= p_0_in;\n')
f.write(f'\n{1*VHDL_TAB}-- Input and output assignment\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(
lambda p: p.execution_time == 0, (p for pc in assignment for p in pc)
)
vhdl.common.write_synchronous_process_prologue(
f,
clk='clk',
indent=len(VHDL_TAB),
name='output_reg_proc',
)
f.write(f'{3*VHDL_TAB}case schedule_cnt is\n')
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'{3*VHDL_TAB}end case;\n')
vhdl.common.write_synchronous_process_epilogue(
f,
clk='clk',
indent=len(VHDL_TAB),
name='output_reg_proc',
)
f.write('\n')
f.write(f'end architecture {architecture_name};')
f.write(f'\nend architecture {architecture_name};')
def write_register_based_storage(
......@@ -231,10 +280,9 @@ def write_register_based_storage(
name_pad=14,
default_value='0',
)
f.write('\n')
# Shift register
f.write(f'{VHDL_TAB}-- Shift register\n')
f.write(f'\n{VHDL_TAB}-- Shift register\n')
vhdl.common.write_type_decl(
f,
name='shift_reg_type',
......@@ -247,6 +295,16 @@ def write_register_based_storage(
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
#
......@@ -257,7 +315,6 @@ def write_register_based_storage(
f=f,
name='schedule_cnt_proc',
clk='clk',
indent=len(1 * VHDL_TAB),
body=(
f'{0*VHDL_TAB}if en = \'1\' then\n'
f'{1*VHDL_TAB}if schedule_cnt = {schedule_time}-1 then\n'
......@@ -269,15 +326,13 @@ def write_register_based_storage(
),
)
# Shift register multiplexer logic
f.write(f'\n{VHDL_TAB}-- Multiplexers for shift register\n')
vhdl.common.write_synchronous_process_prologue(
f,
clk='clk',
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}shift_reg(0) <= p_0_in;\n')
for reg_idx in range(1, reg_cnt):
......@@ -296,17 +351,17 @@ def write_register_based_storage(
f,
clk='clk',
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_regs}\n')
f.write(f'\n{VHDL_TAB}-- { list(range(len(output_regs))) }\n')
vhdl.common.write_synchronous_process_prologue(
f,
clk='clk',
name='out_mux_proc',
indent=len(VHDL_TAB),
)
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}case schedule_cnt is\n')
......@@ -322,12 +377,10 @@ def write_register_based_storage(
)
f.write(f'{4*VHDL_TAB}when others => null;\n')
f.write(f'{3*VHDL_TAB}end case;\n')
vhdl.common.write_synchronous_process_epilogue(
f,
clk='clk',
name='out_mux_proc',
indent=len(VHDL_TAB),
)
f.write(f'end architecture {architecture_name};')
......@@ -66,6 +66,8 @@ def write_signal_decl(
type: str,
default_value: Optional[str] = None,
name_pad: Optional[int] = None,
vivado_ram_style: Optional[str] = None,
quartus_ram_style: Optional[str] = None,
):
"""
Create a VHDL signal declaration: ::
......@@ -80,10 +82,16 @@ def write_signal_decl(
Signal name.
type : str
Signal type.
default_value : str, optional
default_value : string, optional
An optional default value to the signal.
name_pad : int, optional
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
name_pad = 0 if name_pad is None else name_pad
......@@ -91,6 +99,18 @@ def write_signal_decl(
if default_value is not None:
f.write(f' := {default_value}')
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(
......@@ -141,42 +161,63 @@ def write_type_decl(
f.write(f'{VHDL_TAB}type {name} is {alias};\n')
def write_synchronous_process(
def write_process_prologue(
f: TextIOWrapper,
clk: str,
body: str,
indent: Optional[int] = 0,
sensitivity_list: 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.
Write only the prologue of a regular VHDL process with a user provided sensitivity list.
This method should almost always guarantely be followed by a write_asynchronous_process_epilogue.
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 : Optional[int]
Indent this process block with `indent` columns
The TextIOWrapper object to write the type declaration to.
sensitivity_list : str
Content of the process sensitivity list.
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 for the process
An optional name for the process.
"""
space = '' if indent is None else ' ' * indent
write_synchronous_process_prologue(f, clk, indent, name)
for line in body.split('\n'):
if len(line):
f.write(f'{space}{2*VHDL_TAB}{line}\n')
write_synchronous_process_epilogue(f, clk, indent, name)
if name is not None:
f.write(f'{indent}{name}: process({sensitivity_list})\n')
else:
f.write(f'{indent}process({sensitivity_list})\n')
f.write(f'{indent}begin\n')
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(
f: TextIOWrapper,
clk: str,
indent: Optional[int] = 0,
indent: str = VHDL_TAB,
name: Optional[str] = None,
):
"""
......@@ -190,24 +231,19 @@ def write_synchronous_process_prologue(
The TextIOWrapper to write the VHDL code onto.
clk : str
Name of the clock.
indent : Optional[int]
Indent this process block with `indent` columns
indent : str, default: 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 for the process
An optional name for the process.
"""
space = '' if indent is None else ' ' * indent
if name is not None:
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')
write_process_prologue(f, sensitivity_list=clk, indent=indent, name=name)
f.write(f'{indent}{VHDL_TAB}if rising_edge(clk) then\n')
def write_synchronous_process_epilogue(
f: TextIOWrapper,
clk: Optional[str],
indent: Optional[int] = 0,
indent: str = VHDL_TAB,
name: Optional[str] = None,
):
"""
......@@ -221,18 +257,45 @@ def write_synchronous_process_epilogue(
The TextIOWrapper to write the VHDL code onto.
clk : str
Name of the clock.
indent : Optional[int]
indent : str, default: VHDL_TAB
Indent this process block with `indent` columns
name : Optional[str]
An optional name for the process
"""
_ = clk
space = '' if indent is None else ' ' * indent
f.write(f'{space}{VHDL_TAB}end if;\n')
f.write(f'{space}end process')
if name is not None:
f.write(' ' + name)
f.write(';\n')
f.write(f'{indent}{VHDL_TAB}end if;\n')
write_process_epilogue(f, sensitivity_list=clk, indent=indent, name=name)
def write_synchronous_process(
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(
......@@ -260,7 +323,7 @@ def write_synchronous_memory(
"""
assert len(read_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:
f.write(f'{3*VHDL_TAB}if {re} = \'1\' then\n')
f.write(f'{4*VHDL_TAB}{read_name} <= memory({address});\n')
......@@ -269,7 +332,7 @@ def write_synchronous_memory(
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'{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(
......@@ -280,7 +343,7 @@ def write_asynchronous_read_memory(
name: Optional[str] = None,
):
"""
Infer a VHDL synchronous reads and writes.
Infer a VHDL memory with synchronous writes and asynchronous reads.
Parameters
----------
......@@ -297,11 +360,11 @@ def write_asynchronous_read_memory(
"""
assert len(read_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:
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'{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:
f.write(f'{1*VHDL_TAB}{read_name} <= memory({address});\n')
......@@ -931,6 +931,7 @@ class ProcessCollection:
read_ports: int = 1,
write_ports: int = 1,
total_ports: int = 2,
input_sync: bool = True,
):
"""
Generate VHDL code for memory based storage of processes (MemoryVariables).
......@@ -960,6 +961,10 @@ class ProcessCollection:
total_ports : int, default: 2
The total number of ports used when splitting process collection based on
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
is_memory_variable = all(
......@@ -1024,6 +1029,7 @@ class ProcessCollection:
read_ports=read_ports,
write_ports=write_ports,
total_ports=total_ports,
input_sync=input_sync,
)
def generate_register_based_storage_vhdl(
......
......@@ -62,7 +62,7 @@ class TestProcessCollectionPlainMemoryVariable:
def test_generate_register_based_vhdl(self):
for rows in [2, 3, 4, 5, 7]:
generate_matrix_transposer(
rows, min_lifetime=1
rows, min_lifetime=0
).generate_register_based_storage_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}',
......
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