├── lake ├── __init__.py ├── models │ ├── __init__.py │ ├── model.py │ ├── passthru_model.py │ ├── pipe_reg_model.py │ ├── agg_aligner_model.py │ ├── agg_model.py │ ├── sram_model.py │ ├── demux_reads_model.py │ ├── addr_gen_model.py │ ├── prefetcher_model.py │ ├── register_file_model.py │ ├── chain_model.py │ ├── rw_arbiter_model.py │ └── agg_buff_model.py ├── passes │ ├── __init__.py │ └── cut_generator.py ├── spec │ ├── __init__.py │ ├── test_accessor.py │ ├── config_memory.py │ ├── test_LB.py │ ├── test_DB.py │ └── ir.py ├── top │ ├── __init__.py │ ├── cgra_tile_builder.py │ └── extract_tile_info.py ├── attributes │ ├── __init__.py │ ├── dedicated_port.py │ ├── hybrid_port_attr.py │ ├── sram_port_attr.py │ ├── shared_fifo_attr.py │ ├── range_group.py │ ├── control_signal_attr.py │ ├── config_reg_attr.py │ └── formal_attr.py ├── modules │ ├── __init__.py │ ├── passthru.py │ ├── hwtypes │ │ ├── acc_magma.py │ │ ├── utils.py │ │ ├── memory_magma.py │ │ └── test_sram_hwtypes.py │ ├── glb_read.py │ ├── spec │ │ ├── shared_config.txt │ │ └── lake_new.py │ ├── glb_write.py │ ├── config_reg.py │ ├── alu.py │ ├── mux.py │ ├── one_state_fsm.py │ ├── counter.py │ ├── virtual_remap_table.py │ ├── magma_code │ │ ├── aggregator_magma.py │ │ └── magma_aggregator.py │ ├── ready_valid_interface.py │ ├── sw_net.py │ ├── pipe_reg.py │ ├── agg_aligner.py │ ├── two_port_sram_stub.py │ ├── sram_stub_generator.py │ ├── chain_accessor.py │ ├── linebuffer_control.py │ ├── onyx_pe_intf.py │ ├── cfg_reg_wrapper.py │ ├── sram_stub.py │ ├── demux_reads.py │ └── aggregator.py ├── dsl │ ├── lake_imports.py │ ├── dsl_examples │ │ ├── pond.py │ │ ├── simba.py │ │ └── memtile.py │ ├── mem_port.py │ ├── edge.py │ └── helper.py └── utils │ ├── spec_enum.py │ ├── tile_builder.py │ ├── flattenND.py │ ├── reverse_flatten.py │ └── sram_macro.py ├── SPARSE_UNIT_TEST ├── ext_us_pos_in.txt ├── setup_test.sh ├── unit_test_template.tcl ├── behavioral_pe_add.sv ├── sram_sp.sv ├── tile_read.sv ├── glb_read.sv ├── pass_through_tb.sv ├── repeatsig_tb.sv ├── tile_write.sv ├── glb_write.sv ├── reduce_tb.sv ├── locator_tb.sv ├── glb_stream_read.sv ├── repeat_tb.sv └── glb_stream_write.sv ├── tests ├── test_import_sam.py ├── dump_fsdb.tcl ├── test_spec_hw │ ├── run_sim.tcl │ ├── test_comparison.py │ └── Makefile ├── test_lake.py ├── test_top_dual_port.py ├── test_passthru.py ├── test_pohan_wrapper.py ├── test_agg_aligner.py ├── test_aggregator.py ├── test_pipe_reg.py ├── wrapper_tb.sv ├── test_agg_magma.py ├── test_addr_gen.py ├── test_sram.py ├── test_prefetcher.py ├── test_agg_buffer.py ├── test_reg_fifo.py ├── test_demux_reads.py ├── test_dsl_memtile.py ├── test_chain.py └── test_tba.py ├── configure └── README.md ├── setup.cfg ├── run_lake.py ├── scripts └── setenv.sh ├── examples ├── example_output.v └── example_output_desired.v ├── .gitignore ├── .github ├── scripts │ └── run.sh └── workflows │ └── linux.yml ├── conftest.py ├── README.md ├── setup.py └── LICENSE /lake/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lake/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lake/passes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lake/spec/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lake/top/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lake/attributes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lake/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/ext_us_pos_in.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 10000 3 | 10100 4 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/setup_test.sh: -------------------------------------------------------------------------------- 1 | module load base 2 | module load vcs 3 | module load verdi -------------------------------------------------------------------------------- /tests/test_import_sam.py: -------------------------------------------------------------------------------- 1 | def test_import_sam(): 2 | import sam as sam 3 | pass 4 | -------------------------------------------------------------------------------- /lake/dsl/lake_imports.py: -------------------------------------------------------------------------------- 1 | from lake.dsl.top_lake import Lake 2 | from lake.dsl.mem_port import MemPort, PortType 3 | from lake.dsl.helper import make_params 4 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/unit_test_template.tcl: -------------------------------------------------------------------------------- 1 | dump -file cgra.fsdb -type FSDB 2 | dump -add xxx -fsdb_opt +mda+packedmda+struct 3 | power xxx.dut 4 | power -enable 5 | run 6 | power -disable 7 | run 8 | exit -------------------------------------------------------------------------------- /configure/README.md: -------------------------------------------------------------------------------- 1 | # Configuration Finder 2 | 3 | 4 | A framework for automated configuration of lake's CGRA memory tiles. 5 | 6 | 7 | The page is under construction. Please come back soon. 8 | -------------------------------------------------------------------------------- /tests/dump_fsdb.tcl: -------------------------------------------------------------------------------- 1 | dump -file mu2f_io_core.fsdb -type FSDB 2 | dump -add mu2f_io_core_tb -fsdb_opt +mda+packedmda+struct 3 | power mu2f_io_core_tb.dut 4 | power -enable 5 | run 6 | power -disable 7 | run -------------------------------------------------------------------------------- /lake/models/model.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | 4 | class Model(): 5 | def __init__(self): 6 | pass 7 | 8 | @abstractmethod 9 | def set_config(self, new_config): 10 | pass 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | max-line-length = 500 3 | ignore = E741,E266,W504,E211,E127,E128,E261,E722 4 | # E741: do not use variables named ‘l’, ‘O’, or ‘I’ 5 | # We ignore this because I and O is currently idiomatic magma for port names 6 | -------------------------------------------------------------------------------- /tests/test_spec_hw/run_sim.tcl: -------------------------------------------------------------------------------- 1 | if { $::env(WAVEFORM) != "0" } { 2 | database -open -vcd vcddb -into waveforms.vcd -default -timescale ps 3 | probe -create -all -vcd -depth all 4 | } 5 | run 6 | assertion -summary -final 7 | quit 8 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/behavioral_pe_add.sv: -------------------------------------------------------------------------------- 1 | module behavioral_pe_add ( 2 | input logic [16:0] a_in, 3 | input logic [16:0] b_in, 4 | output logic [16:0] out 5 | ); 6 | 7 | // Behavioral PE add: won't add EOS tokens 8 | assign out = a_in[16] ? a_in : (a_in + b_in); 9 | 10 | endmodule -------------------------------------------------------------------------------- /lake/top/cgra_tile_builder.py: -------------------------------------------------------------------------------- 1 | class CGRATileBuilder(): 2 | ''' 3 | This class provides utilities for interfacing with the AHA CGRA infrastructure 4 | ''' 5 | def get_bitstream(self, config_json): 6 | ''' 7 | Takes top level configuration for the tile, processes it and returns config... 8 | ''' 9 | raise NotImplementedError 10 | -------------------------------------------------------------------------------- /lake/modules/passthru.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | 3 | 4 | class PassThroughMod(Generator): 5 | def __init__(self): 6 | super().__init__("PassThrough") 7 | 8 | self._clk = self.clock("clk") 9 | self._data_in = self.input("data_in", 1) 10 | self._data_out = self.output("data_out", 1) 11 | self.wire(self._data_out, self._data_in) 12 | -------------------------------------------------------------------------------- /lake/attributes/dedicated_port.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | 3 | 4 | class DedicatedPortAttribute(kts.Attribute): 5 | def __init__(self, 6 | doc_string=""): 7 | super().__init__() 8 | self.documentation = doc_string 9 | 10 | def set_documentation(self, new_doc): 11 | self.documentation = new_doc 12 | 13 | def get_documentation(self): 14 | return self.documentation 15 | -------------------------------------------------------------------------------- /run_lake.py: -------------------------------------------------------------------------------- 1 | import kratos 2 | from lake.modules.passthru import * 3 | from lake.top.lake_top import * 4 | from lake.modules.sram_stub import * 5 | 6 | # lake_top = LakeTop(16) 7 | # lake_top_src = kratos.verilog(lake_top, optimize_passthrough=False) 8 | # print(lake_top_src["LakeTop"]) 9 | 10 | sramstub = SRAMStub(16, 512) 11 | sramstub_src = kratos.verilog(sramstub, optimize_passthrough=False) 12 | print(sramstub_src["SRAMStub"]) 13 | -------------------------------------------------------------------------------- /lake/dsl/dsl_examples/pond.py: -------------------------------------------------------------------------------- 1 | from lake.dsl.lake_imports import * 2 | 3 | pond = Lake(16, 1, 1) 4 | 5 | read_port = MemPort(0, 1) 6 | write_port = MemPort(1, 1) 7 | 8 | pond_params = make_params("pond", 32, 1, 1) 9 | pond.add_memory(pond_params, read_ports=[read_port], write_ports=[write_port]) 10 | pond.add_input_edge(0, "pond", dim=2, max_stride=2**5) 11 | pond.add_output_edge(0, "pond", dim=2, max_stride=2**5) 12 | 13 | # pond.construct_lake("pond") 14 | -------------------------------------------------------------------------------- /tests/test_spec_hw/test_comparison.py: -------------------------------------------------------------------------------- 1 | from lake.utils.util import verify_gold 2 | import argparse 3 | 4 | 5 | if __name__ == "__main__": 6 | 7 | parser = argparse.ArgumentParser(description='Test verifier...') 8 | parser.add_argument("--dir", type=str, default=None) 9 | args = parser.parse_args() 10 | ret_val = verify_gold(args.dir) 11 | if ret_val is True: 12 | print("Test PASSED!") 13 | else: 14 | print("Test FAILED...") 15 | -------------------------------------------------------------------------------- /lake/attributes/hybrid_port_attr.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | 3 | 4 | class HybridPortAddr(kts.Attribute): 5 | 6 | def __init__(self, 7 | doc_string=""): 8 | super().__init__() 9 | self.value = "hybrid_port_attribute" 10 | self.documentation = doc_string 11 | 12 | def set_documentation(self, new_doc): 13 | self.documentation = new_doc 14 | 15 | def get_documentation(self): 16 | return self.documentation 17 | -------------------------------------------------------------------------------- /lake/modules/hwtypes/acc_magma.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | 3 | 4 | def create_circuit(): 5 | @m.circuit.sequential(async_reset=True) 6 | class DelayBy2: 7 | def __init__(self): 8 | self.x: m.Bits[2] = m.bits(0, 2) 9 | self.y: m.Bits[2] = m.bits(0, 2) 10 | 11 | def __call__(self, I: m.Bits[2]) -> m.Bits[2]: 12 | O = self.y 13 | self.y = self.x 14 | self.x = I 15 | return O 16 | 17 | return DelayBy2 18 | 19 | 20 | create_circuit() 21 | -------------------------------------------------------------------------------- /scripts/setenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # detect the current dir 4 | ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 5 | ROOT="$(dirname ${ROOT})" 6 | 7 | # detect if clockwork as been cloned or not 8 | export CLOCKWORK_DIR="${ROOT}/clockwork" 9 | 10 | if [ ! -d ${CLOCKWORK_DIR} ] 11 | then 12 | git clone -b lower_ubuffer https://github.com/dillonhuff/clockwork ${CLOCKWORK_DIR} 13 | fi 14 | 15 | export LAKE_CONTROLLERS="${CLOCKWORK_DIR}/lake_controllers" 16 | export LAKE_STREAM="${CLOCKWORK_DIR}/lake_stream/" 17 | -------------------------------------------------------------------------------- /lake/models/passthru_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | 3 | 4 | class PassthruModel(Model): 5 | 6 | def __init__(self): 7 | self._val = 0 8 | 9 | def set_config(self, new_config): 10 | # Configure top level 11 | for key, config_val in new_config.items(): 12 | if key not in self.config: 13 | AssertionError("Gave bad config...") 14 | else: 15 | self.config[key] = config_val 16 | 17 | def set_in(self, in_data): 18 | return in_data 19 | -------------------------------------------------------------------------------- /examples/example_output.v: -------------------------------------------------------------------------------- 1 | module LakeTop ( 2 | input logic [1:0] i_data_in, 3 | output logic [1:0] o_data_out 4 | ); 5 | 6 | logic u_passthru_0_in; 7 | logic u_passthru_0_out; 8 | logic u_passthru_1_in; 9 | logic u_passthru_1_out; 10 | assign o_data_out[0] = u_passthru_0_out; 11 | assign o_data_out[1] = u_passthru_1_out; 12 | PassThrough u_passthru_0 ( 13 | .in(u_passthru_0_in), 14 | .out(u_passthru_0_out) 15 | ); 16 | 17 | PassThrough u_passthru_1 ( 18 | .in(u_passthru_1_in), 19 | .out(u_passthru_1_out) 20 | ); 21 | 22 | endmodule // LakeTop 23 | 24 | -------------------------------------------------------------------------------- /lake/attributes/sram_port_attr.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | 3 | 4 | # this attribute indicates sram ports which may be renamed if macro being used requires 5 | # different port names 6 | class SRAMPortAttr(kts.Attribute): 7 | def __init__(self, 8 | doc_string=""): 9 | super().__init__() 10 | self.value = "sram_port" 11 | self.documentation = doc_string 12 | 13 | def set_documentation(self, new_doc): 14 | self.documentation = new_doc 15 | 16 | def get_documentation(self): 17 | return self.documentation 18 | -------------------------------------------------------------------------------- /lake/utils/spec_enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | ### 5 | # Port Enums 6 | ### 7 | class Direction(Enum): 8 | IN = 0 9 | OUT = 1 10 | 11 | 12 | class Runtime(Enum): 13 | STATIC = 0 14 | DYNAMIC = 1 15 | 16 | 17 | ### 18 | # MemoryPort Enums 19 | ### 20 | class MemoryPortType(Enum): 21 | R = 0 22 | W = 1 23 | RW = 2 24 | READ = 0 25 | WRITE = 1 26 | READWRITE = 2 27 | 28 | 29 | ### 30 | # LF Comparison Enum 31 | ### 32 | class LFComparisonOperator(Enum): 33 | LT = 0 34 | GT = 1 35 | EQ = 2 36 | LTE = 3 37 | -------------------------------------------------------------------------------- /lake/utils/tile_builder.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from lake.attributes.config_reg_attr import ConfigRegAttr 3 | 4 | 5 | class TileBase(kts.Generator): 6 | 7 | def __init__(self, name: str, debug: bool): 8 | super().__init__(name, debug=debug) 9 | 10 | def add_cfg_reg(self, name, description, width, **kwargs): 11 | cfg_reg = self.input(name, width, **kwargs) 12 | cfg_reg.add_attribute(ConfigRegAttr(description)) 13 | return cfg_reg 14 | 15 | # def add_tile_input(self, is_control=False, ignore=False, full_bus=False, doc_string="", ): 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ######## 2 | # SWAP # 3 | ######## 4 | *.swp 5 | .magma/ 6 | *.txt 7 | *.csv 8 | *.json 9 | 10 | ########## 11 | # Images # 12 | ########## 13 | *.jpg 14 | *.png 15 | 16 | ############### 17 | # Build Files # 18 | ############### 19 | build 20 | dist 21 | lake.egg-info 22 | 23 | ########### 24 | # pycache # 25 | ########### 26 | *__pycache__* 27 | 28 | ########### 29 | # venvs # 30 | ########### 31 | venv/* 32 | 33 | # dump 34 | dump/ 35 | *dump* 36 | # verilog outputs 37 | *.sv 38 | *.v 39 | *.vscode 40 | 41 | *.idea 42 | 43 | # local clone of clockwork 44 | clockwork 45 | 46 | *egg-info* 47 | TEST 48 | -------------------------------------------------------------------------------- /tests/test_lake.py: -------------------------------------------------------------------------------- 1 | from lake.utils.test_infra import lake_test_app_args, gen_test_lake 2 | import pytest 3 | 4 | 5 | # add more tests with this function by adding args 6 | @pytest.mark.skip 7 | # @pytest.mark.parametrize("args", [lake_test_app_args("separate")]) 8 | def test_lake(args): 9 | gen_test_lake(config_path=args[0], 10 | stream_path=args[1], 11 | in_file_name=args[2], 12 | out_file_name=args[3]) 13 | 14 | 15 | if __name__ == "__main__": 16 | # separate accessors conv_3_3 17 | conv33args = lake_test_app_args("separate") 18 | test_lake(conv33args) 19 | -------------------------------------------------------------------------------- /.github/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # As of April 2024: This script is completely unused now, 4 | # I think, but I'm too chicken to delete it :) 5 | 6 | set +x 7 | set -e 8 | 9 | cd /lake 10 | 11 | source scripts/setenv.sh 12 | 13 | # force color 14 | export PYTEST_ADDOPTS="--color=yes" 15 | 16 | echo pip install py, apt-get install verilator 17 | pip install py 18 | apt-get update 19 | apt-get install verilator 20 | 21 | echo python3 -m pycodestyle lake/ 22 | python3 -m pycodestyle lake/ 23 | 24 | echo python3 -m pycodestyle tests/ 25 | python3 -m pycodestyle tests/ 26 | 27 | echo pytest -v tests/ 28 | pytest -v tests/ 29 | 30 | set -x 31 | -------------------------------------------------------------------------------- /lake/dsl/mem_port.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from enum import IntEnum, auto 3 | 4 | 5 | class PortType(IntEnum): 6 | READ = auto() 7 | WRITE = auto() 8 | READWRITE = auto() 9 | 10 | 11 | class MemPort(): 12 | def __init__(self, 13 | latency, 14 | II, 15 | addr_domain=[-1, -1]): 16 | 17 | self.port_info = {"latency": latency, 18 | "initiation_interval": II, 19 | "addr_domain": {"min": addr_domain[0], "max": addr_domain[1]}} 20 | 21 | def set_addr_domain(self, addr_domain): 22 | self.port_info["addr_domain"] = {"min": addr_domain[0], "max": addr_domain[1]} 23 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/sram_sp.sv: -------------------------------------------------------------------------------- 1 | module sram_sp ( 2 | input logic clk, 3 | input logic clk_en, 4 | input logic [63:0] data_in_p0, 5 | input logic flush, 6 | input logic [8:0] read_addr_p0, 7 | input logic read_enable_p0, 8 | input logic [8:0] write_addr_p0, 9 | input logic write_enable_p0, 10 | output logic [63:0] data_out_p0 11 | ); 12 | 13 | logic [63:0] data_array [511:0]; 14 | 15 | always_ff @(posedge clk) begin 16 | if (clk_en) begin 17 | if (write_enable_p0 == 1'h1) begin 18 | data_array[write_addr_p0] <= data_in_p0; 19 | end 20 | else if (read_enable_p0) begin 21 | data_out_p0 <= data_array[read_addr_p0]; 22 | end 23 | end 24 | end 25 | endmodule // sram_sp -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from magma import clear_cachedFunctions 3 | import magma 4 | from kratos import clear_context 5 | 6 | collect_ignore = [ 7 | "tests/test_transpose_buffer_kratos.py" 8 | ] 9 | 10 | 11 | @pytest.fixture(autouse=True) 12 | def kratos_test(): 13 | clear_cachedFunctions() 14 | magma.frontend.coreir_.ResetCoreIR() 15 | clear_context() 16 | 17 | 18 | def pytest_addoption(parser): 19 | parser.addoption('--longrun', action='store_true', dest="longrun", 20 | default=False, help="enable longrun decorated tests") 21 | 22 | 23 | def pytest_configure(config): 24 | if not config.option.longrun: 25 | setattr(config.option, 'markexpr', 'not longrun') 26 | -------------------------------------------------------------------------------- /lake/modules/glb_read.py: -------------------------------------------------------------------------------- 1 | import kratos 2 | 3 | 4 | class GLBRead(kratos.Generator): 5 | 6 | def __init__(self, ID=0): 7 | # super().__init__(name=f"glb_read_{ID}") 8 | super().__init__(name=f"glb_read") 9 | 10 | self.external = True 11 | # inputs 12 | self._clk = self.clock("clk") 13 | self._rst_n = self.reset("rst_n") 14 | 15 | self._data = self.input("data", 16) 16 | 17 | self._ready = self.output("ready", 1) 18 | 19 | self._valid = self.input("valid", 1) 20 | 21 | self._done = self.output("done", 1) 22 | 23 | self._flush = self.input("flush", 1) 24 | 25 | self.NUM_BLOCKS_PARAM = self.param("NUM_BLOCKS", 32, 1) 26 | -------------------------------------------------------------------------------- /lake/modules/hwtypes/utils.py: -------------------------------------------------------------------------------- 1 | def get_slice(data, addr, width): 2 | addr = addr.zext(data.size - addr.size) 3 | addr = addr * width 4 | return (data >> addr)[:width] 5 | 6 | 7 | def set_slice(data, addr, width, value): 8 | assert value.size == width 9 | addr = addr.zext(data.size - addr.size) 10 | addr = addr * width 11 | value = value.zext(data.size - value.size) 12 | # Shift out bottom bits / shift in zeros 13 | top_bits = (data >> (addr + width)) << (addr + width) 14 | # shift to address 15 | mid_bits = value << addr 16 | # shift out top bits / shift in zeros 17 | bot_bits = (data << (-addr + data.size)) >> (-addr + data.size) 18 | return top_bits | mid_bits | bot_bits 19 | -------------------------------------------------------------------------------- /lake/attributes/shared_fifo_attr.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from enum import Enum 3 | 4 | 5 | class SharedFifoAttr(kts.Attribute): 6 | def __init__(self, 7 | direction="IN", 8 | doc_string=""): 9 | super().__init__() 10 | self.value = "control_signal" 11 | self.documentation = doc_string 12 | self.direction = direction 13 | 14 | def set_documentation(self, new_doc): 15 | self.documentation = new_doc 16 | 17 | def get_documentation(self): 18 | return self.documentation 19 | 20 | def set_direction(self, new_direction): 21 | self.direction = new_direction 22 | 23 | def get_direction(self): 24 | return self.direction 25 | -------------------------------------------------------------------------------- /lake/modules/spec/shared_config.txt: -------------------------------------------------------------------------------- 1 | AGG - SRAM Shared 2 | agg_read_loops_0_dimensionality, sram_write_loops_dimensionality 3 | agg_read_loops_0_ranges, sram_write_loops_ranges 4 | agg_read_output_sched_gen_sched_addr_gen_strides, sram_write_sched_gen_sched_addr_gen_strides 5 | agg_read_output_sched_gen_sched_addr_gen_starting_addr, sram_write_sched_gen_sched_addr_gen_starting_addr 6 | 7 | # SRAM - TB Shared 8 | sram_read_sched_gen_sched_addr_gen_strides, tb_write_sched_gen_sched_addr_gen_strides 9 | sram_read_sched_gen_sched_addr_gen_starting_addr, tb_write_sched_gen_sched_addr_gen_starting_addr 10 | sram_read_loops_ranges, output_write_loops_ranges 11 | sram_read_loops_dimensionality, output_write_loops_dimensionality 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /lake/attributes/range_group.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from enum import Enum 3 | 4 | 5 | class RangeGroupAttr(kts.Attribute): 6 | def __init__(self, 7 | group_num, 8 | group_name, 9 | doc_string=""): 10 | super().__init__() 11 | self.value = "range_group" 12 | self.group_num = group_num 13 | self.group_name = group_name 14 | self.documentation = doc_string 15 | 16 | def set_documentation(self, new_doc): 17 | self.documentation = new_doc 18 | 19 | def get_documentation(self): 20 | return self.documentation 21 | 22 | def get_group_num(self): 23 | return self.group_num 24 | 25 | def geT_group_name(self): 26 | return self.group_name 27 | -------------------------------------------------------------------------------- /lake/modules/glb_write.py: -------------------------------------------------------------------------------- 1 | import kratos 2 | 3 | 4 | class GLBWrite(kratos.Generator): 5 | 6 | def __init__(self, ID=0): 7 | # super().__init__(name=f"glb_write_{ID}") 8 | super().__init__(name=f"glb_write") 9 | 10 | self.external = True 11 | # inputs 12 | self._clk = self.clock("clk") 13 | self._rst_n = self.reset("rst_n") 14 | 15 | self._data = self.output("data", 16) 16 | 17 | self._ready = self.input("ready", 1) 18 | 19 | self._valid = self.output("valid", 1) 20 | 21 | self._done = self.output("done", 1) 22 | 23 | self._flush = self.input("flush", 1) 24 | 25 | self.TX_SIZE_PARAM = self.param("TX_SIZE", 32, 32) 26 | 27 | self.FILE_NO_PARAM = self.param("FILE_NO", 32, 0) 28 | -------------------------------------------------------------------------------- /examples/example_output_desired.v: -------------------------------------------------------------------------------- 1 | module LakeTop ( 2 | input logic [1:0] i_data_in, 3 | output logic [1:0] o_data_out 4 | ); 5 | 6 | //========================== 7 | //Declarations 8 | logic u_passthru_0_in; 9 | logic u_passthru_0_out; 10 | logic u_passthru_1_in; 11 | logic u_passthru_1_out; 12 | 13 | //===================================== 14 | //Main Body 15 | assign o_data_out[0] = u_passthru_0_out; 16 | assign o_data_out[1] = u_passthru_1_out; 17 | assign u_passthru_0_in = i_data_in[0]; 18 | assign u_passthru_1_in = i_data_in[1]; 19 | 20 | PassThrough u_passthru_0 ( 21 | .in(u_passthru_0_in), 22 | .out(u_passthru_0_out) 23 | ); 24 | 25 | PassThrough u_passthru_1 ( 26 | .in(u_passthru_1_in), 27 | .out(u_passthru_1_out) 28 | ); 29 | 30 | endmodule // LakeTop 31 | 32 | -------------------------------------------------------------------------------- /lake/modules/hwtypes/memory_magma.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | from hwtypes import Bit, BitVector 3 | from math import log2 4 | 5 | 6 | def create_circuit(mem_depth=4, 7 | word_width=16): 8 | 9 | num_bits = mem_depth * word_width 10 | addr_width = log2(mem_depth) 11 | 12 | @m.circuit.sequential(async_reset=True) 13 | class DelayBy2: 14 | def __init__(self): 15 | self.mem: m.Bits[num_bits] = m.bits(0, num_bits) 16 | 17 | def __call__(self, wen: m.Bits[1], 18 | cen: m.Bits[1], 19 | addr: m.Bits[addr_width], 20 | data_in: m.Bits[word_width] 21 | ) -> (m.Bits[word_width]): 22 | 23 | O = self.y 24 | self.y = self.x 25 | self.x = I 26 | return O 27 | 28 | return DelayBy2 29 | 30 | 31 | create_circuit() 32 | -------------------------------------------------------------------------------- /lake/attributes/control_signal_attr.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from enum import Enum 3 | 4 | 5 | class ControlSignalAttr(kts.Attribute): 6 | def __init__(self, 7 | is_control=False, 8 | ignore=False, 9 | full_bus=False, 10 | doc_string=""): 11 | super().__init__() 12 | self.value = "control_signal" 13 | self.is_control = is_control 14 | self.ignore = ignore 15 | self.full_bus = full_bus 16 | self.documentation = doc_string 17 | 18 | def set_documentation(self, new_doc): 19 | self.documentation = new_doc 20 | 21 | def get_documentation(self): 22 | return self.documentation 23 | 24 | def get_control(self): 25 | return self.is_control 26 | 27 | def get_ignore(self): 28 | return self.ignore 29 | 30 | def get_full_bus(self): 31 | return self.full_bus 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lake: An Agile Framework for Designing and Automatically Configuring Physical Unified Buffers 2 | 3 | Lake is a framework for generating synthesizable memory modules from a high-level behavioral specification and widely-available memory macros. Lake also comprises a library of generalized hardware modules aimed at memory controller designs. 4 | 5 | ## Install 6 | `git clone github.com/StanfordAHA/lake` 7 | 8 | `cd lake && pip install -e .` 9 | 10 | ## Run a test 11 | To run a test, you can simply generate the verilog and push through your favorite verilog simulator. Alternatively, Lake uses the pytest framework for unit tests of constituent modules. These tests leverage [fault](https://github.com/leonardt/fault) and [verilator](https://www.veripool.org/wiki/verilator) for open source simulation. Tests should run and pass on Linux and MacOS. 12 | 13 | ## Documentation 14 | Check out the [wiki](https://github.com/StanfordAHA/lake/wiki) of this github repo. 15 | -------------------------------------------------------------------------------- /lake/spec/test_accessor.py: -------------------------------------------------------------------------------- 1 | from accessor import Accessor 2 | 3 | 4 | def test_Accessor(): 5 | 6 | hw_spec = Accessor() 7 | hw_spec.setConstraint( 8 | st_size=4, 9 | var_dim=6, 10 | expr_dim=1, 11 | expr_piece_dim=3 12 | ) 13 | hw_spec.setConfig( 14 | # debug axuilary 15 | st_name_list=["write", "read"], 16 | # real hardware config 17 | st_size=2, 18 | depth=36, 19 | var_dim=[2, 2], 20 | var_range_list=[[5, 5], [3, 3]], 21 | expr_dim=[1, 1], 22 | expr_config=[[[([5, 5], [1, 6, 0])]], [[([3, 3], [1, 6, 14])]]] 23 | ) 24 | 25 | hw_spec.checkConstraint() 26 | 27 | sim_cycle = 36 28 | 29 | for cyc in range(sim_cycle): 30 | print("\n**********cycle: ", cyc, "**********\n") 31 | hw_spec.exeComb() 32 | hw_spec.print_sim_info() 33 | hw_spec.exeSeq() 34 | 35 | 36 | if __name__ == "__main__": 37 | test_Accessor() 38 | -------------------------------------------------------------------------------- /lake/dsl/dsl_examples/simba.py: -------------------------------------------------------------------------------- 1 | from lake.dsl.lake_imports import * 2 | 3 | # weight buffer 4 | weights = Lake(64, 1, 1) 5 | 6 | port = MemPort(1, 0) 7 | 8 | weights_params = make_params("weights", 512, read_write_port_width=1, use_macro=True) 9 | weights.add_memory(weights_params, read_write_ports=[port]) 10 | 11 | weights.construct_lake("simba_weights.sv") 12 | 13 | # accumulation buffer 14 | accum = TopLake(8 * 24, 1, 1) 15 | 16 | port = MemPort(1, 0) 17 | 18 | accum_params = make_params("accum", 64, read_write_port_width=1, use_macro=True) 19 | accum.add_memory(accum_params, read_write_ports=[port]) 20 | 21 | accum.construct_lake("simba_accum.sv") 22 | 23 | # input activation buffer 24 | input_buffer = TopLake(64, 1, 1) 25 | 26 | port = MemPort(1, 0) 27 | 28 | input_params = make_params("input_buffer", 1024, read_write_port_width=1, use_macro=True) 29 | input_buffer.add_memory(input_params, read_write_ports=[port]) 30 | 31 | input_buffer.construct_lake("simba_input.sv") 32 | -------------------------------------------------------------------------------- /lake/models/pipe_reg_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | 3 | 4 | class PipeRegModel(Model): 5 | def __init__(self, 6 | data_width, 7 | stages): 8 | self.data_width = data_width 9 | self.stages = stages 10 | 11 | self.reg_stage = [] 12 | if (self.stages > 0): 13 | for i in range(self.stages): 14 | self.reg_stage.append(0) 15 | 16 | # No config 17 | def set_config(self, new_config): 18 | return 19 | 20 | def update_data(self, data_in): 21 | if (self.stages == 0): 22 | return data_in 23 | else: 24 | data_curr = [] 25 | for i in range(self.stages): 26 | data_curr.append(self.reg_stage[i]) 27 | self.reg_stage[0] = data_in 28 | for i in range(self.stages - 1): 29 | self.reg_stage[i + 1] = data_curr[i] 30 | return self.reg_stage[self.stages - 1] 31 | -------------------------------------------------------------------------------- /lake/modules/config_reg.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from lake.attributes.config_reg_attr import ConfigRegAttr 3 | 4 | 5 | class ConfigReg(Generator): 6 | def __init__(self, 7 | name, 8 | data_width, 9 | init_val): 10 | super().__init__(f"config_reg_{name}_{data_width}") 11 | 12 | self.add_attribute(ConfigRegAttr()) 13 | 14 | self.init_val = init_val 15 | self.data_width = data_width 16 | 17 | self._out = self.output("o_data_out", data_width) 18 | self._clk = self.clock("i_clk") 19 | self._rst_n = self.reset("i_rst_n") 20 | 21 | self.add_code(self.set_out) 22 | 23 | @always((posedge, "i_clk"), (negedge, "i_rst_n")) 24 | def set_out(self): 25 | if ~self._rst_n: 26 | self._out = self.init_val 27 | 28 | 29 | if __name__ == "__main__": 30 | data_width = 16 31 | configregdut = ConfigReg(name="config_test", data_width=data_width, init_val=2) 32 | verilog(configregdut, filename=f"config_reg_{data_width}.sv") 33 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name='lake-aha', 8 | version="0.0.4", 9 | author='Maxwell Strange', 10 | author_email='mstrange@stanford.edu', 11 | description='Memory Generator based on Kratos: The God of War.', 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/StanfordAHA/lake", 15 | python_requires=">=3.5", 16 | packages=[ 17 | "lake", 18 | "lake.attributes", 19 | "lake.dsl", 20 | "lake.dsl.dsl_examples", 21 | "lake.models", 22 | "lake.modules", 23 | "lake.modules.spec", 24 | "lake.passes", 25 | "lake.spec", 26 | "lake.top", 27 | "lake.utils" 28 | ], 29 | install_requires=[ 30 | "kratos", 31 | "fault", 32 | "magma-lang", 33 | "pytest", 34 | "matplotlib", 35 | "networkx" 36 | ] 37 | ) 38 | -------------------------------------------------------------------------------- /lake/modules/alu.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | import kratos as kts 3 | 4 | 5 | class ALU(Generator): 6 | def __init__(self, data_width=16): 7 | super().__init__(name=f"alu_{data_width}") 8 | 9 | self.data_width = data_width 10 | 11 | self._data_in = self.input("data_in", self.data_width, size=2, explicit_array=True, packed=True) 12 | self._data_out = self.output("data_out", self.data_width) 13 | 14 | self._op = self.input("op", 1) 15 | self._execute_op = self.input("execute_op", 1) 16 | 17 | self.add_code(self.ALU_comb) 18 | 19 | @always_comb 20 | def ALU_comb(self): 21 | if self._execute_op: 22 | if self._op == 0: 23 | self._data_out = self._data_in[0] + self._data_in[1] 24 | elif self._op == 1: 25 | self._data_out = self._data_in[0] * self._data_in[1] 26 | else: 27 | self._data_out = 0 28 | else: 29 | self._data_out = 0 30 | 31 | 32 | if __name__ == "__main__": 33 | 34 | alu_dut = ALU(data_width=16) 35 | 36 | verilog(alu_dut, filename="alu.sv", 37 | optimize_if=False) 38 | -------------------------------------------------------------------------------- /lake/modules/mux.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from kratos import * 3 | 4 | 5 | class Mux(Generator): 6 | 7 | def __init__(self, height: int, width: int): 8 | name = "Mux_{0}_{1}".format(width, height) 9 | super().__init__(name) 10 | 11 | # pass through wires 12 | if height == 1: 13 | self.in_ = self.input("I", width) 14 | self.out_ = self.output("O", width) 15 | self.wire(self.out_, self.in_) 16 | return 17 | 18 | self.sel_size = clog2(height) 19 | input_ = self.input("I", width, size=height) 20 | self.out_ = self.output("O", width) 21 | self._sel = self.input("S", self.sel_size) 22 | 23 | # add a combinational block 24 | comb = self.combinational() 25 | 26 | # add a case statement 27 | switch_ = comb.switch_(self.ports.S) 28 | for i in range(height): 29 | switch_.case_(i, self.out_(input_[i])) 30 | # add default 31 | switch_.case_(None, self.out_(0)) 32 | 33 | 34 | if __name__ == "__main__": 35 | 36 | mux_dut = Mux(height=4, width=16) 37 | 38 | verilog(mux_dut, filename="mux.sv", 39 | optimize_if=False) 40 | -------------------------------------------------------------------------------- /lake/modules/one_state_fsm.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | import kratos as kts 3 | 4 | 5 | class OneStateFSM(Generator): 6 | def __init__(self, debug: bool = False, is_clone: bool = False, internal_generator=None): 7 | super().__init__("onestate", debug, is_clone, internal_generator) 8 | 9 | self._clk = self.clock("clk") 10 | self._rst_n = self.reset("rst_n") 11 | 12 | self.boring_fsm = self.add_fsm("boring_fsm", reset_high=False) 13 | START = self.boring_fsm.add_state("START") 14 | 15 | self._boring_var = self.var("boring_var", 1) 16 | #################### 17 | # START 18 | #################### 19 | # Get the first block size... 20 | START.next(START, None) 21 | 22 | self.boring_fsm.output(self._boring_var) 23 | 24 | START.output(self._boring_var, 1) 25 | 26 | self.boring_fsm.set_start_state(START) 27 | 28 | # Force FSM realization first so that flush gets added... 29 | kts.passes.realize_fsm(self.internal_generator) 30 | 31 | 32 | if __name__ == "__main__": 33 | onestate_dut = OneStateFSM() 34 | verilog(onestate_dut, filename="onestate.sv", 35 | optimize_if=False) 36 | -------------------------------------------------------------------------------- /tests/test_top_dual_port.py: -------------------------------------------------------------------------------- 1 | from lake.utils.wrapper import get_dut, wrapper 2 | from kratos import * 3 | import pytest 4 | 5 | 6 | @pytest.mark.skip 7 | def test_gen_dual_port(): 8 | 9 | config_path = "/nobackup/joeyliu/aha/poly/clockwork/aha_garnet_design_pond/three_level_pond_rolled/lake_collateral/ub_hw_input_stencil_BANK_0/" 10 | 11 | lake_gen_kwargs = { 12 | "interconnect_input_ports": 2, 13 | "interconnect_output_ports": 2, 14 | "read_delay": 1, 15 | "name": "JoeysWorld", 16 | "rw_same_cycle": True, 17 | "fifo_mode": False, 18 | "stencil_valid": True, 19 | "mem_width": 16 20 | } 21 | 22 | # Get the DUT 23 | lt_dut, module_name, iter_support = get_dut(pond=False, 24 | pd=0, 25 | pl=0, 26 | **lake_gen_kwargs) 27 | 28 | wrapper(dut=lt_dut, 29 | module_name=module_name, 30 | iterator_support=iter_support, 31 | config_path_input=config_path, 32 | name=lake_gen_kwargs['name']) 33 | 34 | 35 | if __name__ == "__main__": 36 | test_gen_dual_port() 37 | -------------------------------------------------------------------------------- /lake/modules/counter.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from lake.utils.util import add_counter 3 | 4 | 5 | class Counter(kts.Generator): 6 | 7 | def __init__(self, name: str, 8 | bitwidth=16, 9 | increment=None, 10 | pos_reset=False): 11 | super().__init__(name, debug=True) 12 | 13 | self._clk = self.clock("clk") 14 | self._rst_n = self.reset("rst_n") 15 | 16 | inc_var = kts.const(1, 1) 17 | if increment is not None: 18 | inc_var = increment 19 | self.ctr = add_counter(self, f"{name}_ctr", bitwidth, increment=inc_var, clear=None, pos_reset=pos_reset) 20 | 21 | self._count_out = self.output("count_out", bitwidth) 22 | self.wire(self._count_out, self.ctr) 23 | 24 | self.add_attribute("sync-reset=flush") 25 | kts.passes.auto_insert_sync_reset(self.internal_generator) 26 | flush_port = self.internal_generator.get_port("flush") 27 | 28 | 29 | if __name__ == "__main__": 30 | ctr_dut = Counter(name="test_ctr", 31 | bitwidth=16, 32 | increment=None) 33 | kts.verilog(ctr_dut, filename="counter.sv", 34 | optimize_if=False) 35 | -------------------------------------------------------------------------------- /lake/utils/flattenND.py: -------------------------------------------------------------------------------- 1 | import kratos 2 | from kratos import * 3 | 4 | 5 | class FlattenND(Generator): 6 | def __init__(self, array_width, *array_size): 7 | super().__init__("flattenND", True) 8 | 9 | total_dims = array_width * array_size[0] 10 | apparent_width = array_width 11 | for i in range(1, len(array_size)): 12 | total_dims = total_dims * array_size[i] 13 | apparent_width = apparent_width * array_size[i] 14 | 15 | self.in_ = self.input("input_array", 16 | width=array_width, 17 | size=array_size, 18 | packed=True, 19 | explicit_array=True) 20 | 21 | self.out_ = self.output("output_array", width=total_dims) 22 | 23 | self.add_code(self.flatten_array) 24 | 25 | @always_comb 26 | def flatten_array(self): 27 | for i in range(array_size[0]): 28 | self.out_[(i + 1) * apparent_width - 1, i * apparent_width] = self.in_[i] 29 | 30 | 31 | if __name__ == "__main__": 32 | # input_array needs to be unpacked for third dimension > 1 33 | dut = FlattenND(16, 4, 1) 34 | verilog(dut, filename="flatten.sv") 35 | -------------------------------------------------------------------------------- /lake/utils/reverse_flatten.py: -------------------------------------------------------------------------------- 1 | import kratos 2 | from kratos import * 3 | 4 | 5 | class ReverseFlatten(Generator): 6 | def __init__(self, array_width, *array_size): 7 | super().__init__("ReverseFlatten", True) 8 | 9 | total_dims = array_width * array_size[0] 10 | apparent_width = array_width 11 | for i in range(1, len(array_size)): 12 | total_dims = total_dims * array_size[i] 13 | apparent_width = apparent_width * array_size[i] 14 | 15 | self.in_ = self.input("input_array", width=total_dims) 16 | self.out_ = self.output("output_array", 17 | width=array_width, 18 | size=array_size, 19 | packed=True, 20 | explicit_array=True) 21 | 22 | self.add_code(self.make_array) 23 | 24 | @always_comb 25 | def make_array(self): 26 | for i in range(array_size[0]): 27 | self.out_[i] = self.in_[(i + 1) * apparent_width - 1, i * apparent_width] 28 | 29 | 30 | if __name__ == "__main__": 31 | # output_array needs to be unpacked for third dimension > 1 32 | dut = ReverseFlatten(16, 4, 1) 33 | verilog(dut, filename="reverse_flatten.sv") 34 | -------------------------------------------------------------------------------- /lake/attributes/config_reg_attr.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | 3 | 4 | class ConfigRegAttr(kts.Attribute): 5 | 6 | def __init__(self, 7 | doc_string="", 8 | read_only=False): 9 | super().__init__() 10 | self.value = "config_reg" 11 | self.documentation = doc_string 12 | self.intercepted = False 13 | self.read_only = read_only 14 | self.observers = [] 15 | 16 | def get_read_only(self): 17 | return self.read_only 18 | 19 | def set_read_only(self, ro): 20 | self.read_only = ro 21 | 22 | def set_documentation(self, new_doc): 23 | self.documentation = new_doc 24 | 25 | def get_documentation(self): 26 | return self.documentation 27 | 28 | def get_intercepted(self): 29 | return self.intercepted 30 | 31 | def set_intercepted(self, new_intercepted): 32 | self.intercepted = new_intercepted 33 | 34 | def add_observer(self, generator, port): 35 | self.observers.append((generator, port)) 36 | 37 | # Get the observer variables of this config reg in 38 | # the generator 39 | def get_observers(self, generator): 40 | return [port for (gen, port) in self.observers if gen.internal_generator == generator] 41 | -------------------------------------------------------------------------------- /lake/models/agg_aligner_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | 3 | 4 | class AggAlignerModel(Model): 5 | ''' 6 | Model for agg aligner 7 | ''' 8 | def __init__(self, 9 | data_width, 10 | max_line_length): 11 | 12 | self.max_line_length = max_line_length 13 | self.data_width = data_width 14 | 15 | self.config = {} 16 | self.config["line_length"] = 0 17 | 18 | self.count = 0 19 | 20 | def set_config(self, new_config): 21 | for key, config_val in new_config.items(): 22 | if key not in self.config: 23 | AssertionError("Gave bad config...") 24 | else: 25 | self.config[key] = config_val 26 | 27 | assert self.config["line_length"] <= self.max_line_length, "Exceeded max line length" 28 | 29 | def interact(self, data_in, valid_in): 30 | ''' 31 | Returns (data_out, valid, align) 32 | ''' 33 | if valid_in == 0: 34 | return (data_in, 0, 0) 35 | else: 36 | align = 0 37 | self.count += 1 38 | if (self.count == self.config["line_length"]): 39 | self.count = 0 40 | align = 1 41 | return (data_in, 1, align) 42 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: CI Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | if: "!contains(github.event.head_commit.message, 'skip ci')" 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Checkout submodules 14 | shell: bash 15 | run: | 16 | auth_header="$(git config --local --get http.https://github.com/.extraheader)" 17 | git submodule sync --recursive 18 | git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 19 | - name: Pull and run docker 🐋 20 | shell: bash 21 | run: | 22 | docker run -it -d --name lakebox --mount type=bind,source="$(pwd)"/../lake,target=/lake stanfordaha/garnet:latest bash 23 | - name: Run tests ⚙️ 24 | shell: bash 25 | run: | 26 | # Remove docker lake and insert our version that we want to test 27 | docker exec -i lakebox /bin/bash -c "rm -rf /aha/lake" 28 | docker cp ../lake lakebox:/aha/lake 29 | # Tests require installation of verilator 30 | docker exec -i lakebox bash -c 'yes | apt-get install verilator' 31 | # Run the tests 32 | docker exec -i lakebox bash -c 'source /aha/bin/activate; cd lake; pytest -v tests/' 33 | -------------------------------------------------------------------------------- /lake/modules/virtual_remap_table.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from lake.modules.sram_stub import SRAMStub 3 | 4 | 5 | # Should we do this with registers or SRAM? 6 | # What options do we have? 7 | class VirtualRemapTable(Generator): 8 | def __init__(self, macro_width, logical_banks, macro_depth): 9 | super().__init__("virtual_remap_table") 10 | self._virt_addr = self.input("i_virt_addr", width) 11 | self._ren = self.input("i_ren", 1) 12 | self._out = self.output("o_data_out", width) 13 | self._in = self.input("i_data_in", width) 14 | self._clk = self.clock("i_clk") 15 | self._rst_n = self.reset("i_rst_n") 16 | 17 | # First wrap sram_stub 18 | sram_stub = SRAMStub(width, 1024) 19 | self.add_child_generator(f"u_sram_stub_0", sram_stub) 20 | self.wire(sram_stub.i_data, self._in) 21 | self.wire(self._out, sram_stub.o_data) 22 | 23 | self.wire(sram_stub.i_addr, 0) 24 | self.wire(sram_stub.i_cen, 0) 25 | self.wire(sram_stub.i_wen, 0) 26 | self.wire(sram_stub.i_clk, self._clk) 27 | self.wire(sram_stub.i_rst_n, self._rst_n) 28 | 29 | 30 | if __name__ == "__main__": 31 | vrt_dut = VirtualRemapTable() 32 | verilog(vrt_dut, filename="virtual_remap_table.sv", check_active_high=False) 33 | -------------------------------------------------------------------------------- /lake/attributes/formal_attr.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from enum import Enum 3 | from enum import auto 4 | 5 | 6 | class FormalSignalConstraint(Enum): 7 | X = auto() 8 | SET0 = auto() 9 | SET1 = auto() 10 | CLK = auto() 11 | RSTN = auto() 12 | SOLVE = auto() 13 | SEQUENCE = auto() 14 | 15 | 16 | class FormalAttr(kts.Attribute): 17 | def __init__(self, 18 | port_name, 19 | formalsig_cnst, 20 | module="all", 21 | doc_string=""): 22 | super().__init__() 23 | self.port_name = port_name 24 | self.formalsig_cnst = formalsig_cnst 25 | self.module = module 26 | self.documentation = doc_string 27 | 28 | def set_documentation(self, new_doc): 29 | self.documentation = new_doc 30 | 31 | def get_documentation(self): 32 | return self.documentation 33 | 34 | def get_annotation(self): 35 | # need annotation to be updated if port_name is updated 36 | # after creation like during cut_generator pass 37 | return f"{self.port_name}\t{self.formalsig_cnst.name}" 38 | 39 | def get_module(self): 40 | return self.module 41 | 42 | def get_port_name(self): 43 | return self.port_name 44 | 45 | def get_formal_ann(self): 46 | return self.formalsig_cnst 47 | -------------------------------------------------------------------------------- /tests/test_passthru.py: -------------------------------------------------------------------------------- 1 | from lake.models.passthru_model import PassthruModel 2 | from lake.modules.passthru import PassThroughMod 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | from kratos import * 8 | import random as rand 9 | import kratos as k 10 | import pytest 11 | 12 | 13 | def test_passthru(): 14 | 15 | model_pt = PassthruModel() 16 | config_dict = {} 17 | model_pt.set_config(config_dict) 18 | 19 | dut = PassThroughMod() 20 | magma_dut = k.util.to_magma(dut, 21 | flatten_array=True, 22 | check_flip_flop_always_ff=False) 23 | tester = fault.Tester(magma_dut, magma_dut.clk) 24 | 25 | tester.circuit.clk = 0 26 | tester.circuit.data_in = 0 27 | tester.eval() 28 | tester.step(2) 29 | tester.eval() 30 | tester.step(2) 31 | 32 | rand.seed(0) 33 | 34 | data_in = 0 35 | 36 | for i in range(1000): 37 | data_in = rand.randint(0, 1) 38 | tester.circuit.data_in = data_in 39 | data_out = model_pt.set_in(data_in) 40 | tester.eval() 41 | tester.circuit.data_out.expect(data_out) 42 | tester.step(2) 43 | 44 | with tempfile.TemporaryDirectory() as tempdir: 45 | tester.compile_and_run(target="verilator", 46 | directory=tempdir, 47 | magma_output="verilog", 48 | flags=["-Wno-fatal"]) 49 | -------------------------------------------------------------------------------- /lake/models/agg_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | 3 | 4 | class AggModel(Model): 5 | 6 | def __init__(self, 7 | num_elts): 8 | 9 | self.num_elts = num_elts 10 | 11 | self.shift_reg = [] 12 | for i in range(self.num_elts): 13 | self.shift_reg.append(0) 14 | 15 | self.word_count = 0 16 | self.valid_out = 0 17 | self.curr_valid = 0 18 | self.sr_copy = 0 19 | 20 | def set_config(self, **kwargs): 21 | self.word_count = 0 22 | self.valid_out = 0 23 | return 24 | 25 | def get_valid_out(self): 26 | return self.curr_valid 27 | 28 | def get_data_out(self): 29 | return self.sr_copy 30 | 31 | def interact(self, data, valid_in, align): 32 | ''' 33 | Returns (agg_out, valid_out, next_full) 34 | ''' 35 | 36 | self.curr_valid = self.valid_out 37 | self.sr_copy = self.shift_reg.copy() 38 | word_count_curr = self.word_count 39 | 40 | next_full = (valid_in & (word_count_curr == self.num_elts - 1)) | align 41 | if valid_in: 42 | if next_full: 43 | self.valid_out = 1 44 | self.word_count = 0 45 | else: 46 | self.valid_out = 0 47 | self.word_count = word_count_curr + 1 48 | self.shift_reg[word_count_curr] = data 49 | else: 50 | self.valid_out = 0 51 | return (self.sr_copy, self.curr_valid, next_full) 52 | -------------------------------------------------------------------------------- /lake/utils/sram_macro.py: -------------------------------------------------------------------------------- 1 | class SRAMMacroInfo: 2 | def __init__(self, 3 | name="default_name", 4 | addr_port_name="A", 5 | ce_port_name="CEB", 6 | clk_port_name="CLK", 7 | data_in_port_name="D", 8 | data_out_port_name="Q", 9 | wen_port_name="WEB", 10 | wtsel_port_name="WTSEL", 11 | rtsel_port_name="RTSEL", 12 | wtsel_value=0, 13 | rtsel_value=0): 14 | 15 | self.name = name 16 | self.addr_port_name = addr_port_name 17 | self.ce_port_name = ce_port_name 18 | self.clk_port_name = clk_port_name 19 | self.data_in_port_name = data_in_port_name 20 | self.data_out_port_name = data_out_port_name 21 | self.wen_port_name = wen_port_name 22 | self.wtsel_port_name = wtsel_port_name 23 | self.rtsel_port_name = rtsel_port_name 24 | 25 | self.wtsel_value = wtsel_value 26 | self.rtsel_value = rtsel_value 27 | 28 | self.ports = (addr_port_name, 29 | ce_port_name, 30 | clk_port_name, 31 | data_in_port_name, 32 | data_out_port_name, 33 | wen_port_name, 34 | wtsel_port_name, 35 | rtsel_port_name) 36 | 37 | def __hash__(self): 38 | return hash(self.ports) 39 | 40 | def __eq__(self, other: "SRAMMacroInfo"): 41 | return self.ports == other.ports 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Stanford AHA! Agile Hardware Center 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /lake/modules/magma_code/aggregator_magma.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import mantle 3 | import fault 4 | import math 5 | from math import ceil, log 6 | from mantle import Register, CounterModM, DFF 7 | 8 | 9 | def DefineAggregator(word_width, memory_width): 10 | class _Aggregator(m.Circuit): 11 | name = 'Aggregator_' + str(word_width) + '_' + str(memory_width) 12 | IO = ['INPUT_PIXELS', m.In(m.Bits[word_width]), 13 | 'AGGREGATED_OUTPUT', m.Out(m.Array[memory_width, m.Bits[word_width]]), 14 | 'VALID', m.Out(m.Bit)] + m.ClockInterface() 15 | 16 | @classmethod 17 | def definition(agg): 18 | # stores input pixels from each cycle 19 | regs = [Register(word_width) for i in range(memory_width)] 20 | regs[0].I <= agg.INPUT_PIXELS 21 | if memory_width == 1: 22 | agg.VALID <= 1 23 | else: 24 | # keep track of number of input pixels so far 25 | counter = CounterModM(memory_width, ceil(log(memory_width, 2))) 26 | # output VALID on same clock when data is in AGGREGATED_OUTPUT 27 | valid_dff = DFF() 28 | # valid when number of input pixels is same as memory_width 29 | valid_dff.I <= (counter.O == memory_width - 1) 30 | agg.VALID <= valid_dff.O 31 | # output all memory_width INPUT_PIXELS so far 32 | a = m.scan(regs, scanargs={'I': 'O'}) 33 | agg.AGGREGATED_OUTPUT <= a.O 34 | 35 | return _Aggregator 36 | 37 | 38 | def Aggregator(word_width, memory_width): 39 | return DefineAggregator(word_width, memory_width) 40 | -------------------------------------------------------------------------------- /lake/dsl/dsl_examples/memtile.py: -------------------------------------------------------------------------------- 1 | from lake.dsl.lake_imports import * 2 | 3 | # Amber memory tile: 2 aggs -> SRAM -> 2 tbs 4 | tile = Lake(16, 2, 2) 5 | 6 | fw = 4 # fetch_width for SRAM 7 | num_aggs, num_tbs = 2, 2 8 | 9 | # wide fetch SRAM 10 | sram_port = MemPort(1, 1) 11 | sram_params = make_params("sram", 512, read_write_port_width=fw) 12 | tile.add_memory(sram_params, read_write_ports=[sram_port]) 13 | 14 | # aggregators: serial to parallel converters before SRAM 15 | for i in range(num_aggs): 16 | mem_name = f"agg{i}" 17 | write_port, read_port = MemPort(1, 1), MemPort(0, 1) 18 | agg_params = make_params(mem_name, 19 | fw, 20 | read_port_width=fw, 21 | write_port_width=1) 22 | tile.add_memory(agg_params, [write_port], [read_port]) 23 | 24 | tile.add_input_edge(i, mem_name) 25 | tile.add_edge(mem_name, 26 | "sram", 27 | dim=6, 28 | max_range=65536) 29 | 30 | # transpose buffers: parallel to serial converters after SRAM 31 | for i in range(num_tbs): 32 | mem_name = f"tb{i}" 33 | write_port, read_port = MemPort(1, 1), MemPort(0, 1) 34 | tb_params = make_params(mem_name, 35 | fw * 2, # double buffer 36 | read_port_width=1, 37 | write_port_width=fw) 38 | tile.add_memory(tb_params, [write_port], [read_port]) 39 | 40 | tile.add_output_edge(i, mem_name) 41 | tile.add_edge("sram", 42 | mem_name, 43 | dim=6, 44 | max_range=65536) 45 | 46 | tile.construct_lake("memtile") 47 | -------------------------------------------------------------------------------- /lake/passes/cut_generator.py: -------------------------------------------------------------------------------- 1 | import kratos 2 | from lake.attributes.formal_attr import * 3 | 4 | 5 | def add_attrs(port, new_port, new_port_name): 6 | attrs = port.find_attribute(lambda a: isinstance(a, FormalAttr)) 7 | for attr in attrs: 8 | attr.port_name = new_port_name 9 | new_port.add_attribute(attr) 10 | 11 | 12 | def cut_generator(gen: kratos.Generator, suffix="_top"): 13 | # cut the generator from it's parent scope 14 | parent_gen = gen.internal_generator.parent_generator() 15 | assert parent_gen is not None, f"{gen.name} does not have a parent" 16 | new_ports = set() 17 | for port_name in gen.ports: 18 | port = gen.ports[port_name] 19 | # only need to worry about the output 20 | if port.port_direction == kratos.PortDirection.In.value: 21 | continue 22 | 23 | new_port_name = port_name + suffix 24 | new_port = parent_gen.port(port, new_port_name) 25 | 26 | add_attrs(port, new_port, new_port_name) 27 | 28 | # flip the port direction 29 | new_port.port_direction = kratos.PortDirection.In.value 30 | 31 | new_port.move_sink_to(port, new_port, parent_gen, True) 32 | new_ports.add(new_port.name) 33 | # remove from parent 34 | parent_gen.remove_child_generator(gen.internal_generator) 35 | # lifting things up 36 | while parent_gen.parent_generator() is not None: 37 | p_gen = parent_gen.parent_generator() 38 | for port_name in new_ports: 39 | port = parent_gen.get_port(port_name) 40 | new_port = p_gen.port(port, port_name) 41 | p_gen.add_stmt(port.assign(new_port)) 42 | add_attrs(port, new_port, port_name) 43 | parent_gen = p_gen 44 | -------------------------------------------------------------------------------- /lake/modules/ready_valid_interface.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from kratos import Generator 3 | from lake.utils.spec_enum import Direction 4 | 5 | 6 | class RVInterface(): 7 | 8 | def __init__(self, generator: Generator, direction: Direction, **port_kwargs): 9 | 10 | self.port_interface = { 11 | 'data': None, 12 | 'valid': None, 13 | 'ready': None 14 | } 15 | self.generator = generator 16 | self.direction = direction 17 | self.port_ready = None 18 | self.port_valid = None 19 | self.port = None 20 | self.signal_name = port_kwargs['name'] 21 | if self.direction == Direction.IN: 22 | self.port = self.generator.input(**port_kwargs) 23 | self.port_valid = self.generator.input(f"{self.signal_name}_valid", 1) 24 | self.port_ready = self.generator.output(f"{self.signal_name}_ready", 1) 25 | elif self.direction == Direction.OUT: 26 | self.port = self.generator.output(**port_kwargs) 27 | self.port_valid = self.generator.output(f"{self.signal_name}_valid", 1) 28 | self.port_ready = self.generator.input(f"{self.signal_name}_ready", 1) 29 | else: 30 | raise NotImplementedError 31 | 32 | self.port_interface['data'] = self.port 33 | self.port_interface['valid'] = self.port_valid 34 | self.port_interface['ready'] = self.port_ready 35 | 36 | def get_port_interface(self): 37 | return self.port_interface 38 | 39 | def get_port(self): 40 | return self.port 41 | 42 | def get_data(self): 43 | return self.port 44 | 45 | def get_valid(self): 46 | return self.port_valid 47 | 48 | def get_ready(self): 49 | return self.port_ready 50 | -------------------------------------------------------------------------------- /lake/models/sram_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | import kratos as kts 3 | 4 | 5 | class SRAMModel(Model): 6 | def __init__(self, 7 | data_width, 8 | width_mult, 9 | depth, 10 | num_tiles): 11 | self.data_width = data_width 12 | self.width_mult = width_mult 13 | self.depth = depth 14 | self.num_tiles = num_tiles 15 | self.address_width = kts.clog2(self.num_tiles * self.depth) 16 | 17 | self.chain_idx_bits = max(1, kts.clog2(num_tiles)) 18 | 19 | self.chain_idx_tile = 0 20 | 21 | self.rd_reg = [] 22 | for i in range(self.width_mult): 23 | self.rd_reg.append(0) 24 | self.mem = [] 25 | for i in range(self.depth): 26 | row = [] 27 | for j in range(self.width_mult): 28 | row.append(0) 29 | self.mem.append(row) 30 | 31 | def set_config(self, new_config): 32 | # No configuration space 33 | return 34 | 35 | def interact(self, 36 | wen, 37 | cen, 38 | addr, 39 | data): 40 | ''' 41 | Returns (rd_reg) 42 | ''' 43 | 44 | rd_reg_ret = self.rd_reg 45 | 46 | addr = addr % self.depth 47 | 48 | # no-op 49 | if cen == 0: 50 | return rd_reg_ret 51 | elif wen == 1: 52 | self.mem[addr] = data.copy() 53 | else: 54 | # Read 55 | self.rd_reg = self.mem[addr] 56 | 57 | return list(rd_reg_ret) 58 | 59 | def get_rd_reg(self): 60 | return list(self.rd_reg) 61 | 62 | def dump_mem(self): 63 | for i in range(self.depth): 64 | print(f"addr: {i}, data: {self.mem[i]}") 65 | -------------------------------------------------------------------------------- /lake/spec/config_memory.py: -------------------------------------------------------------------------------- 1 | import kratos 2 | import random as rand 3 | from lake.attributes.config_reg_attr import ConfigRegAttr 4 | from kratos import PortDirection, always_ff, posedge, negedge 5 | from lake.modules.ready_valid_interface import RVInterface 6 | from lake.utils.spec_enum import Direction 7 | 8 | 9 | class ConfigMemory(kratos.Generator): 10 | 11 | """ 12 | Config memory wrapper module 13 | """ 14 | def __init__(self, width, name=""): 15 | if name == "": 16 | super().__init__(f"config_memory_{width}") 17 | else: 18 | super().__init__(f"config_memory_{width}_{name}") 19 | 20 | self.width = width 21 | 22 | self._clk = self.clock("clk") 23 | self._rst_n = self.reset("rst_n") 24 | # self._flush = self.reset("flush", is_async=False) 25 | 26 | self._flush = self.input("flush", 1) 27 | # self.add_attribute("sync-reset=flush") 28 | 29 | self._config_memory = self.input("config_memory", self.width, packed=True) 30 | # Wrap it in a module 31 | self._config_memory_harden = self.var("config_memory_harden", self.width, packed=True) 32 | self._config_memory_harden_en = self.input("config_memory_wen", 1) 33 | 34 | self._config_memory_out = self.output("config_memory_out", self.width, packed=True) 35 | 36 | @always_ff((posedge, "clk"), (negedge, "rst_n")) 37 | def hardened_config_flop(): 38 | if ~self._rst_n: 39 | self._config_memory_harden = 0 40 | elif self._config_memory_harden_en: 41 | self._config_memory_harden = self._config_memory 42 | self.add_code(hardened_config_flop) 43 | 44 | self.wire(self._config_memory_out, self._config_memory_harden) 45 | 46 | def get_width(self): 47 | return self.width 48 | -------------------------------------------------------------------------------- /lake/models/demux_reads_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | 3 | 4 | class DemuxReadsModel(Model): 5 | ''' 6 | Model for agg aligner 7 | ''' 8 | def __init__(self, 9 | fetch_width, 10 | data_width, 11 | banks, 12 | int_out_ports): 13 | 14 | self.fetch_width = fetch_width 15 | self.data_width = data_width 16 | self.fw_int = int(fetch_width / data_width) 17 | self.banks = banks 18 | self.int_out_ports = int_out_ports 19 | 20 | # Doesn't actually have configuration state 21 | def set_config(self, new_config): 22 | return 23 | 24 | def interact(self, data_in, valid_in, port_in, mem_valid_data): 25 | ''' 26 | Returns (data_out, valid_out, mem_valid_data_out) 27 | ''' 28 | data_out = [] 29 | valid_out = [] 30 | mem_valid_data_out = [] 31 | for i in range(self.int_out_ports): 32 | row = [] 33 | for j in range(self.fw_int): 34 | row.append(0) 35 | data_out.append(list(row)) 36 | valid_out.append(0) 37 | mem_valid_data_out.append(0) 38 | 39 | no_valid = True 40 | for i in range(self.banks): 41 | if (valid_in[i]): 42 | no_valid = False 43 | 44 | if no_valid: 45 | return (data_out, valid_out, mem_valid_data_out) 46 | 47 | for i in range(self.int_out_ports): 48 | for j in range(self.banks): 49 | if (valid_in[j] & (port_in[j] == (1 << i))): 50 | data_out[i] = list(data_in[j]) 51 | valid_out[i] = 1 52 | mem_valid_data_out[i] = mem_valid_data[j] 53 | break 54 | 55 | return (data_out, valid_out, mem_valid_data_out) 56 | -------------------------------------------------------------------------------- /lake/modules/hwtypes/test_sram_hwtypes.py: -------------------------------------------------------------------------------- 1 | from lake.modules.hwtypes.sram_stub import sram_stub 2 | from peak import family_closure, family 3 | from math import log2 4 | from lake.modules.hwtypes.utils import * 5 | from hwtypes import Bit, BitVector 6 | 7 | import magma as m 8 | import fault 9 | import tempfile 10 | import kratos as kts 11 | import random as rand 12 | import pytest 13 | 14 | 15 | # This doesn't work 16 | @pytest.mark.skip 17 | # this test tests sram_stub as well as part of sram_wrapper in the kratos code 18 | @pytest.mark.parametrize("data_width", [16, 32]) 19 | @pytest.mark.parametrize("mem_depth", [512, 1024]) 20 | @pytest.mark.parametrize("fetch_width", [1, 2]) 21 | def test_sram_basic(data_width, 22 | mem_depth, 23 | fetch_width): 24 | 25 | # Set up model... 26 | # sram_py = sram_stub(depth, data_width, width_mult, family.PyFamily()) 27 | # model_sram = sram_py(family=family.PyFamily())() 28 | 29 | sram_magma = sram_stub(mem_depth, data_width, fetch_width, family=family.MagmaFamily()) 30 | sram_magma_defn = sram_magma(family=family.MagmaFamily()) 31 | tester = fault.Tester(sram_magma_defn, sram_magma_defn.CLK) 32 | 33 | data = 0 34 | for i in range(100): 35 | tester.circuit.wen = i % 2 36 | tester.circuit.cen = 1 37 | tester.circuit.data_in = data 38 | tester.circuit.addr = data 39 | data = data + 1 40 | tester.eval() 41 | tester.step(2) 42 | 43 | with tempfile.TemporaryDirectory() as tempdir: 44 | tester.compile_and_run(target="verilator", 45 | directory=tempdir, 46 | flags=["-Wno-fatal"]) 47 | 48 | 49 | if __name__ == "__main__": 50 | test_sram_basic(data_width=16, 51 | mem_depth=512, 52 | fetch_width=4) 53 | -------------------------------------------------------------------------------- /lake/models/addr_gen_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | 3 | 4 | class AddrGenModel(Model): 5 | 6 | def __init__(self, iterator_support, address_width): 7 | self.iterator_support = iterator_support 8 | self.address_width = address_width 9 | 10 | self.config = {} 11 | 12 | self.config["starting_addr"] = 0 13 | self.config["dimensionality"] = 0 14 | 15 | self.dim_cnt = [] 16 | 17 | self.address = 0 18 | 19 | for i in range(self.iterator_support): 20 | self.config[f"ranges_{i}"] = 0 21 | self.config[f"strides_{i}"] = 0 22 | self.dim_cnt.append(0) 23 | 24 | def set_config(self, new_config): 25 | for key, config_val in new_config.items(): 26 | if key not in self.config: 27 | AssertionError("Gave bad config...") 28 | else: 29 | self.config[key] = config_val 30 | for i in range(self.iterator_support): 31 | self.dim_cnt[i] = 0 32 | self.address = 0 + self.config["starting_addr"] 33 | 34 | def get_address(self): 35 | return self.address 36 | 37 | def step(self): 38 | for i in range(self.config["dimensionality"]): 39 | if (i == 0): 40 | update_curr = True 41 | 42 | if update_curr: 43 | self.dim_cnt[i] = self.dim_cnt[i] + 1 44 | if (self.dim_cnt[i] == self.config[f"ranges_{i}"]): 45 | self.dim_cnt[i] = 0 46 | else: 47 | break 48 | else: 49 | break 50 | 51 | self.address = self.config["starting_addr"] 52 | for i in range(self.config["dimensionality"]): 53 | offset = self.dim_cnt[i] * self.config[f"strides_{i}"] 54 | self.address = self.address + offset 55 | -------------------------------------------------------------------------------- /lake/modules/sw_net.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | 3 | 4 | class SW_NET(Generator): 5 | ''' 6 | The switching network will bring in multiple items per cycle and direct 7 | them to the appropriate aggregation buffer 8 | ''' 9 | def __init__(self, 10 | interconnect_in, 11 | offsets, # Offset between the interconnect_in ports 12 | data_width, 13 | memory_width, # Will be the size of the agg buffer 14 | num_aggregators, 15 | num_banks, 16 | stride_0): 17 | super().__init__("sw_net") 18 | 19 | self.interconnect_in = interconnect_in 20 | self.offsets = offsets 21 | self.data_width = data_width 22 | self.memory_width = memory_width 23 | self.num_aggregators = num_aggregators 24 | self.num_banks = num_banks 25 | self.stride_0 = stride_0 26 | 27 | # PORT DEFS: begin 28 | self._clk = self.clock("clk") 29 | self._clk_en = self.input("clk_en", 1) 30 | self._reset = self.reset("reset") 31 | self._flush = self.input("flush", 1) 32 | 33 | self._data_in = self.input("data_in", 34 | data_width, 35 | size=interconnect_in, 36 | packed=True, 37 | explicit_array=True) # Actually an array 38 | self._valid_in = self.input("valid_in", interconnect_in) 39 | 40 | self._data_out # should be interconnect_in as well - basically a single stage input pipe 41 | self._agg_index_out # should be the num_aggregators 42 | self._valid_out # Should be the num of banks 43 | 44 | # Somehow get data to the output -> 45 | # probably with an address -> which will be 0-num_aggregators 46 | # Then tag it with valid or not 47 | -------------------------------------------------------------------------------- /tests/test_pohan_wrapper.py: -------------------------------------------------------------------------------- 1 | from lake.top.pohan_top import PohanTop 2 | from kratos import * 3 | import pytest 4 | import argparse 5 | 6 | 7 | @pytest.mark.skip 8 | def test_gen_dual_port(config_path="/aha/config.json", 9 | base_vlog_filename="default_base", 10 | wrapper_vlog_filename="default_wrapper", 11 | vlog_extension="v"): 12 | 13 | print(f"Using configuration file at: {config_path}") 14 | pohan_top = PohanTop() 15 | pohan_top.get_flat_verilog(filename=f"{base_vlog_filename}.{vlog_extension}") 16 | pohan_top_wrapper = pohan_top.wrapper(wrapper_vlog_filename=wrapper_vlog_filename, 17 | vlog_extension=vlog_extension, 18 | config_path=config_path) 19 | print(f"Generated base verilog file : {base_vlog_filename}.{vlog_extension}") 20 | print(f"Generated wrapper verilog file : {wrapper_vlog_filename}.{vlog_extension}") 21 | return pohan_top_wrapper 22 | 23 | 24 | if __name__ == "__main__": 25 | ap = argparse.ArgumentParser(description="Parse arguments for PohanTop") 26 | ap.add_argument("-f", 27 | type=str, 28 | help="path to configuration json file", 29 | default="/aha/config.json") 30 | ap.add_argument("-b", 31 | type=str, 32 | help="base verilog filename", 33 | default="default_base") 34 | ap.add_argument("-w", 35 | type=str, 36 | help="wrapper verilog filename", 37 | default="default_wrapper") 38 | ap.add_argument("-e", 39 | type=str, 40 | help="verilog extension (v or sv)", 41 | default="v") 42 | 43 | args = ap.parse_args() 44 | 45 | test_gen_dual_port(config_path=args.f, 46 | base_vlog_filename=args.b, 47 | wrapper_vlog_filename=args.w, 48 | vlog_extension=args.e) 49 | -------------------------------------------------------------------------------- /lake/models/prefetcher_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | from lake.models.reg_fifo_model import RegFIFOModel 3 | 4 | 5 | class PrefetcherModel(Model): 6 | def __init__(self, 7 | fetch_width, 8 | data_width, 9 | max_prefetch): 10 | 11 | self.fetch_width = fetch_width 12 | self.max_prefetch = max_prefetch 13 | self.data_width = data_width 14 | self.fw_int = int(self.fetch_width / self.data_width) 15 | 16 | self.config = {} 17 | self.config['input_latency'] = 0 18 | 19 | self.fifo = RegFIFOModel(data_width=self.data_width, 20 | width_mult=self.fw_int, 21 | depth=self.max_prefetch) 22 | 23 | self.cnt = 0 24 | 25 | def set_config(self, new_config): 26 | # Configure top level 27 | for key, config_val in new_config.items(): 28 | if key not in self.config: 29 | AssertionError("Gave bad config...") 30 | else: 31 | self.config[key] = config_val 32 | 33 | def update_cnt(self, valid_read, tba_rdy): 34 | if valid_read != 0 and tba_rdy == 0: 35 | self.cnt -= 1 36 | elif valid_read != 0 & tba_rdy != 0 and not self.fifo.get_full(): 37 | self.cnt += 1 38 | 39 | def get_step(self): 40 | return int((self.cnt + self.config['input_latency']) < (self.max_prefetch)) 41 | 42 | def get_cnt(self): 43 | return self.cnt 44 | 45 | def interact(self, data_in, valid_read, tba_rdy, mem_valid_data): 46 | ''' 47 | Returns (data, valid, step, mem_valid_data_out) 48 | ''' 49 | (d_out, v_out, empty, full, mem_valid_data_out) = \ 50 | self.fifo.interact(valid_read, tba_rdy, data_in, mem_valid_data) 51 | stp = self.get_step() 52 | self.update_cnt(valid_read, tba_rdy) 53 | if type(d_out) is list: 54 | return (d_out.copy(), v_out, stp, mem_valid_data_out) 55 | else: 56 | return (d_out, v_out, stp, mem_valid_data_out) 57 | -------------------------------------------------------------------------------- /lake/models/register_file_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | 3 | 4 | class RegisterFileModel(Model): 5 | def __init__(self, 6 | data_width, 7 | write_ports, 8 | read_ports, 9 | width_mult, 10 | depth): 11 | 12 | self.width_mult = width_mult 13 | self.data_width = data_width 14 | self.write_ports = write_ports 15 | self.read_ports = read_ports 16 | self.depth = depth 17 | 18 | self.mem = [] 19 | for i in range(self.depth): 20 | row = [] 21 | for j in range(self.width_mult): 22 | row.append(0) 23 | self.mem.append(row) 24 | 25 | def set_config(self, new_config): 26 | # No configuration space 27 | return 28 | 29 | def interact(self, 30 | wen, 31 | wr_addr, 32 | rd_addr, 33 | data_in): 34 | ''' 35 | Returns rd_data (list) 36 | ''' 37 | 38 | # Do the read first - data won't pass through on the same cycle 39 | 40 | if type(rd_addr) is int: 41 | rd_addr = [rd_addr] 42 | if type(wr_addr) is int: 43 | wr_addr = [wr_addr] 44 | if type(wen) is int: 45 | wen = [wen] 46 | if type(data_in) is int: 47 | data_in = [data_in] 48 | elif type(data_in[0]) is int: 49 | data_in = [data_in] 50 | 51 | ret_data = [] 52 | for i in range(self.read_ports): 53 | ret_data.append(self.mem[rd_addr[i]].copy()) 54 | 55 | for i in range(self.write_ports): 56 | if wen[i] == 1: 57 | self.mem[wr_addr[i]] = data_in[i].copy() 58 | 59 | return ret_data.copy() 60 | 61 | def get_reads(self, rd_addr): 62 | ret_data = [] 63 | for i in range(self.read_ports): 64 | ret_data.append(self.mem[rd_addr[i]].copy()) 65 | 66 | def dump_mem(self): 67 | for i in range(self.depth): 68 | print(f"addr: {i}, data: {self.mem[i]}") 69 | -------------------------------------------------------------------------------- /tests/test_spec_hw/Makefile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ------------------------------------------------------------------- 4 | # Variables 5 | # ------------------------------------------------------------------- 6 | TOOL ?= XCELIUM 7 | export WAVEFORM ?= 0 8 | export BITSTREAM_SIZE ?= 0 9 | 10 | # ------------------------------------------------------------------- 11 | # Compile Parameters 12 | # ------------------------------------------------------------------- 13 | CLK_PERIOD ?= 1ns 14 | 15 | DESIGN_FILES += tb.sv \ 16 | inputs/lakespec.sv 17 | 18 | XRUN = xrun \ 19 | -64bit \ 20 | -sv \ 21 | -timescale 1ns/1ns \ 22 | -debug \ 23 | -sysv \ 24 | -top tb \ 25 | -elaborate \ 26 | -l xrun.log \ 27 | -covoverwrite \ 28 | +maxdelays \ 29 | -access r \ 30 | -notimingchecks \ 31 | $(COMPILE_ARGS) \ 32 | $(INPUT_ARGS) 33 | 34 | # ------------------------------------------------------------------- 35 | # Compile & Run 36 | # ------------------------------------------------------------------- 37 | COMPILE_RTL_ARGS += +define+CLK_PERIOD=$(CLK_PERIOD) 38 | COMPILE_GLS_ARGS += +define+CLK_PERIOD=$(CLK_PERIOD) 39 | ifeq ($(TOOL), XCELIUM) 40 | COMPILE = $(XRUN) 41 | RUN = xrun -R 42 | else 43 | @echo "TOOL must be XCELIUM" 44 | endif 45 | 46 | ifeq ($(TOOL), XCELIUM) 47 | DUMP_ARGS = -input run_sim.tcl 48 | else ifeq ($(TOOL), VCS) 49 | DUMP_ARGS = -ucli -i dump_fsdb.tcl 50 | endif 51 | 52 | .PHONY: compile 53 | compile: COMPILE_ARGS = $(COMPILE_RTL_ARGS) 54 | compile: INPUT_ARGS = $(DESIGN_FILES) $(TB_FILES) $(IP_FILES) 55 | compile: 56 | $(COMPILE) $(shell cat "./inputs/comp_args.txt") 57 | 58 | .PHONY: run 59 | run: 60 | $(RUN) $(DUMP_ARGS) $(RUN_ARGS) $(APP_ARGS) +TEST_DIRECTORY=./ $(shell cat "./inputs/PARGS.txt") 61 | 62 | .PHONY: compare 63 | compare: 64 | python test_comparison.py --dir ./ 65 | 66 | .PHONY: sim 67 | sim: compile run compare 68 | 69 | # ------------------------------------------------------------------- 70 | # Clean 71 | # ------------------------------------------------------------------- 72 | .PHONY: clean 73 | clean: 74 | rm -rf simv.daidir 75 | -------------------------------------------------------------------------------- /lake/modules/pipe_reg.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | 3 | 4 | class PipeReg(Generator): 5 | def __init__(self, 6 | data_width, 7 | stages): 8 | super().__init__("pipe_reg") 9 | self._data_in = self.input("data_in", data_width) 10 | self._data_out = self.output("data_out", data_width) 11 | self._clk = self.clock("clk") 12 | self._rst_n = self.reset("rst_n") 13 | self._clk_en = self.input("clk_en", 1) 14 | 15 | self.stages = stages 16 | 17 | if stages > 1: 18 | self._reg_array = self.var("reg_array", 19 | data_width, 20 | size=stages, 21 | packed=True, 22 | explicit_array=True) 23 | self.add_code(self.stage_elab) 24 | self.add_code(self.set_out) 25 | elif stages == 1: 26 | self._reg_ = self.var("reg_", data_width) 27 | self.add_code(self.set_out_one_stage) 28 | self.wire(self._data_out, self._reg_) 29 | else: 30 | self.wire(self._data_out, self._data_in) 31 | 32 | @always_ff((posedge, "clk"), (negedge, "rst_n")) 33 | def stage_elab(self): 34 | if ~self._rst_n: 35 | self._reg_array = 0 36 | elif self._clk_en: 37 | self._reg_array[0] = self._data_in 38 | for i in range(self.stages - 1): 39 | self._reg_array[i + 1] = self._reg_array[i] 40 | 41 | @always_comb 42 | def set_out(self): 43 | # Combinationally set this to the last register in the stage array. 44 | self._data_out = self._reg_array[self.stages - 1] 45 | 46 | @always_ff((posedge, "clk"), (negedge, "rst_n")) 47 | def set_out_one_stage(self): 48 | if ~self._rst_n: 49 | self._reg_ = 0 50 | elif self._clk_en: 51 | self._reg_ = self._data_in 52 | 53 | 54 | if __name__ == "__main__": 55 | stages = 3 56 | piperegdut = PipeReg(16, stages) 57 | verilog(piperegdut, filename="pipe_reg.sv") 58 | -------------------------------------------------------------------------------- /tests/test_agg_aligner.py: -------------------------------------------------------------------------------- 1 | from lake.models.agg_aligner_model import AggAlignerModel 2 | from lake.modules.agg_aligner import AggAligner 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | import kratos as k 8 | import random as rand 9 | 10 | 11 | def test_agg_aligner_basic(data_width=16, 12 | max_line_length=2048, 13 | line_length=64): 14 | 15 | # Set up model... 16 | model_al = AggAlignerModel(data_width=data_width, max_line_length=max_line_length) 17 | new_config = {} 18 | new_config['line_length'] = line_length 19 | model_al.set_config(new_config=new_config) 20 | ### 21 | 22 | # Set up dut... 23 | dut = AggAligner(data_width=data_width, 24 | max_line_length=max_line_length) 25 | 26 | magma_dut = k.util.to_magma(dut, flatten_array=True, 27 | check_flip_flop_always_ff=False) 28 | tester = fault.Tester(magma_dut, magma_dut.clk) 29 | ### 30 | 31 | for key, value in new_config.items(): 32 | setattr(tester.circuit, key, value) 33 | 34 | # initial reset 35 | tester.circuit.clk = 0 36 | tester.circuit.rst_n = 0 37 | tester.step(2) 38 | tester.circuit.rst_n = 1 39 | tester.step(2) 40 | 41 | rand.seed(0) 42 | 43 | data_in = 0 44 | 45 | for i in range(1000): 46 | new_val = rand.randint(0, 1) 47 | 48 | (model_dat, model_val, model_align) = model_al.interact(data_in, new_val) 49 | tester.circuit.in_valid = new_val 50 | tester.circuit.in_dat = data_in 51 | tester.eval() 52 | 53 | tester.circuit.out_valid.expect(model_val) 54 | tester.circuit.align.expect(model_align) 55 | tester.circuit.out_dat.expect(model_dat) 56 | 57 | tester.step(2) 58 | 59 | data_in += 1 60 | 61 | with tempfile.TemporaryDirectory() as tempdir: 62 | tester.compile_and_run(target="verilator", 63 | directory=tempdir, 64 | magma_output="verilog", 65 | flags=["-Wno-fatal"]) 66 | -------------------------------------------------------------------------------- /lake/spec/test_LB.py: -------------------------------------------------------------------------------- 1 | from ir import * 2 | 3 | 4 | def test_LB(): 5 | ''' 6 | Conv 33 linebuffer on 6x6 image 7 | schedule: {input[i_wr, j_wr]->[i_wr + 6*j_wr + 0]: 0<=i_wr<=5, 0<=j_wr<=5} 8 | schedule: {output[i_rd, j_rd]->[i_rd + 6*j_rd + 14]: 0<=i_wr<=3, 0<=j_wr<=3} 9 | ''' 10 | 11 | # input accessor 12 | i = var("i_wr", 0, 5) 13 | j = var("j_wr", 0, 5) 14 | acc_in = expr([i, j], [([bound("i_ss", 0, 5), bound("j_ss", 0, 5)], [1, 6, 0])]) 15 | acc_ctr_in = map([i, j], [acc_in]) 16 | 17 | # input address generator 18 | i = var("i_in", 0, 5) 19 | j = var("j_in", 0, 5) 20 | addr_in = expr([i, j], [([bound("i_ss", 0, 5), bound("j_ss", 0, 5)], [1, 6, 0])]) 21 | addr_gen_in = map([i, j], [addr_in]) 22 | 23 | # output accessor 24 | ir = var("i_rd", 0, 3) 25 | jr = var("j_rd", 0, 3) 26 | acc_out = expr([ir, jr], [([bound("ir_ss", 0, 3), bound("jr_ss", 0, 3)], [1, 6, 14])]) 27 | acc_ctr_out = map([ir, jr], [acc_out]) 28 | 29 | # output address generator : the right bottom of the 3x3 window 30 | ir = var("i_out", 0, 3) 31 | jr = var("j_out", 0, 3) 32 | addr_out = expr([ir, jr], [([bound("ir_ss", 0, 3), bound("jr_ss", 0, 3)], [1, 6, 14])]) 33 | addr_gen_out = map([ir, jr], [addr_out]) 34 | # test stream 35 | cycle = 36 36 | for itr in range(cycle): 37 | sched_rd = acc_ctr_out.eval() 38 | sched_wr = acc_ctr_in.eval() 39 | is_rd = (sched_rd == itr) 40 | is_wr = (sched_wr == itr) 41 | wr_idx = acc_ctr_in.getDomain() 42 | rd_idx = acc_ctr_out.getDomain() 43 | # print ("cycle:", itr, " write idx: ", wr_idx, "sched_wr:", sched_wr) 44 | if is_wr: 45 | print("cycle:", itr, " write ", wr_idx) 46 | print("write address: ", addr_gen_in.eval()) 47 | acc_ctr_in.update() 48 | addr_gen_in.update() 49 | if is_rd: 50 | print("cycle:", itr, " read ", rd_idx) 51 | print("read address: ", addr_gen_out.eval()) 52 | acc_ctr_out.update() 53 | addr_gen_out.update() 54 | print("\n***************************************\n") 55 | 56 | 57 | if __name__ == "__main__": 58 | test_LB() 59 | -------------------------------------------------------------------------------- /tests/test_aggregator.py: -------------------------------------------------------------------------------- 1 | from lake.modules.aggregator import * 2 | from lake.models.agg_model import AggModel 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | import kratos as k 8 | import random as rand 9 | import pytest 10 | 11 | 12 | def test_aggregator_basic(word_width=16, mem_word_width=4): 13 | 14 | model_agg = AggModel(num_elts=mem_word_width) 15 | # No actual configuration to be set 16 | model_agg.set_config() 17 | 18 | # get verilog file that needs to be copied to agg_dump directory 19 | # before running verilator 20 | dut = Aggregator(word_width=word_width, mem_word_width=mem_word_width) 21 | magma_dut = k.util.to_magma(dut, flatten_array=True, 22 | check_flip_flop_always_ff=False) 23 | tester = fault.Tester(magma_dut, magma_dut.clk) 24 | 25 | num_per_piece = int(mem_word_width / word_width) 26 | 27 | # initial reset 28 | tester.circuit.clk = 0 29 | tester.circuit.rst_n = 0 30 | tester.step(2) 31 | tester.circuit.rst_n = 1 32 | tester.step(2) 33 | 34 | rand.seed(0) 35 | 36 | data_in = 0 37 | valid_in = 0 38 | 39 | for i in range(2500): 40 | 41 | valid_in = rand.randint(0, 1) 42 | data_in = rand.randint(0, 2 ** 16 - 1) 43 | align = rand.randint(0, 1) 44 | # Circuit 45 | tester.circuit.valid_in = valid_in 46 | tester.circuit.in_pixels = data_in 47 | tester.circuit.align = align 48 | # Model 49 | (model_dat, model_val, model_nf) = model_agg.interact(data_in, valid_in, align) 50 | 51 | tester.eval() 52 | 53 | tester.circuit.valid_out.expect(model_val) 54 | if model_val == 1: 55 | for i in range(mem_word_width): 56 | getattr(tester.circuit, f"agg_out_{i}").expect(model_dat[i]) 57 | 58 | tester.step(2) 59 | 60 | with tempfile.TemporaryDirectory() as tempdir: 61 | tester.compile_and_run(target="verilator", 62 | directory=tempdir, 63 | magma_output="verilog", 64 | flags=["-Wno-fatal"]) 65 | 66 | 67 | if __name__ == "__main__": 68 | test_aggregator_basic() 69 | -------------------------------------------------------------------------------- /tests/test_pipe_reg.py: -------------------------------------------------------------------------------- 1 | from lake.models.pipe_reg_model import PipeRegModel 2 | from lake.modules.pipe_reg import PipeReg 3 | from lake.passes.passes import lift_config_reg 4 | import _kratos 5 | import magma as m 6 | from magma import * 7 | import fault 8 | import tempfile 9 | import kratos as k 10 | import random as rand 11 | import pytest 12 | 13 | 14 | @pytest.mark.parametrize("stages", [1, 2, 4, 8]) 15 | def test_pipe_reg_basic(stages, 16 | data_width=16): 17 | 18 | # Set up model.. 19 | model_pr = PipeRegModel(data_width=data_width, 20 | stages=stages) 21 | new_config = {} 22 | model_pr.set_config(new_config=new_config) 23 | ### 24 | 25 | # Set up dut... 26 | dut = PipeReg(data_width=data_width, 27 | stages=stages) 28 | lift_config_reg(dut.internal_generator) 29 | magma_dut = k.util.to_magma(dut, flatten_array=True, 30 | check_multiple_driver=False, 31 | check_flip_flop_always_ff=False) 32 | tester = fault.Tester(magma_dut, magma_dut.clk) 33 | ### 34 | 35 | for key, value in new_config.items(): 36 | setattr(tester.circuit, key, value) 37 | 38 | # initial reset 39 | tester.circuit.clk = 0 40 | tester.circuit.rst_n = 0 41 | tester.circuit.clk_en = 1 42 | tester.circuit.data_in = 0 43 | tester.step(2) 44 | tester.circuit.rst_n = 1 45 | tester.step(2) 46 | # Seed for posterity 47 | rand.seed(0) 48 | 49 | data_in = 0 50 | 51 | for i in range(1000): 52 | 53 | data_in = rand.randint(0, 2 ** data_width - 1) 54 | tester.circuit.data_in = data_in 55 | data_out_exp = model_pr.update_data(data_in) 56 | tester.eval() 57 | 58 | if (stages == 0): 59 | tester.circuit.data_out.expect(data_out_exp) 60 | 61 | tester.step(2) 62 | 63 | if (stages > 0): 64 | tester.circuit.data_out.expect(data_out_exp) 65 | 66 | with tempfile.TemporaryDirectory() as tempdir: 67 | tester.compile_and_run(target="verilator", 68 | directory=tempdir, 69 | magma_output="verilog", 70 | flags=["-Wno-fatal"]) 71 | -------------------------------------------------------------------------------- /lake/modules/magma_code/magma_aggregator.py: -------------------------------------------------------------------------------- 1 | import magma as m 2 | import mantle 3 | import fault 4 | import math 5 | from math import log2 6 | from mantle import Register, CounterModM, DFF 7 | 8 | 9 | def DefineAggregator(word_width, mem_word_width): 10 | class Aggregator(m.Circuit): 11 | name = 'Aggregator_' + str(word_width) + '_' + str(mem_word_width) 12 | IO = ['in_pixels', m.In(m.Bits[word_width]), 13 | 'valid_in', m.In(m.Bit), 14 | 'agg_out', m.Out(m.Array[mem_word_width, m.Bits[word_width]]), 15 | 'valid_out', m.Out(m.Bit), 16 | 'next_full', m.Out(m.Bit)] + m.ClockInterface() 17 | 18 | @m.circuit.combinational 19 | def check_valid_in(valid_in: m.Bit, check: m.Bit) -> m.Bit: 20 | return valid_in & check 21 | 22 | @classmethod 23 | def definition(agg): 24 | # stores input pixels from each cycle 25 | regs = [Register(word_width) for i in range(mem_word_width)] 26 | regs[0].I <= agg.in_pixels 27 | mem_word_width_bits = int(log2(mem_word_width)) 28 | if mem_word_width == 1: 29 | agg.valid_out <= 1 30 | m.wire(agg.next_full, agg.valid_in) 31 | else: 32 | # keep track of number of input pixels so far 33 | counter = CounterModM(mem_word_width, mem_word_width_bits) 34 | # output VALID on same clock when data is in AGGREGATED_OUTPUT 35 | valid_dff = DFF() 36 | # valid when number of input pixels is same as mem_word_width 37 | valid_dff.I <= (counter.O == mem_word_width - 2) 38 | m.wire(agg.valid_out, agg.check_valid_in(agg.valid_in, valid_dff.O)) 39 | # agg.valid_out = agg.check_valid_in(agg.valid_in, valid_dff.O) 40 | m.wire(agg.next_full, agg.check_valid_in(agg.valid_in, (counter.O == mem_word_width - 2))) 41 | # agg.next_full = agg.check_valid_in(agg.valid_in, (counter.O == mem_word_width - 2)) 42 | # m.wire(agg.next_full, agg.valid_in) 43 | # output all mem_word_width in_pixels so far 44 | a = m.scan(regs, scanargs={'I': 'O'}) 45 | agg.agg_out <= a.O 46 | 47 | return Aggregator 48 | -------------------------------------------------------------------------------- /tests/wrapper_tb.sv: -------------------------------------------------------------------------------- 1 | `define CLK_PERIOD 1.1 2 | `define CLK_PERIOD 1.1 3 | `define ASSIGNMENT_DELAY 0.22000000000000003 4 | `define CONFIG_TIME 4096 5 | `define RUN_TIME 406900 6 | 7 | module TB; 8 | 9 | reg [0:0] clk; 10 | reg [15:0] data_in_0; 11 | reg [0:0] rst_n; 12 | reg [15:0] data_out_0; 13 | reg [15:0] data_out_1; 14 | reg [0:0] empty; 15 | reg [0:0] full; 16 | reg [1:0] read_out; 17 | reg [1:0] valid_out; 18 | reg [31:0] config0; 19 | reg [31:0] config1; 20 | reg [2:0] test; 21 | // reg [0:0] stencil_valid; 22 | 23 | LakeWrapper DUT ( 24 | .addr_in_0(0), 25 | .addr_in_1(0), 26 | .chain_data_in_0(0), 27 | .chain_data_in_1(0), 28 | .clk(clk), 29 | .clk_en(1), 30 | .config_addr_in(0), 31 | .config_data_in(0), 32 | .config_en(0), 33 | .config_read(0), 34 | .config_write(0), 35 | .data_in_0(data_in_0), 36 | .data_in_1(0), 37 | .fifo_ctrl_fifo_depth(0), 38 | .flush(0), 39 | .ren_in(0), 40 | .rst_n(rst_n), 41 | .wen_in(0), 42 | .config_data_out_0(config0), 43 | .config_data_out_1(config1), 44 | .data_out_0(data_out_0), 45 | .data_out_1(data_out_1), 46 | .empty(empty), 47 | .full(full), 48 | .sram_ready_out(read_out), 49 | // .stencil_valid(stencil_valid), 50 | .valid_out(valid_out) 51 | ); 52 | 53 | always #(`CLK_PERIOD/2) clk =~clk; 54 | 55 | always @ (posedge clk) begin 56 | if (test < 2) begin 57 | test += 1; 58 | rst_n = 0; 59 | data_in_0 = 0; 60 | end else begin 61 | test = 2; 62 | rst_n = 1; 63 | data_in_0 = data_in_0 + 1; 64 | end 65 | end 66 | 67 | initial begin 68 | clk <= 0; 69 | data_in_0 = 0; 70 | test = 0; 71 | rst_n <= 0; 72 | end 73 | 74 | initial begin 75 | $vcdplusfile("dump.vpd"); 76 | $vcdplusmemon(); 77 | $vcdpluson(0, TB); 78 | $set_toggle_region(TB); 79 | #(`CONFIG_TIME); 80 | $toggle_start(); 81 | #(`RUN_TIME); 82 | $toggle_stop(); 83 | $toggle_report("outputs/run.saif", 1e-9, TB); 84 | $finish(2); 85 | end 86 | 87 | endmodule 88 | -------------------------------------------------------------------------------- /tests/test_agg_magma.py: -------------------------------------------------------------------------------- 1 | import fault 2 | import magma as m 3 | import fault 4 | import tempfile 5 | from math import ceil, log 6 | import pytest 7 | 8 | 9 | def DefineAggregator(word_width, mem_word_width): 10 | from mantle import Register, CounterModM, DFF 11 | 12 | class _Aggregator(m.Circuit): 13 | name = 'Aggregator_' + str(word_width) + '_' + str(mem_word_width) 14 | IO = ['INPUT_PIXELS', m.In(m.Bits[word_width]), 15 | 'AGGREGATED_OUTPUT', m.Out(m.Array[mem_word_width, m.Bits[word_width]]), 16 | 'VALID', m.Out(m.Bit)] + m.ClockInterface() 17 | 18 | @classmethod 19 | def definition(agg): 20 | # stores input pixels from each cycle 21 | regs = [Register(word_width) for i in range(mem_word_width)] 22 | regs[0].I <= agg.INPUT_PIXELS 23 | if mem_word_width == 1: 24 | agg.VALID <= 1 25 | else: 26 | # keep track of number of input pixels so far 27 | counter = CounterModM(mem_word_width, ceil(log(mem_word_width, 2))) 28 | # output VALID on same clock when data is in AGGREGATED_OUTPUT 29 | valid_dff = DFF() 30 | # valid when number of input pixels is same as memory_width 31 | valid_dff.I <= (counter.O == mem_word_width - 1) 32 | agg.VALID <= valid_dff.O 33 | # output all memory_width INPUT_PIXELS so far 34 | a = m.scan(regs, scanargs={'I': 'O'}) 35 | agg.AGGREGATED_OUTPUT <= a.O 36 | 37 | return _Aggregator 38 | 39 | 40 | def Aggregator(word_width, mem_word_width): 41 | return DefineAggregator(word_width, mem_word_width) 42 | 43 | 44 | @pytest.mark.skip 45 | def test_agg_magma(word_width=16, mem_word_width=4): 46 | 47 | dut = Aggregator(word_width=word_width, mem_word_width=mem_word_width) 48 | tester = fault.Tester(dut, dut.CLK) 49 | tester.circuit.CLK = 0 50 | tester.circuit.rst_n = 0 51 | tester.step(2) 52 | tester.circuit.rst_n = 1 53 | tester.step(2) 54 | 55 | for i in range(15): 56 | tester.circuit.INPUT_PIXELS = i % 5 57 | tester.eval() 58 | tester.step(2) 59 | 60 | with tempfile.TemporaryDirectory() as tempdir: 61 | tester.compile_and_run(target="verilator", 62 | directory=tempdir, 63 | flags=["-Wno-fatal"]) 64 | -------------------------------------------------------------------------------- /lake/modules/agg_aligner.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from lake.utils.util import increment, decrement 3 | from lake.attributes.config_reg_attr import ConfigRegAttr 4 | 5 | 6 | class AggAligner(Generator): 7 | ''' 8 | This module will help with the word alignment of the aggregator. This module 9 | will keep a running count of how many aggregators have been filled based 10 | on the line length (or some other notion of termination) 11 | ''' 12 | def __init__(self, 13 | data_width, 14 | max_line_length): 15 | super().__init__("agg_aligner") 16 | 17 | # Capture to the object 18 | self.data_width = data_width 19 | self.max_line_length = max_line_length 20 | self.counter_width = clog2(self.max_line_length) 21 | 22 | # Clock and Reset 23 | self._clk = self.clock("clk") 24 | self._rst_n = self.reset("rst_n") 25 | 26 | # Inputs 27 | self._in_dat = self.input("in_dat", self.data_width) 28 | self._in_valid = self.input("in_valid", 1) 29 | 30 | self._line_length = self.input("line_length", self.counter_width) 31 | self._line_length.add_attribute(ConfigRegAttr("Line Length/Image Width for alignment")) 32 | 33 | # Outputs 34 | self._out_dat = self.output("out_dat", self.data_width) 35 | self._out_valid = self.output("out_valid", 1) 36 | self._out_align = self.output("align", 1) 37 | 38 | # Local Signals 39 | self._cnt = self.var("cnt", self.counter_width) 40 | 41 | # Generate 42 | self.add_code(self.update_cnt) 43 | self.add_code(self.set_align) 44 | 45 | self.wire(self._out_dat, self._in_dat) 46 | self.wire(self._out_valid, self._in_valid) 47 | 48 | @always_ff((posedge, "clk"), (negedge, "rst_n")) 49 | def update_cnt(self): 50 | if ~self._rst_n: 51 | self._cnt = 0 52 | elif (self._in_valid): 53 | if (self._cnt == decrement(self._line_length, 1)): 54 | self._cnt = 0 55 | else: 56 | self._cnt = increment(self._cnt, 1) 57 | 58 | @always_comb 59 | def set_align(self): 60 | self._out_align = self._in_valid & (self._cnt == decrement(self._line_length, 1)) 61 | 62 | 63 | if __name__ == "__main__": 64 | align_dut = AggAligner(data_width=16, 65 | max_line_length=64) 66 | verilog(align_dut, filename="agg_aligner.sv") 67 | -------------------------------------------------------------------------------- /lake/spec/test_DB.py: -------------------------------------------------------------------------------- 1 | from ir import * 2 | 3 | 4 | def test_DB(): 5 | ''' 6 | Conv 33 linebuffer on 6x6 image 7 | schedule: {input[i_wr, j_wr]->[i_wr]: 0<=i_wr<=5, j_wr=0} and 8 | {input[i_wr, j_wr] -> [i_wr + j_wr * 12 - 6] : 0<=i_wr<= 5, 1<=j_wr<=5} 9 | schedule: {output[i_rd, j_rd]->[i_rd + 12*j_rd + 6]: 0<=i_wr<=11, 0<=j_wr<=5} 10 | ''' 11 | 12 | i = var("i_wr", 0, 5) 13 | j = var("j_wr", 0, 5) 14 | 15 | # accessor for input 16 | acc_in = expr([i, j], [([bound("i_wo", 0, 5), bound("j_wo", 0, 0)], [1, 0, 0]), 17 | ([bound("i_ss", 0, 5), bound("j_ss", 1, 5)], [1, 12, -6])]) 18 | acc_ctr_in = map([i, j], [acc_in]) 19 | 20 | i_addr = var("i_in", 0, 5) 21 | j_addr = var("j_in", 0, 5) 22 | # address generator for input 23 | addr_in = expr([i_addr, j_addr], [([bound("bd_i", 0, 5), bound("bd_j", 0, 5)], [1, 6, 0])]) 24 | addr_gen_in = map([i_addr, j_addr], [addr_in]) 25 | 26 | ir = var("i_rd", 0, 11) 27 | jr = var("j_rd", 0, 5) 28 | 29 | acc_out = expr([ir, jr], [([bound("ir_ss", 0, 11), bound("jr_ss", 0, 5)], [1, 12, 6])]) 30 | acc_ctr_out = map([ir, jr], [acc_out]) 31 | 32 | ir_addr = var("i_out", 0, 5) 33 | kr_addr = var("k_out", 0, 1) 34 | jr_addr = var("j_out", 0, 5) 35 | addr_out = expr([ir_addr, kr_addr, jr_addr], 36 | [([bound("bd_i", 0, 5), bound("bd_k", 0, 1), bound("bd_j", 0, 5)], [1, 0, 6, 0])]) 37 | addr_gen_out = map([ir_addr, kr_addr, jr_addr], [addr_out]) 38 | 39 | # test stream 40 | cycle = 78 41 | for itr in range(cycle): 42 | sched_rd = acc_ctr_out.eval() 43 | sched_wr = acc_ctr_in.eval() 44 | is_rd = (sched_rd == itr) 45 | is_wr = (sched_wr == itr) 46 | wr_idx = acc_ctr_in.getDomain() 47 | rd_idx = acc_ctr_out.getDomain() 48 | # print ("cycle:", itr, " write idx: ", wr_idx, "sched_wr:", sched_wr) 49 | if is_wr: 50 | print("cycle:", itr, " write ", wr_idx) 51 | print("write addr: ", addr_gen_in.eval()) 52 | acc_ctr_in.update() 53 | addr_gen_in.update() 54 | if is_rd: 55 | print("cycle:", itr, " read ", rd_idx) 56 | print("read addr: ", addr_gen_out.eval()) 57 | acc_ctr_out.update() 58 | addr_gen_out.update() 59 | print("\n***************************************\n") 60 | 61 | 62 | if __name__ == "__main__": 63 | test_DB() 64 | -------------------------------------------------------------------------------- /lake/modules/two_port_sram_stub.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from math import log 3 | 4 | 5 | class TwoPortSRAMStub(Generator): 6 | ''' 7 | 2 port SRAM 8 | ''' 9 | ########################## 10 | # Generation # 11 | ########################## 12 | def __init__(self, width, depth, bypass): 13 | super().__init__("two_port_sram_stub", True) 14 | 15 | ############################ 16 | # Clock and Reset # 17 | ############################ 18 | self.i_clk = self.clock("i_clk") 19 | self.i_rst_n = self.reset("i_rst_n", 1) 20 | 21 | ############################ 22 | # Inputs # 23 | ############################ 24 | self.i_wen = self.input("i_wen", 1) 25 | self.i_ren = self.input("i_ren", 1) 26 | 27 | self.i_wr_addr = self.input("i_wr_addr", clog2(depth)) 28 | self.i_rd_addr = self.input("i_rd_addr", clog2(depth)) 29 | 30 | self.i_data = self.input("i_data", width) 31 | 32 | ############################ 33 | # Outputs # 34 | ############################ 35 | self.o_data = self.output("o_data", width) 36 | 37 | ############################ 38 | # Local Variables # 39 | ############################ 40 | self.data_array = self.var("data_array", width=width, size=depth, packed=True) 41 | 42 | self.bypass = bypass 43 | 44 | ############################ 45 | # Add seq blocks # 46 | ############################ 47 | self.add_code(self.seq_data_access) 48 | self.add_code(self.seq_data_out) 49 | 50 | ########################## 51 | # Access sram array # 52 | ########################## 53 | @always((posedge, "i_clk")) 54 | def seq_data_access(self): 55 | if (self.i_wen): 56 | self.data_array[self.i_wr_addr] = self.i_data 57 | 58 | @always((posedge, "i_clk"), (negedge, "i_rst_n")) 59 | def seq_data_out(self): 60 | if ~self.i_rst_n: 61 | self.o_data = 0 62 | elif self.i_ren: 63 | if (self.i_rd_addr == self.i_wr_addr) & (self.bypass == 1): 64 | self.o_data = self.i_data 65 | else: 66 | self.o_data = self.data_array[self.i_rd_addr] 67 | 68 | 69 | if __name__ == "__main__": 70 | dut = TwoPortSRAMStub(16, 1024, 0) 71 | verilog(dut, filename="two_port_sram_stub.sv", check_active_high=False) 72 | -------------------------------------------------------------------------------- /lake/dsl/edge.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from lake.modules.for_loop import ForLoop 3 | from lake.utils.util import safe_wire 4 | from lake.modules.addr_gen import AddrGen 5 | from lake.dsl.helper import * 6 | 7 | 8 | def get_full_edge_params(edge_params): 9 | if "dim" not in edge_params: 10 | edge_params["dim"] = 6 11 | if "max_range" not in edge_params: 12 | edge_params["max_range"] = 65535 13 | if "max_stride" not in edge_params: 14 | edge_params["max_stride"] = 65535 15 | 16 | 17 | def edge_inst(edge_params, from_mem, to_mem, from_inst, to_inst, edge_collateral): 18 | 19 | edge = Edge(edge_params, from_mem, to_mem, from_inst, to_inst) 20 | get_params(edge, edge_collateral, "edge") 21 | 22 | return edge 23 | 24 | 25 | class Edge(Generator): 26 | def __init__(self, 27 | edge_params, 28 | from_mem, 29 | to_mem, 30 | from_inst, 31 | to_inst): 32 | 33 | super().__init__(f"lake_edge", debug=True) 34 | 35 | # PARAMETERS 36 | # data_out 37 | self.from_signal = edge_params["from_signal"] 38 | # data_in 39 | self.to_signal = edge_params["to_signal"] 40 | self.dim = edge_params["dim"] 41 | self.max_range = edge_params["max_range"] 42 | self.max_stride = edge_params["max_stride"] 43 | 44 | self._write(f"write_{self.to_signal}", 45 | width=1) 46 | 47 | forloop = ForLoop(iterator_support=self.dim, 48 | config_width=clog2(self.max_range)) 49 | 50 | # get memory params from top Lake or make a wrapper func for user 51 | # with just these params and then pass in mem for this signal 52 | # self._write_addr(f"write_addr_{to_signal}") 53 | 54 | self.add_child(f"loops_{self.from_signal}_{self.to_signal}", 55 | forloop, 56 | clk=self._clk, 57 | rst_n=self._rst_n, 58 | step=self._write) 59 | 60 | AG_write = AddrGen(iterator_support=addr_gen_dim, 61 | config_width=clog2(addr_gen_max_range)) 62 | 63 | self.add_child(f"AG_write_{self.from_signal}_{self.to_signal}", 64 | AG_write, 65 | clk=self._clk, 66 | rst_n=self._rst_n, 67 | step=self._write, 68 | mux_sel=forloop.ports.mux_sel_out) 69 | 70 | safe_wire(self, AG_write.ports.addr_out, self._write_addr) 71 | -------------------------------------------------------------------------------- /lake/dsl/helper.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | import json 3 | 4 | 5 | def make_params(name, 6 | capacity, 7 | read_port_width=0, 8 | write_port_width=0, 9 | read_write_port_width=0, 10 | num_chain=1, 11 | use_macro=False, 12 | macro_name="SRAM_default_name", 13 | rw_same_cycle=False): 14 | 15 | assert num_chain >= 1, "Can chain 1 or more Lake objects" 16 | 17 | params_dict = {"name": name, 18 | "capacity": capacity, 19 | "use_macro": use_macro, 20 | "rw_same_cycle": rw_same_cycle, 21 | "macro_name": macro_name, 22 | "chaining": not (num_chain == 1), 23 | "num_chain": num_chain} 24 | 25 | if read_port_width != 0: 26 | params_dict["read_port_width"] = read_port_width 27 | if write_port_width != 0: 28 | params_dict["write_port_width"] = write_port_width 29 | if read_write_port_width != 0: 30 | params_dict["read_write_port_width"] = read_write_port_width 31 | 32 | return params_dict 33 | 34 | 35 | def get_params(gen, collateral, name_id): 36 | orig_gen = Generator("original") 37 | gen_dict = vars(gen).copy() 38 | 39 | gen_dict = dict((key, value) for key, value in gen.__dict__.items() 40 | if not callable(value) and not key.startswith('__')) 41 | 42 | for key in vars(orig_gen): 43 | if key in gen_dict: 44 | del gen_dict[key] 45 | 46 | if name_id + "_name" not in gen_dict: 47 | idx = len(collateral) 48 | collateral[name_id + f"_{idx}"] = gen_dict 49 | else: 50 | collateral[gen_dict[name_id + "_name"]] = gen_dict 51 | 52 | 53 | def get_json(mem_collateral, 54 | edge_collateral, 55 | input_edge_collateral, 56 | output_edge_collateral, 57 | filename="collateral2compiler.json"): 58 | all_collateral = {} 59 | all_collateral["memories"] = mem_collateral 60 | all_collateral["edges"] = edge_collateral 61 | all_collateral["input_edges"] = input_edge_collateral 62 | all_collateral["output_edges"] = output_edge_collateral 63 | 64 | with open (filename, 'w') as outfile: 65 | json.dump(all_collateral, outfile, indent=4) 66 | 67 | 68 | def get_edge_name(edge): 69 | # get unique edge_name identifier for hardware modules 70 | from_sigs, to_sigs = "", "" 71 | for e in edge["from_signal"]: 72 | from_sigs += e + "_" 73 | for e in edge["to_signal"]: 74 | to_sigs += e + "_" 75 | 76 | return from_sigs + to_sigs + "edge" 77 | -------------------------------------------------------------------------------- /lake/modules/sram_stub_generator.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from math import log 3 | from lake.attributes.sram_port_attr import SRAMPortAttr 4 | 5 | 6 | class SRAMStubGenerator(Generator): 7 | 8 | ########################## 9 | # Generation # 10 | ########################## 11 | def __init__(self, 12 | sram_name, 13 | data_width, 14 | width_mult, 15 | depth): 16 | super().__init__(sram_name) 17 | 18 | # for provided external sram macro 19 | self.external = True 20 | 21 | self.data_width = data_width 22 | self.width_mult = width_mult 23 | self.depth = depth 24 | 25 | ############################ 26 | # Clock and Reset # 27 | ############################ 28 | self._clk = self.clock("sram_clk") 29 | self._clk.add_attribute(SRAMPortAttr("sram clk")) 30 | 31 | ############################ 32 | # Inputs # 33 | ############################ 34 | 35 | # attribute indicates that all these ports will be renamed to match 36 | # the port names for external sram macro 37 | self._wen = self.input("sram_wen", 1) 38 | self._wen.add_attribute(SRAMPortAttr("sram wen")) 39 | 40 | self._cen = self.input("sram_cen", 1) 41 | self._cen.add_attribute(SRAMPortAttr("sram cen")) 42 | 43 | self._addr = self.input("sram_addr", clog2(self.depth)) 44 | self._addr.add_attribute(SRAMPortAttr("sram addr")) 45 | 46 | self._data_in = self.input("sram_data_in", 47 | self.data_width * self.width_mult) 48 | self._data_in.add_attribute(SRAMPortAttr("sram data in")) 49 | 50 | ############################ 51 | # Outputs # 52 | ############################ 53 | self._data_out = self.output("sram_data_out", 54 | self.data_width * self.width_mult) 55 | self._data_out.add_attribute(SRAMPortAttr("sram data out")) 56 | 57 | self._wtsel = self.input("sram_wtsel", 2) 58 | self._wtsel.add_attribute(SRAMPortAttr("sram wtsel")) 59 | 60 | self._rtsel = self.input("sram_rtsel", 2) 61 | self._rtsel.add_attribute(SRAMPortAttr("sram rtsel")) 62 | 63 | 64 | if __name__ == "__main__": 65 | # to see interface, mark self.use_stub = True and self.external = False 66 | dut = SRAMStubGenerator(sram_name="TSMC", 67 | data_width=16, 68 | width_mult=1, 69 | depth=124) 70 | verilog(dut, filename="tsmc_macro.sv") 71 | -------------------------------------------------------------------------------- /tests/test_addr_gen.py: -------------------------------------------------------------------------------- 1 | from lake.models.addr_gen_model import AddrGenModel 2 | from lake.modules.addr_gen import AddrGen 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | from kratos import * 8 | import kratos as k 9 | import pytest 10 | 11 | 12 | @pytest.mark.skip 13 | def test_addr_gen_basic(depth=512, 14 | addr_width=16, 15 | iterator_support=6): 16 | model_ag = AddrGenModel(iterator_support=iterator_support, 17 | address_width=addr_width) 18 | 19 | config_dict = {} 20 | config_dict["starting_addr"] = 0 21 | config_dict["dimensionality"] = 3 22 | config_dict["strides_0"] = 1 23 | config_dict["strides_1"] = 3 24 | config_dict["strides_2"] = 9 25 | config_dict["ranges_0"] = 3 26 | config_dict["ranges_1"] = 3 27 | config_dict["ranges_2"] = 3 28 | 29 | model_ag.set_config(config_dict) 30 | 31 | word_width = 1 32 | fetch_width = 4 33 | stencil_height = 3 34 | max_range_value = 5 35 | img_height = 4 36 | dut = AddrGen(iterator_support=iterator_support, 37 | address_width=addr_width) 38 | 39 | magma_dut = k.util.to_magma(dut, flatten_array=True, 40 | check_flip_flop_always_ff=False) 41 | tester = fault.Tester(magma_dut, magma_dut.clk) 42 | 43 | tester.circuit.dimensionality = 4 44 | tester.circuit.starting_addr = 0 45 | # tester.circuit.strides_0 = 1 46 | # tester.circuit.strides_1 = 3 47 | # tester.circuit.strides_2 = 9 48 | # tester.circuit.ranges_0 = 3 49 | # tester.circuit.ranges_1 = 3 50 | # tester.circuit.ranges_2 = 3 51 | tester.circuit.strides_0 = 1 52 | tester.circuit.strides_1 = 1 53 | tester.circuit.strides_2 = 1 54 | tester.circuit.strides_3 = -26 55 | tester.circuit.ranges_0 = 1 56 | tester.circuit.ranges_1 = 1 57 | tester.circuit.ranges_2 = 1 58 | tester.circuit.ranges_3 = 10000 59 | 60 | tester.circuit.clk = 0 61 | tester.circuit.clk_en = 1 62 | tester.circuit.rst_n = 0 63 | tester.eval() 64 | tester.step(2) 65 | tester.circuit.rst_n = 1 66 | tester.eval() 67 | tester.step(2) 68 | 69 | for i in range(1000): 70 | tester.circuit.step = 1 71 | tester.circuit.addr_out.expect(model_ag.get_address()) 72 | model_ag.step() 73 | tester.eval() 74 | tester.step(2) 75 | 76 | with tempfile.TemporaryDirectory() as tempdir: 77 | tester.compile_and_run(target="verilator", 78 | directory=tempdir, 79 | magma_output="verilog", 80 | flags=["-Wno-fatal"]) 81 | 82 | 83 | if __name__ == "__main__": 84 | test_addr_gen_basic() 85 | -------------------------------------------------------------------------------- /lake/modules/chain_accessor.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from lake.attributes.config_reg_attr import ConfigRegAttr 3 | from lake.attributes.formal_attr import FormalAttr, FormalSignalConstraint 4 | 5 | import kratos as kts 6 | 7 | 8 | class ChainAccessor(Generator): 9 | def __init__(self, 10 | data_width, 11 | interconnect_output_ports): 12 | super().__init__(f"Chain_{interconnect_output_ports}_{data_width}", debug=True) 13 | 14 | # generator parameters 15 | self.data_width = data_width 16 | self.interconnect_output_ports = interconnect_output_ports 17 | 18 | # chain enable configuration register 19 | self._chain_en = self.input("chain_en", 1) 20 | self._chain_en.add_attribute(ConfigRegAttr("Signal indicating whether to enable chaining")) 21 | self._chain_en.add_attribute(FormalAttr(self._chain_en.name, FormalSignalConstraint.SET0)) 22 | 23 | # inputs 24 | self._curr_tile_data_out = self.input("curr_tile_data_out", 25 | self.data_width, 26 | size=self.interconnect_output_ports, 27 | packed=True, 28 | explicit_array=True) 29 | 30 | self._chain_data_in = self.input("chain_data_in", 31 | self.data_width, 32 | size=self.interconnect_output_ports, 33 | packed=True, 34 | explicit_array=True) 35 | 36 | self._accessor_output = self.input("accessor_output", 37 | self.interconnect_output_ports) 38 | 39 | self._data_out_tile = self.output("data_out_tile", 40 | self.data_width, 41 | size=self.interconnect_output_ports, 42 | packed=True, 43 | explicit_array=True) 44 | 45 | self.add_code(self.set_data_out) 46 | 47 | @always_comb 48 | def set_data_out(self): 49 | for i in range(self.interconnect_output_ports): 50 | if self._accessor_output[i]: 51 | self._data_out_tile[i] = self._curr_tile_data_out[i] 52 | else: 53 | if self._chain_en: 54 | self._data_out_tile[i] = self._chain_data_in[i] 55 | else: 56 | self._data_out_tile[i] = 0 57 | 58 | 59 | if __name__ == "__main__": 60 | dut = ChainAccessor(data_width=16, 61 | interconnect_output_ports=2) 62 | verilog(dut, filename="chain_acc.sv") 63 | -------------------------------------------------------------------------------- /lake/models/chain_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | import kratos as kts 3 | 4 | 5 | # chain model 6 | class ChainModel(Model): 7 | 8 | def __init__(self, 9 | data_width, 10 | interconnect_output_ports, 11 | chain_idx_bits, 12 | enable_chain_output, 13 | chain_idx_output): 14 | 15 | # generation parameters 16 | self.data_width = data_width 17 | self.interconnect_output_ports = interconnect_output_ports 18 | self.chain_idx_bits = chain_idx_bits 19 | 20 | # configuration registers passed down from top level 21 | self.enable_chain_output = enable_chain_output 22 | self.chain_idx_output = chain_idx_output 23 | 24 | def set_config(self, new_config): 25 | for key, config_val in new_config.items(): 26 | if key not in self.config: 27 | AssertionError("Gave bad config...") 28 | else: 29 | self.config[key] = config_val 30 | 31 | def interact(self, curr_tile_valid_out, curr_tile_data_out, chain_valid_in, chain_data_in): 32 | 33 | chain_data_out_inter = [] 34 | chain_valid_out_inter = [] 35 | for i in range(self.interconnect_output_ports): 36 | if chain_valid_in[i] == 0: 37 | chain_data_out_inter.append(curr_tile_data_out[i]) 38 | chain_valid_out_inter.append(curr_tile_valid_out[i]) 39 | else: 40 | chain_data_out_inter.append(chain_data_in[i]) 41 | chain_valid_out_inter.append(chain_valid_in[i]) 42 | 43 | # all combinational outputs 44 | # set data_out_tile 45 | if self.enable_chain_output: 46 | data_out_tile = chain_data_out_inter 47 | else: 48 | data_out_tile = curr_tile_data_out 49 | 50 | # set valid_out_tile 51 | valid_out_tile = [] 52 | if self.enable_chain_output: 53 | if not (self.chain_idx_output == 0): 54 | for i in range(self.interconnect_output_ports): 55 | valid_out_tile.append(0) 56 | else: 57 | valid_out_tile = chain_valid_out_inter 58 | else: 59 | valid_out_tile = curr_tile_valid_out 60 | 61 | # set chain_data_out 62 | chain_data_out = chain_data_out_inter 63 | 64 | # set chain_valid_out 65 | chain_valid_out = [] 66 | if (self.chain_idx_output == 0) or \ 67 | (not self.enable_chain_output): 68 | for i in range(self.interconnect_output_ports): 69 | chain_valid_out.append(0) 70 | else: 71 | chain_valid_out = chain_valid_out_inter 72 | 73 | return chain_data_out, chain_valid_out, data_out_tile, valid_out_tile 74 | -------------------------------------------------------------------------------- /lake/modules/linebuffer_control.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | 3 | 4 | class LineBufferControl(Generator): 5 | def __init__(self): 6 | super().__init__("linebuffer_control", True) 7 | 8 | # PORT DEFS: begin 9 | self._clk = self.clock("clk", 1) 10 | self._clk_en = self.input("clk_en", 1) 11 | self._reset = self.reset("reset") 12 | self._flush = self.input("flush", 1) 13 | 14 | self._wen = self.input("wen", 1) 15 | self._depth = self.input("depth", 16) 16 | self._stencil_width = self.input("stencil_width", 16) 17 | self._num_words_mem = self.input("num_words_mem", 16) 18 | 19 | self._valid = self.output("valid", 1) 20 | self._ren_to_fifo = self.output("ren_to_fifo", 1) 21 | # PORT DEFS: end 22 | 23 | # LOCAL SIGNALS: begin 24 | self._vg_ctr = self.var("vg_ctr", 16) 25 | self._valid_gate = self.var("valid_gate", 1) 26 | self._valid_int = self.var("valid_int", 1) 27 | self._threshold = self.var("threshold", 1) 28 | # LOCAL SIGNALS: end 29 | 30 | # GENERATION LOGIC: begin 31 | self.wire(self._valid_gate, 32 | ternary((self._stencil_width == const(0, 16)), 33 | const(1, 1), 34 | (self._vg_ctr > (self._stencil_width - const(1, 16))))) 35 | self.wire(self._valid_int, 36 | (self._num_words_mem >= (self._depth - 1)) & 37 | self._wen & self._threshold & (self._depth > 0)) 38 | self.wire(self._valid, self._valid_gate & self._valid_int) 39 | self.wire(self._ren_to_fifo, (self._num_words_mem >= (self._depth - 1)) & 40 | self._wen & (self._depth > 0)) 41 | 42 | self.add_code(self.threshold_update) 43 | self.add_code(self.vg_ctr_update) 44 | # GENERATION LOGIC: end 45 | 46 | @always((posedge, "clk"), (posedge, "reset")) 47 | def threshold_update(self): 48 | if (self._reset): 49 | self._threshold = 0 50 | elif self._clk_en: 51 | if self._flush: 52 | self._threshold = 0 53 | elif (self._num_words_mem == (self._depth - 1)) & self._wen: 54 | self._threshold = 1 55 | 56 | @always((posedge, "clk"), (posedge, "reset")) 57 | def vg_ctr_update(self): 58 | if (self._reset): 59 | self._vg_ctr = 0 60 | elif self._clk_en: 61 | if self._flush: 62 | self._vg_ctr = 0 63 | elif self._valid_int: 64 | if self._vg_ctr == (self._depth - 1): 65 | self._vg_ctr = 0 66 | else: 67 | self._vg_ctr = self._vg_ctr + 1 68 | 69 | 70 | if __name__ == "__main__": 71 | dut = LineBufferControl() 72 | verilog(dut, filename="linebuffer_control.sv", check_active_high=False) 73 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/tile_read.sv: -------------------------------------------------------------------------------- 1 | module tile_read #( 2 | parameter NUM_BLOCKS = 1, 3 | parameter FILE_NAME = "dst.txt", 4 | parameter LOCATION = "X00_Y00", 5 | parameter TX_NUM = 1, 6 | parameter RAN_SHITF = 0 7 | ) 8 | ( 9 | input logic clk, 10 | input logic rst_n, 11 | input logic [16:0] data, 12 | output logic ready, 13 | input logic valid, 14 | output logic done, 15 | input logic flush 16 | ); 17 | 18 | logic [16:0] local_mem_0 [0:2047]; 19 | integer num_rx; 20 | integer size_0; 21 | 22 | string F1_PARGS; 23 | string NUM_BLOCKS_PARGS; 24 | integer NUM_BLOCKS_USE; 25 | string F1_USE; 26 | string ENABLED_PARGS; 27 | integer ENABLED; 28 | integer DONE_TOKEN; 29 | integer done_count; 30 | integer delay_count; 31 | integer ADD_DELAY; 32 | integer mask; 33 | 34 | initial begin 35 | 36 | ENABLED = 1; 37 | // ENABLED_PARGS = $sformatf("%s_ENABLED=%%d", LOCATION); 38 | // $value$plusargs(ENABLED_PARGS, ENABLED); 39 | 40 | num_rx = 0; 41 | ready = 0; 42 | size_0 = 0; 43 | done = 0; 44 | DONE_TOKEN = 17'h10100; 45 | done_count = TX_NUM; 46 | ADD_DELAY = 0; 47 | mask = 32'd3 << RAN_SHITF; 48 | 49 | if (ENABLED == 1) begin 50 | 51 | $display("%s is enabled...", LOCATION); 52 | 53 | F1_PARGS = $sformatf("%s_F1=%%s", LOCATION); 54 | NUM_BLOCKS_PARGS = $sformatf("%s_NUM_BLOCKS=%%d", LOCATION); 55 | 56 | $display("Location: %s", LOCATION); 57 | F1_USE = FILE_NAME; 58 | $display("F1_USE before: %s", F1_USE); 59 | $value$plusargs(F1_PARGS, F1_USE); 60 | $display("F1_USE after: %s", F1_USE); 61 | 62 | @(posedge flush); 63 | @(negedge flush); 64 | 65 | @(posedge clk); 66 | @(posedge clk); 67 | @(posedge clk); 68 | 69 | while(done_count > 0) begin 70 | @(posedge clk); 71 | #1; 72 | // ready = $urandom(); 73 | 74 | ready = 0; 75 | delay_count = $urandom & mask; 76 | while (delay_count > 0 & ADD_DELAY) begin 77 | @(posedge clk); 78 | #1; 79 | delay_count--; 80 | end 81 | 82 | ready = 1; 83 | if(ready == 1 && valid == 1) begin 84 | local_mem_0[num_rx] = data; 85 | if (data == DONE_TOKEN) begin 86 | done_count--; 87 | end 88 | num_rx = num_rx + 1; 89 | end 90 | end 91 | @(posedge clk); 92 | #1; 93 | assert(valid == 0) else $error("Valid signal fails to end"); 94 | ready = 0; 95 | $writememh(F1_USE, local_mem_0); 96 | 97 | end 98 | 99 | @(posedge clk); 100 | ready = 0; 101 | done = 1; 102 | 103 | end 104 | 105 | endmodule 106 | -------------------------------------------------------------------------------- /tests/test_sram.py: -------------------------------------------------------------------------------- 1 | from lake.models.sram_model import SRAMModel 2 | from lake.modules.sram_stub import SRAMStub 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | import kratos as kts 8 | import random as rand 9 | import pytest 10 | 11 | 12 | # this test tests sram_stub as well as part of sram_wrapper in the kratos code 13 | @pytest.mark.parametrize("data_width", [16, 32]) 14 | @pytest.mark.parametrize("depth", [512, 1024]) 15 | @pytest.mark.parametrize("width_mult", [1, 2]) 16 | def test_sram_basic(data_width, 17 | depth, 18 | width_mult, 19 | num_tiles=1): 20 | 21 | # Set up model... 22 | model_sram = SRAMModel(data_width=data_width, 23 | width_mult=width_mult, 24 | depth=depth, 25 | num_tiles=num_tiles) 26 | new_config = {} 27 | model_sram.set_config(new_config=new_config) 28 | ### 29 | 30 | # Set up dut... 31 | dut = SRAMStub(data_width=data_width, 32 | width_mult=width_mult, 33 | depth=depth) 34 | 35 | magma_dut = kts.util.to_magma(dut, flatten_array=True, check_flip_flop_always_ff=False) 36 | tester = fault.Tester(magma_dut, magma_dut.clk) 37 | ### 38 | 39 | for key, value in new_config.items(): 40 | setattr(tester.circuit, key, value) 41 | 42 | rand.seed(0) 43 | 44 | data = [0 for i in range(width_mult)] 45 | 46 | for z in range(1000): 47 | # Generate new input 48 | wen = rand.randint(0, 1) 49 | cen = rand.randint(0, 1) 50 | addr = rand.randint(0, depth - 1) 51 | for i in range(width_mult): 52 | data[i] = rand.randint(0, 2 ** data_width - 1) 53 | 54 | tester.circuit.wen = wen 55 | tester.circuit.cen = cen 56 | tester.circuit.addr = addr 57 | 58 | if width_mult == 1: 59 | tester.circuit.data_in = data[0] 60 | else: 61 | for i in range(width_mult): 62 | setattr(tester.circuit, f"data_in_{i}", data[i]) 63 | 64 | model_dat_out = model_sram.interact(wen, cen, addr, data) 65 | 66 | tester.eval() 67 | 68 | if width_mult == 1: 69 | tester.circuit.data_out.expect(model_dat_out[0]) 70 | else: 71 | for i in range(width_mult): 72 | getattr(tester.circuit, f"data_out_{i}").expect(model_dat_out[i]) 73 | 74 | tester.step(2) 75 | 76 | with tempfile.TemporaryDirectory() as tempdir: 77 | tester.compile_and_run(target="verilator", 78 | directory=tempdir, 79 | magma_output="verilog", 80 | flags=["-Wno-fatal"]) 81 | 82 | 83 | if __name__ == "__main__": 84 | test_sram_basic(data_width=16, 85 | depth=512, 86 | width_mult=4, 87 | num_tiles=1) 88 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/glb_read.sv: -------------------------------------------------------------------------------- 1 | module glb_read #( 2 | parameter NUM_BLOCKS = 1, 3 | parameter FILE_NAME = "dst.txt", 4 | parameter LOCATION = "X00_Y00", 5 | parameter TX_NUM = 1, 6 | parameter RAN_SHITF = 0 7 | ) 8 | ( 9 | input logic clk, 10 | input logic rst_n, 11 | input logic [16:0] data, 12 | output logic ready, 13 | input logic valid, 14 | output logic done, 15 | input logic flush 16 | ); 17 | 18 | logic [16:0] local_mem_0 [0:2047]; 19 | integer num_rx; 20 | integer size_0; 21 | 22 | string F1_PARGS; 23 | string NUM_BLOCKS_PARGS; 24 | integer NUM_BLOCKS_USE; 25 | string F1_USE; 26 | string ENABLED_PARGS; 27 | integer ENABLED; 28 | integer done_count; 29 | integer length_count; 30 | integer delay_count; 31 | integer ADD_DELAY; 32 | integer mask; 33 | 34 | initial begin 35 | 36 | ENABLED = 1; 37 | // ENABLED_PARGS = $sformatf("%s_ENABLED=%%d", LOCATION); 38 | // $value$plusargs(ENABLED_PARGS, ENABLED); 39 | 40 | num_rx = 0; 41 | ready = 0; 42 | size_0 = 0; 43 | done = 0; 44 | done_count = TX_NUM + 1; // extra 1 for the initialization 45 | length_count = 0; 46 | ADD_DELAY = 0; 47 | mask = 32'd3 << RAN_SHITF; 48 | 49 | if (ENABLED == 1) begin 50 | 51 | $display("%s is enabled...", LOCATION); 52 | 53 | F1_PARGS = $sformatf("%s_F1=%%s", LOCATION); 54 | NUM_BLOCKS_PARGS = $sformatf("%s_NUM_BLOCKS=%%d", LOCATION); 55 | 56 | $display("Location: %s", LOCATION); 57 | F1_USE = FILE_NAME; 58 | $display("F1_USE before: %s", F1_USE); 59 | $value$plusargs(F1_PARGS, F1_USE); 60 | $display("F1_USE after: %s", F1_USE); 61 | 62 | @(posedge flush); 63 | @(negedge flush); 64 | 65 | @(posedge clk); 66 | @(posedge clk); 67 | @(posedge clk); 68 | 69 | while(done_count != 1 | length_count != 0) begin 70 | @(posedge clk); 71 | #1; 72 | // ready = $urandom(); 73 | 74 | ready = 0; 75 | delay_count = $urandom & mask; 76 | while (delay_count > 0 & ADD_DELAY) begin 77 | @(posedge clk); 78 | #1; 79 | delay_count--; 80 | end 81 | 82 | ready = 1; 83 | if(ready == 1 && valid == 1) begin 84 | local_mem_0[num_rx] = data; 85 | if (length_count == 0) begin 86 | done_count = done_count - 1; 87 | length_count = data + 1; 88 | end 89 | num_rx = num_rx + 1; 90 | length_count = length_count - 1; 91 | end 92 | end 93 | @(posedge clk); 94 | #1; 95 | assert(valid == 0) else $error("Valid signal fails to end"); 96 | ready = 0; 97 | $writememh(F1_USE, local_mem_0); 98 | 99 | end 100 | 101 | @(posedge clk); 102 | ready = 0; 103 | done = 1; 104 | 105 | end 106 | 107 | endmodule 108 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/pass_through_tb.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ns 2 | `ifndef TX_NUM_0 3 | `define TX_NUM_0 1 4 | `endif 5 | `ifndef SEG_MODE 6 | `define SEG_MODE 1 7 | `endif 8 | 9 | module pass_through_tb; 10 | 11 | reg clk; 12 | reg clk_en; 13 | reg rst_n; 14 | reg stall; 15 | reg flush; 16 | reg tile_en; 17 | reg seg_mode; 18 | 19 | wire [63:0] cycle_count; 20 | 21 | // wire for dut input & output 22 | wire [16:0] stream_in_0; 23 | wire stream_in_0_valid; 24 | wire stream_in_0_ready; 25 | 26 | wire [16:0] stream_out; 27 | wire stream_out_valid; 28 | wire stream_out_ready; 29 | 30 | wire [4:0] done; 31 | parameter NUM_CYCLES = 4000; 32 | 33 | pass_through #( 34 | ) dut ( 35 | .clk(clk), 36 | .clk_en(clk_en), 37 | .flush(flush), 38 | .rst_n(rst_n), 39 | .stream_in(stream_in_0), 40 | .stream_in_valid(stream_in_0_valid), 41 | .stream_out_ready(stream_out_ready), 42 | .tile_en(tile_en), 43 | .stream_in_ready(stream_in_0_ready), 44 | .stream_out(stream_out), 45 | .stream_out_valid(stream_out_valid) 46 | ); 47 | 48 | glb_stream_write #( 49 | .FILE_NAME("stream_in_0.txt"), 50 | .TX_NUM(`TX_NUM_0), 51 | .RAN_SHITF(0) 52 | ) stream_in_0_inst ( 53 | .clk(clk), 54 | .rst_n(rst_n), 55 | .data(stream_in_0), 56 | .ready(stream_in_0_ready), 57 | .valid(stream_in_0_valid), 58 | .done(done[0]), 59 | .flush(flush), 60 | .seg_mode(seg_mode) 61 | ); 62 | 63 | glb_stream_read #( 64 | .FILE_NAME("stream_out.txt"), 65 | .TX_NUM(`TX_NUM_0), 66 | .RAN_SHITF(0) 67 | ) stream_out_inst ( 68 | .clk(clk), 69 | .rst_n(rst_n), 70 | .data(stream_out), 71 | .ready(stream_out_ready), 72 | .valid(stream_out_valid), 73 | .done(done[4]), 74 | .flush(flush), 75 | .seg_mode(seg_mode) 76 | ); 77 | 78 | 79 | integer start_record; 80 | integer clk_count; 81 | integer DONE_TOKEN = 17'h10100; 82 | 83 | // simulated clk signal, 10ns period 84 | initial begin 85 | start_record = 0; 86 | clk_count = 0; 87 | 88 | clk = 0; 89 | clk_en = 1; 90 | rst_n = 0; 91 | tile_en = 1; 92 | flush = 0; 93 | seg_mode = `SEG_MODE; 94 | 95 | #5 clk = 1; 96 | flush = 1; 97 | rst_n = 1; 98 | #5 clk = 0; 99 | flush = 0; 100 | 101 | for(integer i = 0; i < NUM_CYCLES * 2; i = i + 1) begin 102 | #5 clk = ~clk; 103 | if (~start_record && clk && stream_in_0_valid) begin 104 | start_record = 1; 105 | end 106 | if (clk && start_record && ~done[4]) begin 107 | clk_count += 1; 108 | end 109 | 110 | end 111 | $display("cycle count: %0d", clk_count); 112 | $finish; 113 | 114 | end 115 | 116 | endmodule 117 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/repeatsig_tb.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ns 2 | `ifndef TX_NUM_GLB 3 | `define TX_NUM_GLB 1 4 | `endif 5 | 6 | module repeatsig_tb; 7 | 8 | reg clk; 9 | reg clk_en; 10 | reg rst_n; 11 | reg stall; 12 | reg flush; 13 | reg tile_en; 14 | reg [15:0] lvl; 15 | wire [63:0] cycle_count ; 16 | 17 | // wire for dut input & output 18 | wire [16:0] base_data_in; 19 | wire base_data_in_valid; 20 | wire base_data_in_ready; 21 | 22 | wire [16:0] repsig_data_out; 23 | wire repsig_data_out_valid; 24 | wire repsig_data_out_ready; 25 | 26 | wire [1:0] done; 27 | parameter NUM_CYCLES = 4000; 28 | 29 | RepeatSignalGenerator #( 30 | 31 | ) dut ( 32 | .clk(clk), 33 | .clk_en(clk_en), 34 | .base_data_in(base_data_in), 35 | .base_data_in_valid(base_data_in_valid), 36 | .base_data_in_ready(base_data_in_ready), 37 | .tile_en(tile_en), 38 | .repsig_data_out(repsig_data_out), 39 | .repsig_data_out_valid(repsig_data_out_valid), 40 | .repsig_data_out_ready(repsig_data_out_ready), 41 | .rst_n(rst_n), 42 | .flush(flush), 43 | .tile_en(tile_en), 44 | .stop_lvl(lvl) 45 | ); 46 | 47 | tile_write #( 48 | .FILE_NAME("coord_in_0.txt"), 49 | .TX_NUM(`TX_NUM_GLB), 50 | .RAN_SHITF(0) 51 | ) base_data_in_inst ( 52 | .clk(clk), 53 | .rst_n(rst_n), 54 | .data(base_data_in), 55 | .ready(base_data_in_ready), 56 | .valid(base_data_in_valid), 57 | .done(done[0]), 58 | .flush(flush) 59 | ); 60 | 61 | tile_read #( 62 | .FILE_NAME("coord_out.txt"), 63 | .TX_NUM(`TX_NUM_GLB), 64 | .RAN_SHITF(1) 65 | ) repsig_data_out_inst ( 66 | .clk(clk), 67 | .rst_n(rst_n), 68 | .data(repsig_data_out), 69 | .ready(repsig_data_out_ready), 70 | .valid(repsig_data_out_valid), 71 | .done(done[1]), 72 | .flush(flush) 73 | ); 74 | 75 | 76 | integer start_record; 77 | integer clk_count; 78 | integer DONE_TOKEN = 17'h10100; 79 | 80 | // simulated clk signal, 10ns period 81 | initial begin 82 | start_record = 0; 83 | clk_count = 0; 84 | lvl = 0; 85 | 86 | clk = 0; 87 | clk_en = 1; 88 | rst_n = 0; 89 | tile_en = 1; 90 | flush = 0; 91 | 92 | #5 clk = 1; 93 | flush = 1; 94 | rst_n = 1; 95 | #5 clk = 0; 96 | flush = 0; 97 | 98 | for(integer i = 0; i < NUM_CYCLES * 2; i = i + 1) begin 99 | #5 clk = ~clk; 100 | if (~start_record && clk && base_data_in_valid) begin 101 | start_record = 1; 102 | end 103 | if (clk && start_record && ~done[1]) begin 104 | clk_count += 1; 105 | end 106 | 107 | end 108 | $display("cycle count: %0d", clk_count); 109 | $finish; 110 | 111 | end 112 | 113 | endmodule 114 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/tile_write.sv: -------------------------------------------------------------------------------- 1 | module tile_write #( 2 | parameter TX_SIZE = 2048, 3 | parameter FILE_NAME = "src.txt", 4 | parameter LOCATION = "X00_Y00", 5 | parameter TX_NUM = 1, 6 | parameter RAN_SHITF = 0 7 | ) 8 | ( 9 | input logic clk, 10 | input logic rst_n, 11 | output logic [16:0] data, 12 | input logic ready, 13 | output logic valid, 14 | output logic done, 15 | input logic flush 16 | ); 17 | 18 | logic [16:0] local_mem [0:2047]; 19 | integer num_tx; 20 | 21 | string TX_SIZE_PARGS; 22 | string FILE_NAME_PARGS; 23 | integer TX_SIZE_USE; 24 | string FILE_NAME_USE; 25 | string ENABLED_PARGS; 26 | integer ENABLED; 27 | integer DELAY; 28 | integer ADD_DELAY; 29 | integer done_count; 30 | integer DONE_TOKEN; 31 | integer mask; 32 | 33 | initial begin 34 | 35 | num_tx = 0; 36 | valid = 0; 37 | done = 0; 38 | data = 0; 39 | valid = 0; 40 | ENABLED = 1; 41 | ADD_DELAY = 0; 42 | done_count = TX_NUM; 43 | DONE_TOKEN = 17'h10100; 44 | mask = 32'd3 << RAN_SHITF; 45 | 46 | // ENABLED_PARGS = $sformatf("%s_ENABLED=%%d", LOCATION); 47 | // $value$plusargs(ENABLED_PARGS, ENABLED); 48 | 49 | if (ENABLED == 1) begin 50 | 51 | $display("%s is enabled...", LOCATION); 52 | 53 | FILE_NAME_PARGS = $sformatf("%s_FILE_NAME=%%s", LOCATION); 54 | FILE_NAME_USE = FILE_NAME; 55 | $value$plusargs(FILE_NAME_PARGS, FILE_NAME_USE); 56 | // string file_str; 57 | // file_str = $sformatf("/home/max/Documents/SPARSE/garnet/generic_memory_%d.txt", FILE_NO); 58 | $readmemh(FILE_NAME_USE, local_mem); 59 | 60 | TX_SIZE_PARGS = $sformatf("%s_TX_SIZE=%%d", LOCATION); 61 | TX_SIZE_USE = TX_SIZE; 62 | $value$plusargs(TX_SIZE_PARGS, TX_SIZE_USE); 63 | 64 | num_tx = 0; 65 | valid = 0; 66 | done = 0; 67 | data = 0; 68 | 69 | @(posedge flush); 70 | @(negedge flush); 71 | 72 | @(posedge clk); 73 | @(posedge clk); 74 | @(posedge clk); 75 | 76 | // Make as many transfers from the memory as needed. 77 | while(num_tx < TX_SIZE_USE && done_count > 0) begin 78 | @(posedge clk); 79 | #1; //TODO: debug the issue with the 1 unit time delay with line 90 80 | 81 | valid = 0; 82 | DELAY = $urandom & mask; 83 | DELAY = DELAY >> RAN_SHITF; 84 | while (DELAY > 0 & ADD_DELAY) begin 85 | @(posedge clk); 86 | // #1; 87 | DELAY--; 88 | end 89 | 90 | data = local_mem[num_tx]; 91 | valid = 1; 92 | #1; 93 | if(ready == 1 && valid == 1) begin 94 | if (data == DONE_TOKEN) begin 95 | done_count--; 96 | // valid = 0; 97 | end 98 | num_tx = num_tx + 1; 99 | end 100 | end 101 | end 102 | 103 | @(posedge clk); 104 | done = 1; 105 | valid = 0; 106 | 107 | end 108 | 109 | endmodule 110 | -------------------------------------------------------------------------------- /lake/models/rw_arbiter_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | 3 | 4 | class RWArbiterModel(Model): 5 | def __init__(self, 6 | fetch_width, 7 | data_width, 8 | memory_depth, 9 | int_out_ports, 10 | read_delay): 11 | self.fetch_width = fetch_width 12 | self.memory_depth = memory_depth 13 | self.int_out_ports = int_out_ports 14 | self.read_delay = read_delay 15 | 16 | self.rd_valid = 0 17 | self.rd_port = 0 18 | self.ack = 0 19 | 20 | def set_config(self, new_config): 21 | # No configuration space 22 | return 23 | 24 | def interact(self, 25 | wen_in, 26 | wen_en, 27 | w_data, 28 | w_addr, 29 | data_from_mem, 30 | ren_in, 31 | ren_en, 32 | rd_addr, 33 | mem_valid_data): 34 | ''' 35 | Returns (out_dat, out_port, out_valid, 36 | cen_mem, wen_mem, data_to_mem, addr_to_mem, ack, 37 | out_mem_valid_data) 38 | ''' 39 | # These signals are always this way 40 | out_dat = data_from_mem 41 | if self.read_delay == 1: 42 | out_port = self.rd_port 43 | out_valid = self.rd_valid 44 | else: 45 | out_port = 0 46 | out_valid = 0 47 | data_to_mem = w_data 48 | if type(wen_en) is list: 49 | wen_en = wen_en[0] 50 | wen_mem = wen_in & wen_en 51 | # Signals following may vary 52 | cen_mem = 0 53 | addr_to_mem = 0 54 | 55 | self.rd_valid = 0 56 | if wen_in != 0 and wen_en != 0: 57 | cen_mem = 1 58 | addr_to_mem = w_addr 59 | self.rd_valid = 0 60 | self.rd_port = 0 61 | elif ren_in != 0 and ren_en != 0: 62 | for i in range(self.int_out_ports): 63 | # Select lowest 64 | if ren_in[i] != 0 and ren_en[i] != 0: 65 | cen_mem = 1 66 | addr_to_mem = rd_addr[i] 67 | self.rd_valid = 1 68 | self.rd_port = 1 << i 69 | if self.read_delay == 0: 70 | out_valid = 1 71 | out_port = 1 << i 72 | break 73 | ack = self.get_ack(wen_in, wen_en, ren_in, ren_en) 74 | 75 | out_mem_valid_data = mem_valid_data 76 | 77 | return (out_dat, out_port, out_valid, 78 | cen_mem, wen_mem, data_to_mem, 79 | addr_to_mem, ack, out_mem_valid_data) 80 | 81 | def get_ack(self, wen_in, wen_en, ren_in, ren_en): 82 | self.ack = 0 83 | if wen_in != 0 and wen_en != 0: 84 | self.ack = 0 85 | elif ren_in != 0 and ren_en != 0: 86 | for i in range(self.int_out_ports): 87 | # Select lowest 88 | if ren_in[i] != 0 and ren_en[i]: 89 | self.ack = 1 << i 90 | break 91 | return self.ack 92 | -------------------------------------------------------------------------------- /lake/modules/onyx_pe_intf.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | from kratos import * 3 | from lake.top.memory_controller import MemoryController 4 | from lake.attributes.config_reg_attr import ConfigRegAttr 5 | import math 6 | from peak.assembler import Assembler 7 | from hwtypes.modifiers import strip_modifiers 8 | from lassen.sim import PE_fc as lassen_fc 9 | import lassen.asm as asm 10 | from lassen.asm import Mode_t 11 | 12 | 13 | class OnyxPEInterface(MemoryController): 14 | def __init__(self, 15 | data_width=16, 16 | name_prefix=None, 17 | include_RO_cfg=False): 18 | 19 | base_name = "PE" 20 | if name_prefix is not None: 21 | base_name = f"{name_prefix}{base_name}" 22 | 23 | self.ro_config = include_RO_cfg 24 | 25 | super().__init__(base_name, debug=True) 26 | 27 | self.data_width = data_width 28 | self.add_clk_enable = True 29 | self.add_flush = True 30 | 31 | # For consistency with Core wrapper in garnet... 32 | self.total_sets = 0 33 | 34 | # inputs 35 | self._clk = self.clock("CLK") 36 | # self._clk.add_attribute(FormalAttr(f"{self._clk.name}", FormalSignalConstraint.CLK)) 37 | self._rst_n = self.reset("ASYNCRESET") 38 | self._clk_en = self.clock_en("clk_en", 1) 39 | 40 | # Instruction 41 | self._inst = self.input("inst", 84) 42 | self._inst.add_attribute(ConfigRegAttr("PE Instruction")) 43 | 44 | self._data0 = self.input("data0", self.data_width) 45 | self._data1 = self.input("data1", self.data_width) 46 | self._data2 = self.input("data2", self.data_width) 47 | self._bit0 = self.input("bit0", 1) 48 | self._bit1 = self.input("bit1", 1) 49 | self._bit2 = self.input("bit2", 1) 50 | 51 | if self.ro_config: 52 | self._O2 = self.output("O2", self.data_width) 53 | self._O2.add_attribute(ConfigRegAttr("PIPE REG 0", read_only=True)) 54 | self._O3 = self.output("O3", self.data_width) 55 | self._O3.add_attribute(ConfigRegAttr("PIPE REG 1", read_only=True)) 56 | self._O4 = self.output("O4", self.data_width) 57 | self._O4.add_attribute(ConfigRegAttr("PIPE REG 2", read_only=True)) 58 | 59 | self._O0 = self.output("O0", self.data_width) 60 | self._O1 = self.output("O1", 1) 61 | 62 | self.external = True 63 | 64 | def get_memory_ports(self): 65 | ''' 66 | Use this method to indicate what memory ports this controller has 67 | ''' 68 | return [[None]] 69 | 70 | def get_config_mode_str(self): 71 | return "alu_ext" 72 | 73 | def get_bitstream(self, op): 74 | config_base = [("inst", op)] 75 | config = self.chop_config(config_base=config_base) 76 | return config 77 | 78 | 79 | if __name__ == "__main__": 80 | 81 | pe_dut = OnyxPEInterface(data_width=16) 82 | 83 | # Lift config regs and generate annotation 84 | # lift_config_reg(pond_dut.internal_generator) 85 | # extract_formal_annotation(pond_dut, "pond.txt") 86 | 87 | verilog(pe_dut, filename="pe.sv", 88 | optimize_if=False) 89 | -------------------------------------------------------------------------------- /lake/modules/cfg_reg_wrapper.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | import kratos as kts 3 | from _kratos import create_wrapper_flatten 4 | from math import log 5 | 6 | from lake.attributes.config_reg_attr import ConfigRegAttr 7 | from lake.attributes.formal_attr import FormalAttr, FormalSignalConstraint 8 | from lake.dsl.edge import get_full_edge_params 9 | from lake.dsl.helper import * 10 | from lake.dsl.mem_port import MemPort 11 | from lake.dsl.memory import mem_inst 12 | from lake.modules.for_loop import ForLoop 13 | from lake.modules.addr_gen import AddrGen 14 | from lake.modules.spec.sched_gen import SchedGen 15 | from lake.passes.passes import lift_config_reg 16 | from lake.utils.util import safe_wire, trim_config_list 17 | from lake.utils.util import extract_formal_annotation, modular_formal_annotation 18 | from lake.utils.parse_clkwork_config import * 19 | 20 | 21 | ''' 22 | Basically creating configuration wrapper with 23 | bogus config bus for synthesis numbers. 24 | ''' 25 | 26 | 27 | class CFGRegWrapper(Generator): 28 | def __init__(self, 29 | child_gen: kts.Generator): 30 | 31 | super().__init__(f"{child_gen.name}_cfg_W", debug=True) 32 | 33 | # Works for now since we always have these... 34 | self._clk = self.clock("clk") 35 | self._rst_n = self.reset("rst_n") 36 | 37 | self.add_child_generator(f"{child_gen.name}", child_gen, 38 | clk=self._clk, 39 | rst_n=self._rst_n) 40 | 41 | # Create fake config bus... 42 | self._config_en = self.input("config_en", 1) 43 | self._config_data = self.input("config_data", 256) 44 | 45 | # Now copy inputs and outputs that aren't config regs 46 | 47 | for port in child_gen.ports: 48 | actual_port = child_gen.ports[port] 49 | self.uplevel_port(actual_port) 50 | 51 | def uplevel_port(self, port): 52 | 53 | pname = port.name 54 | pdir = str(port.port_direction) 55 | ptype = str(port.port_type) 56 | pattrs = port.get_attributes() 57 | 58 | if pname == "clk" or pname == "rst_n": 59 | return 60 | 61 | if ptype == "PortType.Data" and pdir == "PortDirection.In": 62 | cfgreg_attr = False 63 | for attr in pattrs: 64 | if isinstance(attr, ConfigRegAttr): 65 | cfgreg_attr = True 66 | # We know to hook it up to bogus config bus 67 | if cfgreg_attr is True: 68 | newvar = self.var_from_def(port, pname) 69 | self.wire(newvar, port) 70 | 71 | @always_ff((posedge, "clk"), (negedge, "rst_n")) 72 | def add_to_config_reg(self): 73 | if ~self._rst_n: 74 | newvar = 0 75 | elif self._config_en: 76 | newvar = 1 77 | 78 | self.add_code(add_to_config_reg) 79 | # Otherwise pass it through... 80 | else: 81 | newin = self.port_from_def(port, pname) 82 | self.wire(newin, port) 83 | else: 84 | newport = self.port_from_def(port, pname) 85 | self.wire(newport, port) 86 | -------------------------------------------------------------------------------- /lake/top/extract_tile_info.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | import _kratos 3 | from lake.attributes.config_reg_attr import ConfigRegAttr 4 | from lake.attributes.control_signal_attr import ControlSignalAttr 5 | from lake.passes.passes import lift_config_reg, change_sram_port_names 6 | from lake.utils.sram_macro import SRAMMacroInfo 7 | import collections 8 | 9 | 10 | CFG_info = collections.namedtuple('CFG_info', 'port_name port_size port_width expl_arr read_only') 11 | IO_info = collections.namedtuple('IO_info', 'port_name port_size port_width is_ctrl port_dir expl_arr full_bus') 12 | 13 | 14 | def extract_top_config(circuit_gen: kts.Generator, verbose=False): 15 | int_gen = circuit_gen.internal_generator 16 | config_list = [] 17 | 18 | # Now get the config registers from the top definition 19 | for port_name in int_gen.get_port_names(): 20 | curr_port = int_gen.get_port(port_name) 21 | attrs = curr_port.find_attribute(lambda a: isinstance(a, ConfigRegAttr)) 22 | if len(attrs) != 1: 23 | continue 24 | cr_attr = attrs[0] 25 | if verbose: 26 | print(port_name) 27 | print(cr_attr.get_documentation()) 28 | config_list.append(CFG_info(port_name=port_name, 29 | port_size=curr_port.size, 30 | port_width=curr_port.width, 31 | expl_arr=curr_port.explicit_array, 32 | read_only=cr_attr.get_read_only())) 33 | return config_list 34 | 35 | 36 | def get_interface(circuit_gen: kts.Generator): 37 | intf_sigs = [] 38 | int_gen = circuit_gen.internal_generator 39 | # Now get the config registers from the top definition 40 | for port_name in int_gen.get_port_names(): 41 | curr_port = int_gen.get_port(port_name) 42 | attrs = curr_port.find_attribute(lambda a: isinstance(a, ControlSignalAttr)) 43 | if len(attrs) != 1: 44 | continue 45 | cr_attr = attrs[0] 46 | # Now we have this 47 | intf_sigs.append(IO_info(port_name=port_name, 48 | port_size=curr_port.size, 49 | port_width=curr_port.width, 50 | is_ctrl=cr_attr.get_control(), 51 | port_dir=str(curr_port.port_direction), 52 | expl_arr=curr_port.explicit_array, 53 | full_bus=cr_attr.get_full_bus())) 54 | return intf_sigs 55 | 56 | 57 | if __name__ == "__main__": 58 | from lake.top.lake_top import LakeTop 59 | tsmc_info = SRAMMacroInfo("tsmc_name") 60 | use_sram_stub = False 61 | fifo_mode = True 62 | mem_width = 64 63 | lake_dut = LakeTop(mem_width=mem_width, 64 | sram_macro_info=tsmc_info, 65 | use_sram_stub=use_sram_stub, 66 | fifo_mode=fifo_mode, 67 | add_clk_enable=True, 68 | add_flush=True) 69 | sram_port_pass = change_sram_port_names(use_sram_stub=use_sram_stub, sram_macro_info=tsmc_info) 70 | # Perform pass to move config_reg 71 | extract_top_config(lake_dut) 72 | # get_interface(lake_dut) 73 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/glb_write.sv: -------------------------------------------------------------------------------- 1 | module glb_write #( 2 | parameter TX_SIZE = 2048, 3 | parameter FILE_NAME = "src.txt", 4 | parameter LOCATION = "X00_Y00", 5 | parameter TX_NUM = 1, 6 | parameter RAN_SHITF = 0 7 | ) 8 | ( 9 | input logic clk, 10 | input logic rst_n, 11 | output logic [16:0] data, 12 | input logic ready, 13 | output logic valid, 14 | output logic done, 15 | input logic flush 16 | ); 17 | 18 | logic [16:0] local_mem [0:2047]; 19 | integer num_tx; 20 | 21 | string TX_SIZE_PARGS; 22 | string FILE_NAME_PARGS; 23 | integer TX_SIZE_USE; 24 | string FILE_NAME_USE; 25 | string ENABLED_PARGS; 26 | integer ENABLED; 27 | integer DELAY; 28 | integer ADD_DELAY; 29 | integer done_count; 30 | integer length_count; 31 | integer mask; 32 | 33 | initial begin 34 | 35 | num_tx = 0; 36 | valid = 0; 37 | done = 0; 38 | data = 0; 39 | valid = 0; 40 | ENABLED = 1; 41 | ADD_DELAY = 0; 42 | done_count = TX_NUM; 43 | length_count = 0; 44 | mask = 32'd3 << RAN_SHITF; 45 | 46 | // ENABLED_PARGS = $sformatf("%s_ENABLED=%%d", LOCATION); 47 | // $value$plusargs(ENABLED_PARGS, ENABLED); 48 | 49 | if (ENABLED == 1) begin 50 | 51 | $display("%s is enabled...", LOCATION); 52 | 53 | FILE_NAME_PARGS = $sformatf("%s_FILE_NAME=%%s", LOCATION); 54 | FILE_NAME_USE = FILE_NAME; 55 | $value$plusargs(FILE_NAME_PARGS, FILE_NAME_USE); 56 | // string file_str; 57 | // file_str = $sformatf("/home/max/Documents/SPARSE/garnet/generic_memory_%d.txt", FILE_NO); 58 | $readmemh(FILE_NAME_USE, local_mem); 59 | 60 | TX_SIZE_PARGS = $sformatf("%s_TX_SIZE=%%d", LOCATION); 61 | TX_SIZE_USE = TX_SIZE; 62 | $value$plusargs(TX_SIZE_PARGS, TX_SIZE_USE); 63 | 64 | num_tx = 0; 65 | length_count = local_mem[num_tx] + 1; 66 | valid = 0; 67 | done = 0; 68 | data = 0; 69 | 70 | @(posedge flush); 71 | @(negedge flush); 72 | 73 | @(posedge clk); 74 | @(posedge clk); 75 | @(posedge clk); 76 | 77 | // Make as many transfers from the memory as needed. 78 | while(num_tx < TX_SIZE_USE && done_count > 0) begin 79 | @(posedge clk); 80 | #1; //TODO: debug the issue with the 1 unit time delay with line 90 81 | 82 | valid = 0; 83 | DELAY = $urandom & mask; 84 | while (DELAY > 0 & ADD_DELAY) begin 85 | @(posedge clk); 86 | // #1; 87 | DELAY--; 88 | end 89 | 90 | data = local_mem[num_tx]; 91 | valid = 1; 92 | #1; 93 | if(ready == 1 && valid == 1) begin 94 | num_tx = num_tx + 1; 95 | length_count = length_count - 1; 96 | if (length_count == 0) begin 97 | done_count = done_count - 1; 98 | length_count = local_mem[num_tx] + 1; // potential segfault 99 | end 100 | end 101 | end 102 | end 103 | 104 | @(posedge clk); 105 | done = 1; 106 | valid = 0; 107 | 108 | end 109 | 110 | endmodule 111 | -------------------------------------------------------------------------------- /lake/spec/ir.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class bound: 5 | def __init__(self, n, _lb, _ub): 6 | self.name = n 7 | self.lb = _lb 8 | self.ub = _ub 9 | 10 | def getLB(self): 11 | return self.lb 12 | 13 | def getUB(self): 14 | return self.ub 15 | 16 | def inBound(self, val): 17 | return val >= self.lb and val <= self.ub 18 | 19 | 20 | class var: 21 | def __init__(self, n, _lb, _ub): 22 | # self.name = n 23 | # self.lb = _lb 24 | # self.ub = _ub 25 | self.name = n 26 | self.bd = bound(n + "_bd", _lb, _ub) 27 | self.val = self.bd.getLB() 28 | 29 | def update(self) -> bool: 30 | if self.val < self.bd.getUB(): 31 | self.val += 1 32 | return False 33 | else: 34 | self.val = self.bd.getLB() 35 | return True 36 | 37 | def getVal(self): 38 | return self.val 39 | 40 | 41 | class expr: 42 | def __init__(self, _var_list: list, bd_w_list: list): 43 | ''' 44 | piecewise linear expression, weight is a dictionary 45 | the key save the domain of that piece, and value is 46 | the weight which gives the function expression 47 | ''' 48 | self.var_list = _var_list 49 | self.weight_list = [] 50 | self.bd_list = [] 51 | for bd, w in bd_w_list: 52 | self.weight_list.append(w) 53 | self.bd_list.append(bd) 54 | self.piece = len(bd_w_list) 55 | 56 | def eval(self) -> int: 57 | var_list = [var.getVal() for var in self.var_list] 58 | for nd_bd, weights in zip(self.bd_list, self.weight_list): 59 | list_inBD = [bd.inBound(val) for bd, val in zip(nd_bd, var_list)] 60 | if all(list_inBD): 61 | val = 0 62 | val += weights[-1] 63 | for idx, var in enumerate(self.var_list): 64 | val += weights[idx] * var.getVal() 65 | return val 66 | assert False, "variable exceeded all pieces of bounds." 67 | 68 | 69 | class map: 70 | def __init__(self, _var_list: list, _expr_list: list): 71 | ''' 72 | map is a list of variable map to a list of expression, 73 | basically a [in_dim] vector of varibles map to a [out_dim] 74 | vector of variable 75 | ''' 76 | self.var_list = _var_list 77 | self.expr_list = _expr_list 78 | self.in_dim = len(self.var_list) 79 | self.out_dim = len(self.expr_list) 80 | self.flatten_eval_vec = [1 for _ in range(self.out_dim)] 81 | 82 | def set_flatten_eval_vec(self, vec: list): 83 | self.flatten_eval_vec = vec 84 | 85 | def eval(self): 86 | ret = [] 87 | for expr in self.expr_list: 88 | ret.append(expr.eval()) 89 | return np.dot(ret, self.flatten_eval_vec) 90 | 91 | def getDomain(self): 92 | ret = {} 93 | for var in self.var_list: 94 | ret[var.name] = var.getVal() 95 | return ret 96 | 97 | def update(self): 98 | for var in self.var_list: 99 | inc_next = var.update() 100 | if inc_next is False: 101 | break 102 | -------------------------------------------------------------------------------- /lake/modules/sram_stub.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from math import log 3 | 4 | 5 | class SRAMStub(Generator): 6 | 7 | ########################## 8 | # Generation # 9 | ########################## 10 | def __init__(self, 11 | data_width, 12 | width_mult, 13 | depth): 14 | super().__init__("sram_stub") 15 | self.data_width = data_width 16 | self.width_mult = width_mult 17 | self.depth = depth 18 | 19 | ############################ 20 | # Clock and Reset # 21 | ############################ 22 | self._clk = self.clock("clk") 23 | 24 | ############################ 25 | # Inputs # 26 | ############################ 27 | self._wen = self.input("wen", 1) 28 | self._cen = self.input("cen", 1) 29 | self._addr = self.input("addr", clog2(self.depth)) 30 | # self._data_in = self.input("data_in", self.data_width) 31 | self._data_in = self.input("data_in", 32 | self.data_width, 33 | size=self.width_mult, 34 | explicit_array=True, 35 | packed=True) 36 | 37 | ############################ 38 | # Outputs # 39 | ############################ 40 | # self._data_out = self.output("data_out", self.data_width) 41 | self._data_out = self.output("data_out", 42 | self.data_width, 43 | size=self.width_mult, 44 | explicit_array=True, 45 | packed=True) 46 | 47 | ############################ 48 | # Local Variables # 49 | ############################ 50 | # self._data_array = self.var("data_array", 51 | # self.data_width, 52 | # size=self.depth, 53 | # packed=True, 54 | # explicit_array=True) 55 | self._data_array = self.var("data_array", 56 | self.data_width, 57 | size=(self.depth, 58 | self.width_mult), 59 | packed=True, 60 | explicit_array=True) 61 | 62 | ############################ 63 | # Add seq blocks # 64 | ############################ 65 | self.add_code(self.seq_data_access) 66 | self.add_code(self.seq_data_out) 67 | 68 | ########################## 69 | # Access sram array # 70 | ########################## 71 | @always_ff((posedge, "clk")) 72 | def seq_data_access(self): 73 | if self._cen & self._wen: 74 | self._data_array[self._addr] = self._data_in 75 | 76 | @always_ff((posedge, "clk")) 77 | def seq_data_out(self): 78 | if self._cen & ~self._wen: 79 | self._data_out = self._data_array[self._addr] 80 | 81 | 82 | if __name__ == "__main__": 83 | dut = SRAMStub(16, 1, 1024) 84 | verilog(dut, filename="sram_stub.sv") 85 | -------------------------------------------------------------------------------- /lake/modules/demux_reads.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | import kratos as kts 3 | 4 | 5 | class DemuxReads(Generator): 6 | ''' 7 | Demux the SRAM reads into writes to the transpose buffers 8 | ''' 9 | def __init__(self, 10 | fetch_width=16, 11 | data_width=16, 12 | banks=1, 13 | int_out_ports=2, 14 | strg_rd_ports=2): 15 | 16 | assert not (fetch_width & (fetch_width - 1)), "Memory width needs to be a power of 2" 17 | 18 | super().__init__("demux_reads") 19 | # Absorb inputs 20 | self.fetch_width = fetch_width 21 | self.data_width = data_width 22 | self.fw_int = int(self.fetch_width / self.data_width) 23 | self.int_out_ports = int_out_ports 24 | self.banks = banks 25 | self.strg_rd_ports = strg_rd_ports 26 | 27 | # Clock and Reset 28 | self._clk = self.clock("clk") 29 | self._rst_n = self.reset("rst_n") 30 | 31 | # Inputs 32 | self._data_in = self.input("data_in", 33 | self.data_width, 34 | size=(self.banks * self.strg_rd_ports, 35 | self.fw_int), 36 | explicit_array=True, 37 | packed=True) 38 | 39 | self._valid_in = self.input("valid_in", self.banks * self.strg_rd_ports) 40 | self._port_in = self.input("port_in", 41 | self.int_out_ports, 42 | size=self.strg_rd_ports * self.banks, 43 | explicit_array=True, 44 | packed=True) 45 | 46 | # Outputs 47 | self._data_out = self.output("data_out", 48 | self.data_width, 49 | size=(self.int_out_ports, 50 | self.fw_int), 51 | explicit_array=True, 52 | packed=True) 53 | 54 | self._valid_out = self.output("valid_out", 55 | self.int_out_ports) 56 | 57 | self._mem_valid_data = self.input("mem_valid_data", self.banks * self.strg_rd_ports) 58 | self._mem_valid_data_out = self.output("mem_valid_data_out", self.int_out_ports) 59 | 60 | # Vars 61 | self._done = self.var("done", self.int_out_ports) 62 | 63 | self.add_code(self.set_outs) 64 | 65 | @always_comb 66 | def set_outs(self): 67 | for i in range(self.int_out_ports): 68 | self._valid_out[i] = 0 69 | self._data_out[i] = 0 70 | self._done[i] = 0 71 | self._mem_valid_data_out[i] = 0 72 | for j in range(self.banks * self.strg_rd_ports): 73 | if ~self._done[i]: 74 | if self._valid_in[j] & self._port_in[j][i]: 75 | self._valid_out[i] = 1 76 | self._data_out[i] = self._data_in[j] 77 | self._mem_valid_data_out[i] = self._mem_valid_data[j] 78 | self._done[i] = 1 79 | 80 | 81 | if __name__ == "__main__": 82 | demux_dut = DemuxReads() 83 | verilog(demux_dut, filename="demux_reads.sv") 84 | -------------------------------------------------------------------------------- /tests/test_prefetcher.py: -------------------------------------------------------------------------------- 1 | from lake.models.prefetcher_model import PrefetcherModel 2 | from lake.modules.prefetcher import Prefetcher 3 | from lake.passes.passes import lift_config_reg 4 | import _kratos 5 | import magma as m 6 | from magma import * 7 | import fault 8 | import tempfile 9 | import kratos as k 10 | import random as rand 11 | import pytest 12 | 13 | 14 | @pytest.mark.skip 15 | def test_prefetcher_basic(input_latency=10, 16 | max_prefetch=64, 17 | fetch_width=32, 18 | data_width=16): 19 | 20 | assert input_latency < max_prefetch, "Input latency must be smaller than fifo" 21 | 22 | fw_int = int(fetch_width / data_width) 23 | 24 | # Set up model.. 25 | model_pf = PrefetcherModel(fetch_width=fetch_width, 26 | data_width=data_width, 27 | max_prefetch=max_prefetch) 28 | new_config = {} 29 | new_config['input_latency'] = input_latency 30 | 31 | model_pf.set_config(new_config=new_config) 32 | ### 33 | 34 | # Set up dut... 35 | dut = Prefetcher(fetch_width=fetch_width, 36 | data_width=data_width, 37 | max_prefetch=max_prefetch) 38 | lift_config_reg(dut.internal_generator) 39 | magma_dut = k.util.to_magma(dut, flatten_array=True, 40 | check_multiple_driver=False, 41 | check_flip_flop_always_ff=False) 42 | tester = fault.Tester(magma_dut, magma_dut.clk) 43 | ### 44 | 45 | for key, value in new_config.items(): 46 | setattr(tester.circuit, key, value) 47 | 48 | # initial reset 49 | tester.circuit.clk = 0 50 | tester.circuit.rst_n = 0 51 | tester.circuit.data_in = 0 52 | tester.circuit.valid_read = 0 53 | tester.circuit.tba_rdy_in = 0 54 | tester.step(2) 55 | tester.circuit.rst_n = 1 56 | tester.step(2) 57 | # Seed for posterity 58 | rand.seed(0) 59 | 60 | data_in = [0 for i in range(fw_int)] 61 | 62 | for i in range(1000): 63 | # Gen random data 64 | print(i) 65 | for j in range(fw_int): 66 | data_in[j] = rand.randint(0, 2 ** data_width - 1) 67 | tba_rdy_in = rand.randint(0, 1) 68 | valid_read = rand.randint(0, 1) 69 | mem_valid_data = rand.randint(0, 1) 70 | 71 | (model_d, model_v, model_stp, model_mem_valid) = \ 72 | model_pf.interact(data_in, valid_read, tba_rdy_in, mem_valid_data) 73 | 74 | for j in range(fw_int): 75 | setattr(tester.circuit, f"data_in_{j}", data_in[j]) 76 | tester.circuit.valid_read = valid_read 77 | tester.circuit.tba_rdy_in = tba_rdy_in 78 | 79 | tester.eval() 80 | 81 | # Check the step 82 | tester.circuit.prefetch_step.expect(model_stp) 83 | tester.circuit.valid_out.expect(model_v) 84 | if (model_v): 85 | for j in range(fw_int): 86 | getattr(tester.circuit, f"data_out_{j}").expect(model_d[j]) 87 | 88 | tester.step(2) 89 | 90 | with tempfile.TemporaryDirectory() as tempdir: 91 | tester.compile_and_run(target="verilator", 92 | directory=tempdir, 93 | magma_output="verilog", 94 | flags=["-Wno-fatal"]) 95 | 96 | 97 | if __name__ == "__main__": 98 | test_prefetcher_basic() 99 | -------------------------------------------------------------------------------- /tests/test_agg_buffer.py: -------------------------------------------------------------------------------- 1 | from lake.models.agg_buff_model import AggBuffModel 2 | from lake.modules.aggregation_buffer import AggregationBuffer 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | import kratos as k 8 | import random as rand 9 | import pytest 10 | 11 | 12 | @pytest.mark.skip 13 | def test_agg_buff_basic(agg_height=4, 14 | data_width=16, 15 | mem_width=64, 16 | max_agg_schedule=64): 17 | 18 | # Set up model... 19 | model_ab = AggBuffModel(agg_height=agg_height, 20 | data_width=data_width, 21 | mem_width=mem_width, 22 | max_agg_schedule=max_agg_schedule) 23 | new_config = {} 24 | new_config['in_sched_0'] = 0 25 | new_config['in_sched_1'] = 1 26 | new_config['in_sched_2'] = 2 27 | new_config['in_sched_3'] = 3 28 | new_config['out_sched_0'] = 0 29 | new_config['out_sched_1'] = 1 30 | new_config['out_sched_2'] = 2 31 | new_config['out_sched_3'] = 3 32 | new_config['in_period'] = 4 33 | new_config['out_period'] = 4 34 | 35 | model_ab.set_config(new_config=new_config) 36 | ### 37 | 38 | # Set up dut... 39 | dut = AggregationBuffer(agg_height=agg_height, 40 | data_width=data_width, 41 | mem_width=mem_width, 42 | max_agg_schedule=max_agg_schedule) 43 | 44 | magma_dut = k.util.to_magma(dut, flatten_array=True, 45 | check_flip_flop_always_ff=False) 46 | tester = fault.Tester(magma_dut, magma_dut.clk) 47 | ### 48 | 49 | num_per_agg = int(mem_width / data_width) 50 | 51 | for key, value in new_config.items(): 52 | setattr(tester.circuit, key, value) 53 | 54 | # initial reset 55 | tester.circuit.clk = 0 56 | tester.circuit.rst_n = 0 57 | tester.step(2) 58 | tester.circuit.rst_n = 1 59 | tester.step(2) 60 | 61 | rand.seed(0) 62 | rand.randint(0, 1) 63 | 64 | data_in = 0 65 | valid_in = 0 66 | 67 | write_act = 0 68 | 69 | for i in range(1000): 70 | 71 | valid_in = rand.randint(0, 1) 72 | data_in = rand.randint(0, 2 ** 16 - 1) 73 | align = rand.randint(0, 1) 74 | write_act = 0 75 | 76 | # Circuit 77 | tester.circuit.valid_in = valid_in 78 | tester.circuit.data_in = data_in 79 | tester.circuit.align = align 80 | # Model 81 | (mod_dat, mod_val) = model_ab.interact(data_in, valid_in, write_act, align) 82 | if mod_val: 83 | write_act = 1 84 | tester.circuit.write_act = write_act 85 | 86 | tester.eval() 87 | 88 | tester.circuit.valid_out.expect(mod_val) 89 | if (mod_val == 1): 90 | # Check the data on the output... 91 | for j in range(num_per_agg): 92 | getattr(tester.circuit, 93 | f"data_out_chop_{j}").expect(mod_dat[j]) 94 | 95 | tester.step(2) 96 | 97 | with tempfile.TemporaryDirectory() as tempdir: 98 | tester.compile_and_run(target="verilator", 99 | directory=tempdir, 100 | magma_output="verilog", 101 | flags=["-Wno-fatal"]) 102 | 103 | 104 | if __name__ == "__main__": 105 | test_agg_buff_basic() 106 | -------------------------------------------------------------------------------- /lake/modules/aggregator.py: -------------------------------------------------------------------------------- 1 | from kratos import * 2 | from math import log 3 | 4 | 5 | class Aggregator(Generator): 6 | def __init__(self, 7 | word_width, 8 | mem_word_width): 9 | 10 | super().__init__("aggregator", debug=True) 11 | self.mem_word_width = mem_word_width 12 | # inputs 13 | self.clk = self.clock("clk") 14 | # active low asynchornous reset 15 | self.rst_n = self.reset("rst_n", 1) 16 | self.in_pixels = self.input("in_pixels", 17 | word_width) 18 | self._valid_in = self.input("valid_in", 1) 19 | self._align = self.input("align", 1) 20 | 21 | # outputs 22 | self._agg_out = self.output("agg_out", 23 | word_width, 24 | size=mem_word_width, 25 | packed=True, 26 | explicit_array=True) 27 | self._valid_out = self.output("valid_out", 1) 28 | self._next_full = self.output("next_full", 1) 29 | 30 | self.shift_reg = self.var("shift_reg", 31 | width=word_width, 32 | size=mem_word_width, 33 | explicit_array=True, 34 | packed=True) 35 | 36 | # local variables 37 | if (mem_word_width > 1): 38 | self._word_count = self.var("word_count", clog2(mem_word_width)) 39 | self.add_code(self.update_counter_valid) 40 | self.add_code(self.set_next_full) 41 | self.add_code(self.update_shift_reg) 42 | # add combinational blocks 43 | else: 44 | self.wire(self._valid_out, const(1, 1)) 45 | self.wire(self._next_full, self._valid_in) 46 | # only add the update counter/valid 47 | self.wire(self.shift_reg[0], self.in_pixels) 48 | 49 | self.add_code(self.output_data) 50 | 51 | @always_comb 52 | def set_next_full(self): 53 | self._next_full = (self._valid_in & (self._word_count == const(self.mem_word_width - 1, 54 | self._word_count.width))) | self._align 55 | 56 | # setting valid signal and word_count index 57 | @always_ff((posedge, "clk"), (negedge, "rst_n")) 58 | def update_counter_valid(self): 59 | if ~self.rst_n: 60 | self._valid_out = 0 61 | self._word_count = const(0, self._word_count.width) 62 | # no self._word_count in this case 63 | elif self._valid_in: 64 | if (self._word_count == const(self.mem_word_width - 1, self._word_count.width)) | self._align: 65 | self._valid_out = const(1, 1) 66 | self._word_count = const(0, self._word_count.width) 67 | else: 68 | self._valid_out = const(0, 1) 69 | self._word_count = self._word_count + const(1, clog2(mem_word_width)) 70 | else: 71 | self._valid_out = 0 72 | 73 | @always_ff((posedge, "clk")) 74 | def update_shift_reg(self): 75 | if (self._valid_in): 76 | self.shift_reg[self._word_count] = self.in_pixels 77 | 78 | @always_comb 79 | def output_data(self): 80 | self._agg_out = self.shift_reg 81 | 82 | 83 | if __name__ == "__main__": 84 | db_dut = Aggregator(word_width=16, 85 | mem_word_width=4) 86 | verilog(db_dut, filename="aggregator.sv") 87 | -------------------------------------------------------------------------------- /tests/test_reg_fifo.py: -------------------------------------------------------------------------------- 1 | from lake.models.reg_fifo_model import RegFIFOModel 2 | from lake.modules.reg_fifo import RegFIFO 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | import kratos as k 8 | import random as rand 9 | import pytest 10 | 11 | 12 | @pytest.mark.parametrize("width_mult", [1, 2]) 13 | def test_reg_fifo_basic(width_mult, 14 | data_width=16, 15 | depth=64): 16 | 17 | # Set up model... 18 | model_rf = RegFIFOModel(data_width=data_width, 19 | width_mult=width_mult, 20 | depth=depth) 21 | new_config = {} 22 | model_rf.set_config(new_config=new_config) 23 | ### 24 | 25 | # Set up dut... 26 | dut = RegFIFO(data_width=data_width, 27 | width_mult=width_mult, 28 | depth=depth) 29 | 30 | magma_dut = k.util.to_magma(dut, flatten_array=True, 31 | check_flip_flop_always_ff=False) 32 | tester = fault.Tester(magma_dut, magma_dut.clk) 33 | ### 34 | 35 | for key, value in new_config.items(): 36 | setattr(tester.circuit, key, value) 37 | 38 | # initial reset 39 | tester.circuit.clk = 0 40 | tester.circuit.rst_n = 0 41 | tester.step(2) 42 | tester.circuit.rst_n = 1 43 | tester.step(2) 44 | 45 | rand.seed(0) 46 | 47 | data_in = [] 48 | for i in range(width_mult): 49 | data_in.append(0) 50 | 51 | num_iters = 1000 52 | for z in range(num_iters): 53 | # Generate new input 54 | push = rand.randint(0, 1) 55 | pop = rand.randint(0, 1) 56 | empty = model_rf.get_empty() 57 | full = model_rf.get_full() 58 | for i in range(width_mult): 59 | data_in[i] = rand.randint(0, 2 ** data_width - 1) 60 | 61 | tester.circuit.empty.expect(empty) 62 | tester.circuit.full.expect(full) 63 | 64 | mem_valid_data = rand.randint(0, 1) 65 | # tester.circuit.mem_valid_data = mem_valid_data 66 | 67 | (model_out, model_val, model_empty, model_full, model_mem_valid) = \ 68 | model_rf.interact(push, pop, data_in, mem_valid_data) 69 | 70 | tester.circuit.push = push 71 | tester.circuit.pop = pop 72 | if width_mult == 1: 73 | tester.circuit.data_in = data_in[0] 74 | else: 75 | for i in range(width_mult): 76 | setattr(tester.circuit, f"data_in_{i}", data_in[i]) 77 | 78 | tester.eval() 79 | tester.step(2) 80 | 81 | tester.circuit.valid.expect(model_val) 82 | if model_val: 83 | # I'm not sure what this is for? 84 | # tester.circuit.mem_valid_data_out.expect(model_mem_valid) 85 | if width_mult == 1: 86 | tester.circuit.data_out.expect(model_out[0]) 87 | else: 88 | for i in range(width_mult): 89 | getattr(tester.circuit, f"data_out_{i}").expect(model_out[i]) 90 | 91 | with tempfile.TemporaryDirectory() as tempdir: 92 | tester.compile_and_run(target="verilator", 93 | directory=tempdir, 94 | magma_output="verilog", 95 | flags=["-Wno-fatal"]) 96 | 97 | 98 | if __name__ == "__main__": 99 | test_reg_fifo_basic(width_mult=1, 100 | data_width=16, 101 | depth=64) 102 | -------------------------------------------------------------------------------- /tests/test_demux_reads.py: -------------------------------------------------------------------------------- 1 | from lake.models.demux_reads_model import DemuxReadsModel 2 | from lake.modules.demux_reads import DemuxReads 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | from lake.utils.util import * 8 | import kratos as k 9 | import random as rand 10 | 11 | 12 | def test_demux_reads_basic(fetch_width=32, 13 | data_width=16, 14 | banks=2, 15 | int_out_ports=2): 16 | 17 | fw_int = int(fetch_width / data_width) 18 | 19 | # Set up model... 20 | model_dr = DemuxReadsModel(fetch_width=fetch_width, 21 | data_width=data_width, 22 | banks=banks, 23 | int_out_ports=int_out_ports) 24 | 25 | new_config = {} 26 | model_dr.set_config(new_config=new_config) 27 | ### 28 | 29 | # Set up dut... 30 | dut = DemuxReads(fetch_width=fetch_width, 31 | data_width=data_width, 32 | banks=banks, 33 | int_out_ports=int_out_ports) 34 | 35 | magma_dut = k.util.to_magma(dut, flatten_array=True, 36 | check_flip_flop_always_ff=False) 37 | tester = fault.Tester(magma_dut, magma_dut.clk) 38 | ### 39 | 40 | for key, value in new_config.items(): 41 | setattr(tester.circuit, key, value) 42 | 43 | # initial reset 44 | tester.circuit.clk = 0 45 | tester.circuit.rst_n = 0 46 | tester.step(2) 47 | tester.circuit.rst_n = 1 48 | tester.step(2) 49 | 50 | rand.seed(0) 51 | 52 | data_in = [] 53 | for i in range(banks): 54 | row = [] 55 | for j in range(fw_int): 56 | row.append(0) 57 | data_in.append(row) 58 | 59 | valid_in = [0] * banks 60 | mem_valid_data = [0] * banks 61 | port_in = [0] * banks 62 | port_in_hw = [0] * banks 63 | for z in range(1000): 64 | # Generate new input 65 | for i in range(banks): 66 | valid_in[i] = rand.randint(0, 1) 67 | mem_valid_data[i] = rand.randint(0, 1) 68 | port_in[i] = rand.randint(0, int_out_ports - 1) 69 | # One-Hot encoding in hardware 70 | port_in_hw[i] = 1 << port_in[i] 71 | for j in range(fw_int): 72 | data_in[i][j] = rand.randint(0, 2 ** data_width - 1) 73 | 74 | model_dat, model_val, model_mem_valid_data_out = \ 75 | model_dr.interact(data_in, valid_in, port_in_hw, mem_valid_data) 76 | 77 | for i in range(banks): 78 | tester.circuit.valid_in[i] = valid_in[i] 79 | tester.circuit.mem_valid_data[i] = mem_valid_data[i] 80 | setattr(tester.circuit, f"port_in_{i}", port_in_hw[i]) 81 | for j in range(fw_int): 82 | setattr(tester.circuit, f"data_in_{i}_{j}", data_in[i][j]) 83 | 84 | tester.eval() 85 | 86 | for i in range(int_out_ports): 87 | for j in range(fw_int): 88 | getattr(tester.circuit, f"data_out_{i}_{j}").expect(model_dat[i][j]) 89 | tester.circuit.valid_out[i].expect(model_val[i]) 90 | tester.circuit.mem_valid_data_out[i].expect(model_mem_valid_data_out[i]) 91 | 92 | tester.step(2) 93 | 94 | with tempfile.TemporaryDirectory() as tempdir: 95 | tester.compile_and_run(target="verilator", 96 | directory=tempdir, 97 | magma_output="verilog", 98 | flags=["-Wno-fatal"]) 99 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/reduce_tb.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ns 2 | `ifndef TX_NUM_GLB 3 | `define TX_NUM_GLB 1 4 | `endif 5 | 6 | module reduce_tb; 7 | 8 | reg clk; 9 | reg clk_en; 10 | reg rst_n; 11 | reg stall; 12 | reg flush; 13 | reg tile_en; 14 | reg [15:0] lvl; 15 | wire [63:0] cycle_count ; 16 | 17 | // wire for dut input & output 18 | wire [16:0] val_in; 19 | wire val_in_valid; 20 | wire val_in_ready; 21 | 22 | wire [16:0] val_out; 23 | wire val_out_valid; 24 | wire val_out_ready; 25 | 26 | wire [1:0] done; 27 | parameter NUM_CYCLES = 4000; 28 | 29 | // instruction 30 | reg [83:0] inst; 31 | reg [83:0] onyxpeintf_inst; 32 | reg [2:0] sparse_num_inputs; 33 | reg [2:0] num_inputs; 34 | 35 | reduce_pe_cluster #( 36 | 37 | ) dut ( 38 | .clk(clk), 39 | .clk_en(clk_en), 40 | .bit0(1'b0), 41 | .bit1(1'b0), 42 | .bit2(1'b0), 43 | // Configure pe to dense mode 44 | .pe_dense_mode(1'b1), 45 | // Configure pe to use internal connection with reduce 46 | .pe_in_external(1'b0), 47 | .reduce_data_in(val_in), 48 | .reduce_data_in_valid(val_in_valid), 49 | .reduce_data_out_ready(val_out_ready), 50 | .reduce_default_value(16'b0), 51 | .flush(flush), 52 | .rst_n(rst_n), 53 | .reduce_stop_lvl(16'b0), 54 | .tile_en(tile_en), 55 | .reduce_tile_en(tile_en), 56 | .reduce_data_in_ready(val_in_ready), 57 | .reduce_data_out(val_out), 58 | .reduce_data_out_valid(val_out_valid), 59 | .pe_onyxpeintf_inst(inst) 60 | ); 61 | 62 | tile_write #( 63 | .FILE_NAME("coord_in_0.txt"), 64 | .TX_NUM(`TX_NUM_GLB), 65 | .RAN_SHITF(0) 66 | ) val_in_inst ( 67 | .clk(clk), 68 | .rst_n(rst_n), 69 | .data(val_in), 70 | .ready(val_in_ready), 71 | .valid(val_in_valid), 72 | .done(done[0]), 73 | .flush(flush) 74 | ); 75 | 76 | tile_read #( 77 | .FILE_NAME("coord_out.txt"), 78 | .TX_NUM(`TX_NUM_GLB), 79 | .RAN_SHITF(1) 80 | ) val_out_inst ( 81 | .clk(clk), 82 | .rst_n(rst_n), 83 | .data(val_out), 84 | .ready(val_out_ready), 85 | .valid(val_out_valid), 86 | .done(done[1]), 87 | .flush(flush) 88 | ); 89 | 90 | 91 | integer start_record; 92 | integer clk_count; 93 | integer DONE_TOKEN = 17'h10100; 94 | 95 | // simulated clk signal, 10ns period 96 | initial begin 97 | start_record = 0; 98 | clk_count = 0; 99 | lvl = 0; 100 | 101 | clk = 0; 102 | clk_en = 1; 103 | rst_n = 0; 104 | tile_en = 1; 105 | flush = 0; 106 | 107 | #5 clk = 1; 108 | flush = 1; 109 | rst_n = 1; 110 | #5 clk = 0; 111 | flush = 0; 112 | 113 | onyxpeintf_inst = $value$plusargs("inst=%h", inst); 114 | sparse_num_inputs = $value$plusargs("num_inputs=%h", num_inputs); 115 | 116 | for(integer i = 0; i < NUM_CYCLES * 2; i = i + 1) begin 117 | #5 clk = ~clk; 118 | if (~start_record && clk && val_in_valid) begin 119 | start_record = 1; 120 | end 121 | if (clk && start_record && ~done[1]) begin 122 | clk_count += 1; 123 | end 124 | 125 | end 126 | $display("cycle count: %0d", clk_count); 127 | $finish; 128 | 129 | end 130 | 131 | endmodule 132 | -------------------------------------------------------------------------------- /tests/test_dsl_memtile.py: -------------------------------------------------------------------------------- 1 | import kratos as kts 2 | import fault 3 | import tempfile 4 | import pytest 5 | 6 | from lake.utils.sram_macro import SRAMMacroInfo 7 | # input and output data 8 | from lake.utils.parse_clkwork_csv import generate_data_lists 9 | # configurations 10 | from lake.utils.parse_clkwork_config import * 11 | from lake.utils.util import check_env 12 | from lake.dsl.dsl_examples.memtile import * 13 | 14 | 15 | def base_lake_tester(config_path, 16 | in_file_name, 17 | out_file_name, 18 | in_ports, 19 | out_ports, 20 | lt_dut, 21 | stencil_valid=False): 22 | 23 | configs = lt_dut.get_static_bitstream(config_path, in_file_name, out_file_name) 24 | 25 | magma_dut = kts.util.to_magma(lt_dut, 26 | flatten_array=True, 27 | check_multiple_driver=False, 28 | optimize_if=False, 29 | check_flip_flop_always_ff=False) 30 | 31 | tester = fault.Tester(magma_dut, magma_dut.clk) 32 | configs_list = [] 33 | return lt_dut, configs, configs_list, magma_dut, tester 34 | 35 | 36 | def gen_test_lake(config_path, 37 | stream_path, 38 | lt_dut, 39 | in_file_name="input", 40 | out_file_name="output", 41 | in_ports=2, 42 | out_ports=2): 43 | 44 | lt_dut, configs, configs_list, magma_dut, tester = \ 45 | base_lake_tester(config_path, 46 | in_file_name, 47 | out_file_name, 48 | in_ports, 49 | out_ports, 50 | lt_dut) 51 | 52 | tester.circuit.clk_en = 1 53 | tester.circuit.clk = 0 54 | tester.circuit.rst_n = 0 55 | tester.step(2) 56 | tester.circuit.rst_n = 1 57 | tester.step(2) 58 | tester.eval() 59 | 60 | # args are input ports, output ports 61 | in_data, out_data, valids = generate_data_lists(stream_path, in_ports, out_ports) 62 | 63 | for (f1, f2) in configs: 64 | setattr(tester.circuit, f1, f2) 65 | 66 | for i in range(len(out_data[0])): 67 | for j in range(len(in_data)): 68 | if i < len(in_data[j]): 69 | setattr(tester.circuit, f"data_in_{j}", in_data[j][i]) 70 | 71 | tester.eval() 72 | 73 | for j in range(len(out_data)): 74 | if i < len(out_data[j]): 75 | if len(valids) != 0 and valids[i] == 1: 76 | getattr(tester.circuit, f"data_out_{j}").expect(out_data[j][i]) 77 | if len(valids) == 0: 78 | getattr(tester.circuit, f"data_out_{j}").expect(out_data[j][i]) 79 | 80 | tester.step(2) 81 | 82 | with tempfile.TemporaryDirectory() as tempdir: 83 | tester.compile_and_run(target="verilator", 84 | directory=tempdir, 85 | flags=["-Wno-fatal"]) 86 | 87 | 88 | @pytest.mark.skip 89 | def test_conv_3_3(): 90 | lc, ls = check_env() 91 | config_path = lc + "conv_3_3_recipe_dsl" 92 | stream_path = ls + "conv_3_3_recipe/buf_inst_input_10_to_buf_inst_output_3_ubuf_0_top_SMT.csv" 93 | 94 | lt_dut = tile.test_magma_lake() 95 | 96 | gen_test_lake(config_path=config_path, 97 | stream_path=stream_path, 98 | lt_dut=lt_dut) 99 | 100 | 101 | if __name__ == "__main__": 102 | test_conv_3_3() 103 | -------------------------------------------------------------------------------- /tests/test_chain.py: -------------------------------------------------------------------------------- 1 | from lake.models.chain_model import ChainModel 2 | from lake.modules.chain import Chain 3 | import magma as m 4 | from magma import * 5 | import fault 6 | import tempfile 7 | import kratos as k 8 | import random as rand 9 | import pytest 10 | 11 | 12 | @pytest.mark.skip 13 | def test_chain_module(data_width=16, 14 | interconnect_output_ports=3, 15 | chain_idx_bits=2, 16 | enable_chain_output=0, 17 | chain_idx_output=0): 18 | 19 | model_chain = ChainModel(data_width=data_width, 20 | interconnect_output_ports=interconnect_output_ports, 21 | chain_idx_bits=chain_idx_bits, 22 | enable_chain_output=0, 23 | chain_idx_output=0) 24 | 25 | dut = Chain(data_width=data_width, 26 | interconnect_output_ports=interconnect_output_ports, 27 | chain_idx_bits=chain_idx_bits) 28 | 29 | magma_dut = k.util.to_magma(dut, flatten_array=True, check_flip_flop_always_ff=False) 30 | tester = fault.Tester(magma_dut, magma_dut.clk) 31 | 32 | tester.circuit.clk = 0 33 | tester.circuit.clk_en = 1 34 | tester.circuit.rst_n = 1 35 | tester.step(2) 36 | tester.circuit.rst_n = 0 37 | tester.step(2) 38 | tester.circuit.rst_n = 1 39 | 40 | # configuration registers 41 | new_config = {} 42 | new_config["enable_chain_output"] = 0 43 | new_config["chain_idx_output"] = 0 44 | 45 | for key, value in new_config.items(): 46 | setattr(tester.circuit, key, value) 47 | 48 | rand.seed(0) 49 | 50 | num_iters = 300 51 | for i in range(num_iters): 52 | curr_tile_valid_out = [] 53 | curr_tile_data_out = [] 54 | chain_valid_in = [] 55 | chain_data_in = [] 56 | for j in range(interconnect_output_ports): 57 | curr_tile_valid_out.append(rand.randint(0, 1)) 58 | curr_tile_data_out.append(rand.randint(0, 2**data_width - 1)) 59 | chain_valid_in.append(rand.randint(0, 1)) 60 | chain_data_in.append(rand.randint(0, 2**data_width - 1)) 61 | 62 | for j in range(interconnect_output_ports): 63 | setattr(tester.circuit, f"curr_tile_valid_out_{j}", curr_tile_valid_out[j]) 64 | setattr(tester.circuit, f"curr_tile_data_out_{j}", curr_tile_data_out[j]) 65 | setattr(tester.circuit, f"chain_valid_in_{j}", chain_valid_in[j]) 66 | setattr(tester.circuit, f"chain_data_in_{j}", chain_data_in[j]) 67 | 68 | model_cdo, model_cvo, model_dot, model_vot = \ 69 | model_chain.interact(curr_tile_valid_out, curr_tile_data_out, 70 | chain_valid_in, chain_data_in) 71 | 72 | tester.eval() 73 | 74 | # for j in range(interconnect_output_ports): 75 | # getattr(tester.circuit, f"chain_data_out_{j}").expect(model_cdo[j]) 76 | # getattr(tester.circuit, f"chain_valid_out_{j}").expect(model_cvo[j]) 77 | # getattr(tester.circuit, f"data_out_tile_{j}").expect(model_dot[j]) 78 | # getattr(tester.circuit, f"valid_out_tile_{j}").expect(model_vot[j]) 79 | 80 | tester.step(2) 81 | 82 | with tempfile.TemporaryDirectory() as tempdir: 83 | tester.compile_and_run(target="verilator", 84 | directory=tempdir, 85 | magma_output="verilog", 86 | flags=["-Wno-fatal"]) 87 | 88 | 89 | if __name__ == "__main__": 90 | test_chain_module(data_width=16, 91 | interconnect_output_ports=3, 92 | chain_idx_bits=2) 93 | -------------------------------------------------------------------------------- /lake/models/agg_buff_model.py: -------------------------------------------------------------------------------- 1 | from lake.models.model import Model 2 | from lake.models.agg_model import AggModel 3 | 4 | 5 | class AggBuffModel(Model): 6 | 7 | def __init__(self, 8 | agg_height, 9 | data_width, 10 | mem_width, 11 | max_agg_schedule): 12 | 13 | self.agg_height = agg_height 14 | self.data_width = data_width 15 | self.mem_width = mem_width 16 | self.max_agg_sched = max_agg_schedule 17 | 18 | self.config = {} 19 | self.config['in_period'] = 0 20 | self.config['out_period'] = 0 21 | 22 | self.in_sched_ptr = 0 23 | self.out_sched_ptr = 0 24 | 25 | for i in range(self.max_agg_sched): 26 | self.config[f"in_sched_{i}"] = 0 27 | self.config[f"out_sched_{i}"] = 0 28 | 29 | self.aggs = [] 30 | for i in range(self.agg_height): 31 | self.aggs.append(AggModel(int(self.mem_width / self.data_width))) 32 | self.aggs[i].set_config() 33 | 34 | def set_config(self, new_config): 35 | for key, config_val in new_config.items(): 36 | if key not in self.config: 37 | AssertionError("Gave bad config...") 38 | else: 39 | self.config[key] = config_val 40 | self.in_sched_ptr = 0 41 | self.out_sched_ptr = 0 42 | 43 | def insert(self, in_data, valid): 44 | to_insert = self.aggs[self.config[f"in_sched_{self.in_sched_ptr}"]] 45 | ag_valid = to_insert.insert(in_data, valid) 46 | 47 | if (ag_valid): 48 | self.in_sched_ptr += 1 49 | if (self.in_sched_ptr >= self.config['in_period']): 50 | self.in_sched_ptr = 0 51 | 52 | def get_valid_out(self): 53 | valid_check_agg = self.aggs[self.config[f"out_sched_{self.out_sched_ptr}"]] 54 | return valid_check_agg.get_valid_out() 55 | 56 | def get_item(self): 57 | valid_check_agg = self.aggs[self.config[f"out_sched_{self.out_sched_ptr}"]] 58 | self.out_sched_ptr += 1 59 | if (self.out_sched_ptr >= self.config['out_period']): 60 | self.out_sched_ptr = 0 61 | return valid_check_agg.get_data_out() 62 | 63 | def interact(self, in_data, valid, write_act, align): 64 | ''' 65 | Returns (data_out, valid_out) 66 | ''' 67 | in_sched_curr = self.in_sched_ptr 68 | out_sched_curr = self.out_sched_ptr 69 | 70 | agg_dat = [] 71 | agg_valid = [] 72 | agg_nf = [] 73 | for i in range(self.agg_height): 74 | # Interact with child 75 | if i == self.config[f"in_sched_{in_sched_curr}"]: 76 | (t_ag_d, t_ag_v, t_nf) = self.aggs[i].interact(in_data, valid, align) 77 | else: 78 | (t_ag_d, t_ag_v, t_nf) = self.aggs[i].interact(0, 0, 0) 79 | agg_dat.append(t_ag_d) 80 | agg_valid.append(t_ag_v) 81 | agg_nf.append(t_nf) 82 | 83 | ret_data = agg_dat[self.config[f"out_sched_{out_sched_curr}"]] 84 | ret_valid = agg_valid[self.config[f"out_sched_{out_sched_curr}"]] 85 | 86 | next_full = agg_nf[self.config[f"in_sched_{in_sched_curr}"]] 87 | 88 | if (next_full == 1): 89 | self.in_sched_ptr += 1 90 | if (self.in_sched_ptr >= self.config['in_period']): 91 | self.in_sched_ptr = 0 92 | 93 | # Would be write_act 94 | if (ret_valid == 1): 95 | self.out_sched_ptr += 1 96 | if (self.out_sched_ptr >= self.config['out_period']): 97 | self.out_sched_ptr = 0 98 | 99 | return (ret_data, ret_valid) 100 | 101 | def peek(self): 102 | pass 103 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/locator_tb.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ns 2 | `ifndef TX_NUM_GLB 3 | `define TX_NUM_GLB 1 4 | `endif 5 | `ifndef L_LEVEL 6 | `define L_LEVEL 1 7 | `endif 8 | `ifndef DIM 9 | `define DIM 10 10 | `endif 11 | 12 | module locator_tb; 13 | 14 | reg clk; 15 | reg clk_en; 16 | reg rst_n; 17 | reg stall; 18 | reg flush; 19 | reg tile_en; 20 | reg [4:0] locate_dim_size; 21 | reg [15:0] locate_lvl; 22 | wire [63:0] cycle_count ; 23 | 24 | // wire for dut input & output 25 | wire [16:0] coord_in_0; 26 | wire coord_in_0_valid; 27 | wire coord_in_0_ready; 28 | wire [16:0] coord_in_1; 29 | wire coord_in_1_valid; 30 | wire coord_in_1_ready; 31 | wire [16:0] addr_out; 32 | wire addr_out_valid; 33 | wire addr_out_ready; 34 | 35 | wire [6:0] done; 36 | parameter NUM_CYCLES = 4000; 37 | 38 | locator #( 39 | 40 | ) dut ( 41 | .clk(clk), 42 | .clk_en(clk_en), 43 | .coord_in_0(coord_in_0), 44 | .coord_in_0_valid(coord_in_0_valid), 45 | .coord_in_0_ready(coord_in_0_ready), 46 | .coord_in_1(coord_in_1), 47 | .coord_in_1_valid(coord_in_1_valid), 48 | .coord_in_1_ready(coord_in_1_ready), 49 | .tile_en(tile_en), 50 | .addr_out(addr_out), 51 | .addr_out_valid(addr_out_valid), 52 | .addr_out_ready(addr_out_ready), 53 | .rst_n(rst_n), 54 | .flush(flush), 55 | .locate_dim_size(locate_dim_size), 56 | .locate_lvl(locate_lvl) 57 | ); 58 | 59 | tile_write #( 60 | .FILE_NAME("coord_in_0.txt"), 61 | .TX_NUM(`TX_NUM_GLB), 62 | .RAN_SHITF(0) 63 | ) coord_in_0_inst ( 64 | .clk(clk), 65 | .rst_n(rst_n), 66 | .data(coord_in_0), 67 | .ready(coord_in_0_ready), 68 | .valid(coord_in_0_valid), 69 | .done(done[0]), 70 | .flush(flush) 71 | ); 72 | 73 | tile_write #( 74 | .FILE_NAME("coord_in_1.txt"), 75 | .TX_NUM(`TX_NUM_GLB), 76 | .RAN_SHITF(1) 77 | ) coord_in_1_inst ( 78 | .clk(clk), 79 | .rst_n(rst_n), 80 | .data(coord_in_1), 81 | .ready(coord_in_1_ready), 82 | .valid(coord_in_1_valid), 83 | .done(done[1]), 84 | .flush(flush) 85 | ); 86 | 87 | tile_read #( 88 | .FILE_NAME("pos_out_0.txt"), 89 | .TX_NUM(`TX_NUM_GLB), 90 | .RAN_SHITF(1) 91 | ) pos_out_0_inst ( 92 | .clk(clk), 93 | .rst_n(rst_n), 94 | .data(addr_out), 95 | .ready(addr_out_ready), 96 | .valid(addr_out_valid), 97 | .done(done[5]), 98 | .flush(flush) 99 | ); 100 | 101 | integer start_record; 102 | integer clk_count; 103 | integer DONE_TOKEN = 17'h10100; 104 | 105 | // simulated clk signal, 10ns period 106 | initial begin 107 | start_record = 0; 108 | clk_count = 0; 109 | 110 | clk = 0; 111 | locate_dim_size = `DIM; 112 | locate_lvl = `L_LEVEL; 113 | clk_en = 1; 114 | rst_n = 0; 115 | tile_en = 1; 116 | flush = 0; 117 | 118 | #5 clk = 1; 119 | flush = 1; 120 | rst_n = 1; 121 | #5 clk = 0; 122 | flush = 0; 123 | 124 | for(integer i = 0; i < NUM_CYCLES * 2; i = i + 1) begin 125 | #5 clk = ~clk; 126 | if (~start_record && clk && (coord_in_0_valid | coord_in_1_valid)) begin 127 | start_record = 1; 128 | end 129 | if (clk && start_record && ~done[5]) begin 130 | clk_count += 1; 131 | end 132 | 133 | end 134 | $display("cycle count: %0d", clk_count); 135 | $finish; 136 | 137 | end 138 | 139 | endmodule 140 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/glb_stream_read.sv: -------------------------------------------------------------------------------- 1 | module glb_stream_read #( 2 | parameter NUM_BLOCKS = 1, 3 | parameter FILE_NAME = "dst.txt", 4 | parameter LOCATION = "X00_Y00", 5 | parameter TX_NUM = 1, 6 | parameter RAN_SHITF = 0 7 | ) 8 | ( 9 | input logic clk, 10 | input logic rst_n, 11 | input logic [16:0] data, 12 | output logic ready, 13 | input logic valid, 14 | output logic done, 15 | input logic flush, 16 | input logic seg_mode 17 | ); 18 | 19 | logic [16:0] local_mem_0 [0:4095]; 20 | integer num_rx; 21 | integer size_0; 22 | 23 | string F1_PARGS; 24 | string NUM_BLOCKS_PARGS; 25 | integer NUM_BLOCKS_USE; 26 | string F1_USE; 27 | string ENABLED_PARGS; 28 | integer ENABLED; 29 | integer done_count; 30 | integer length_count; 31 | integer delay_count; 32 | integer ADD_DELAY; 33 | integer mask; 34 | integer stream_count; 35 | integer set_inst; 36 | 37 | initial begin 38 | 39 | ENABLED = 1; 40 | // ENABLED_PARGS = $sformatf("%s_ENABLED=%%d", LOCATION); 41 | // $value$plusargs(ENABLED_PARGS, ENABLED); 42 | 43 | num_rx = 0; 44 | ready = 0; 45 | size_0 = 0; 46 | done = 0; 47 | done_count = TX_NUM + 1; // extra 1 for the initialization 48 | length_count = 0; 49 | ADD_DELAY = 0; 50 | set_inst = 0; 51 | mask = 32'd3 << RAN_SHITF; 52 | 53 | if (ENABLED == 1) begin 54 | 55 | $display("%s is enabled...", LOCATION); 56 | 57 | F1_PARGS = $sformatf("%s_F1=%%s", LOCATION); 58 | NUM_BLOCKS_PARGS = $sformatf("%s_NUM_BLOCKS=%%d", LOCATION); 59 | 60 | $display("Location: %s", LOCATION); 61 | F1_USE = FILE_NAME; 62 | $display("F1_USE before: %s", F1_USE); 63 | $value$plusargs(F1_PARGS, F1_USE); 64 | $display("F1_USE after: %s", F1_USE); 65 | 66 | @(posedge flush); 67 | @(negedge flush); 68 | 69 | @(posedge clk); 70 | @(posedge clk); 71 | @(posedge clk); 72 | 73 | while(done_count != 1 | length_count != 0) begin 74 | @(posedge clk); 75 | #1; 76 | // ready = $urandom(); 77 | 78 | ready = 0; 79 | delay_count = $urandom & mask; 80 | while (delay_count > 0 & ADD_DELAY) begin 81 | @(posedge clk); 82 | #1; 83 | delay_count--; 84 | end 85 | 86 | ready = 1; 87 | if(ready == 1 && valid == 1) begin 88 | local_mem_0[num_rx] = data; 89 | num_rx = num_rx + 1; 90 | if (set_inst == 0) begin 91 | set_inst = 1; 92 | stream_count = seg_mode ? 2 : 1; 93 | end 94 | else begin 95 | if (stream_count != 0 & length_count == 0) begin 96 | length_count = data + 1; 97 | stream_count = stream_count - 1; 98 | if (length_count == 1) begin 99 | done_count = done_count - 1; 100 | set_inst = 0; 101 | end 102 | end 103 | else if (stream_count == 0 & length_count == 1) begin 104 | done_count = done_count - 1; 105 | set_inst = 0; 106 | end 107 | 108 | length_count = length_count - 1; 109 | end 110 | end 111 | end 112 | @(posedge clk); 113 | #1; 114 | assert(valid == 0) else $error("Valid signal fails to end"); 115 | ready = 0; 116 | $writememh(F1_USE, local_mem_0); 117 | 118 | end 119 | 120 | @(posedge clk); 121 | ready = 0; 122 | done = 1; 123 | 124 | end 125 | 126 | endmodule 127 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/repeat_tb.sv: -------------------------------------------------------------------------------- 1 | `timescale 1ns/1ns 2 | `ifndef TX_NUM_GLB 3 | `define TX_NUM_GLB 1 4 | `endif 5 | 6 | module repeat_tb; 7 | 8 | reg clk; 9 | reg clk_en; 10 | reg rst_n; 11 | reg stall; 12 | reg flush; 13 | reg tile_en; 14 | reg root; 15 | reg spacc_mode; 16 | reg [15:0] stop_lvl; 17 | wire [63:0] cycle_count ; 18 | 19 | // wire for dut input & output 20 | wire [16:0] proc_data_in; 21 | wire proc_data_in_valid; 22 | wire proc_data_in_ready; 23 | wire [16:0] repsig_data_in; 24 | wire repsig_data_in_valid; 25 | wire repsig_data_in_ready; 26 | wire [16:0] ref_data_out; 27 | wire ref_data_out_valid; 28 | wire ref_data_out_ready; 29 | 30 | wire [2:0] done; 31 | parameter NUM_CYCLES = 8000; 32 | 33 | Repeat #( 34 | 35 | ) dut ( 36 | .clk(clk), 37 | .clk_en(clk_en), 38 | .proc_data_in(proc_data_in), 39 | .proc_data_in_valid(proc_data_in_valid), 40 | .proc_data_in_ready(proc_data_in_ready), 41 | .repsig_data_in(repsig_data_in), 42 | .repsig_data_in_valid(repsig_data_in_valid), 43 | .repsig_data_in_ready(repsig_data_in_ready), 44 | .tile_en(tile_en), 45 | .ref_data_out(ref_data_out), 46 | .ref_data_out_valid(ref_data_out_valid), 47 | .ref_data_out_ready(ref_data_out_ready), 48 | .rst_n(rst_n), 49 | .flush(flush), 50 | .tile_en(tile_en), 51 | .root(root), 52 | .spacc_mode(spacc_mode), 53 | .stop_lvl(stop_lvl) 54 | ); 55 | 56 | 57 | tile_write #( 58 | .FILE_NAME("pos_in_0.txt"), 59 | .TX_NUM(`TX_NUM_GLB), 60 | .RAN_SHITF(0) 61 | ) proc_data_in_inst ( 62 | .clk(clk), 63 | .rst_n(rst_n), 64 | .data(proc_data_in), 65 | .ready(proc_data_in_ready), 66 | .valid(proc_data_in_valid), 67 | .done(done[0]), 68 | .flush(flush) 69 | ); 70 | 71 | tile_write #( 72 | .FILE_NAME("pos_in_1.txt"), 73 | .TX_NUM(`TX_NUM_GLB), 74 | .RAN_SHITF(1) 75 | ) repsig_data_in_inst ( 76 | .clk(clk), 77 | .rst_n(rst_n), 78 | .data(repsig_data_in), 79 | .ready(repsig_data_in_ready), 80 | .valid(repsig_data_in_valid), 81 | .done(done[1]), 82 | .flush(flush) 83 | ); 84 | 85 | 86 | tile_read #( 87 | .FILE_NAME("pos_out_0.txt"), 88 | .TX_NUM(`TX_NUM_GLB), 89 | .RAN_SHITF(2) 90 | ) ref_data_out_inst ( 91 | .clk(clk), 92 | .rst_n(rst_n), 93 | .data(ref_data_out), 94 | .ready(ref_data_out_ready), 95 | .valid(ref_data_out_valid), 96 | .done(done[2]), 97 | .flush(flush) 98 | ); 99 | 100 | integer start_record; 101 | integer clk_count; 102 | integer DONE_TOKEN = 17'h10100; 103 | 104 | // simulated clk signal, 10ns period 105 | initial begin 106 | start_record = 0; 107 | clk_count = 0; 108 | 109 | clk = 0; 110 | stop_lvl = 0; 111 | spacc_mode = 0; 112 | root = 0; 113 | clk_en = 1; 114 | rst_n = 0; 115 | tile_en = 1; 116 | flush = 0; 117 | 118 | #5 clk = 1; 119 | flush = 1; 120 | rst_n = 1; 121 | #5 clk = 0; 122 | flush = 0; 123 | 124 | for(integer i = 0; i < NUM_CYCLES * 2; i = i + 1) begin 125 | #5 clk = ~clk; 126 | if (~start_record && clk && (proc_data_in_valid | repsig_data_in_valid)) begin 127 | start_record = 1; 128 | end 129 | if (clk && start_record && ~done[2]) begin 130 | clk_count += 1; 131 | end 132 | 133 | end 134 | $display("cycle count: %0d", clk_count); 135 | $finish; 136 | 137 | end 138 | 139 | endmodule 140 | -------------------------------------------------------------------------------- /SPARSE_UNIT_TEST/glb_stream_write.sv: -------------------------------------------------------------------------------- 1 | module glb_stream_write #( 2 | parameter TX_SIZE = 2048, 3 | parameter FILE_NAME = "src.txt", 4 | parameter LOCATION = "X00_Y00", 5 | parameter TX_NUM = 1, 6 | parameter RAN_SHITF = 0 7 | ) 8 | ( 9 | input logic clk, 10 | input logic rst_n, 11 | output logic [16:0] data, 12 | input logic ready, 13 | output logic valid, 14 | output logic done, 15 | input logic flush, 16 | input logic seg_mode 17 | ); 18 | 19 | logic [16:0] local_mem [0:4095]; 20 | integer num_tx; 21 | 22 | string TX_SIZE_PARGS; 23 | string FILE_NAME_PARGS; 24 | integer TX_SIZE_USE; 25 | string FILE_NAME_USE; 26 | string ENABLED_PARGS; 27 | integer ENABLED; 28 | integer DELAY; 29 | integer ADD_DELAY; 30 | integer done_count; 31 | integer length_count; 32 | integer mask; 33 | integer set_inst; 34 | integer stream_count; 35 | 36 | initial begin 37 | 38 | num_tx = 0; 39 | valid = 0; 40 | done = 0; 41 | data = 0; 42 | valid = 0; 43 | ENABLED = 1; 44 | ADD_DELAY = 0; 45 | done_count = TX_NUM; 46 | $display("%d", done_count); 47 | length_count = 0; 48 | set_inst = 0; 49 | mask = 32'd3 << RAN_SHITF; 50 | 51 | // ENABLED_PARGS = $sformatf("%s_ENABLED=%%d", LOCATION); 52 | // $value$plusargs(ENABLED_PARGS, ENABLED); 53 | 54 | if (ENABLED == 1) begin 55 | 56 | $display("%s is enabled...", LOCATION); 57 | 58 | FILE_NAME_PARGS = $sformatf("%s_FILE_NAME=%%s", LOCATION); 59 | FILE_NAME_USE = FILE_NAME; 60 | $value$plusargs(FILE_NAME_PARGS, FILE_NAME_USE); 61 | // string file_str; 62 | // file_str = $sformatf("/home/max/Documents/SPARSE/garnet/generic_memory_%d.txt", FILE_NO); 63 | $readmemh(FILE_NAME_USE, local_mem); 64 | 65 | TX_SIZE_PARGS = $sformatf("%s_TX_SIZE=%%d", LOCATION); 66 | TX_SIZE_USE = TX_SIZE; 67 | $value$plusargs(TX_SIZE_PARGS, TX_SIZE_USE); 68 | 69 | num_tx = 0; 70 | valid = 0; 71 | done = 0; 72 | data = 0; 73 | 74 | @(posedge flush); 75 | @(negedge flush); 76 | 77 | @(posedge clk); 78 | @(posedge clk); 79 | @(posedge clk); 80 | 81 | // Make as many transfers from the memory as needed. 82 | while(num_tx < TX_SIZE_USE && done_count > 0) begin 83 | @(posedge clk); 84 | #1; //TODO: debug the issue with the 1 unit time delay with line 90 85 | 86 | valid = 0; 87 | DELAY = $urandom & mask; 88 | while (DELAY > 0 & ADD_DELAY) begin 89 | @(posedge clk); 90 | // #1; 91 | DELAY--; 92 | end 93 | 94 | data = local_mem[num_tx]; 95 | valid = 1; 96 | #1; 97 | if(ready == 1 && valid == 1) begin 98 | num_tx = num_tx + 1; 99 | 100 | if (set_inst == 0) begin 101 | set_inst = 1; 102 | length_count = local_mem[num_tx] + 1; 103 | stream_count = seg_mode ? 2 : 1; 104 | end 105 | else begin 106 | length_count = length_count - 1; 107 | if (length_count == 0) begin 108 | stream_count = stream_count - 1; 109 | if (stream_count == 0) begin 110 | done_count = done_count - 1; 111 | set_inst = 0; 112 | end 113 | else begin 114 | length_count = local_mem[num_tx] + 1; // potential segfault 115 | end 116 | end 117 | end 118 | end 119 | end 120 | end 121 | 122 | @(posedge clk); 123 | done = 1; 124 | valid = 0; 125 | 126 | end 127 | 128 | endmodule 129 | -------------------------------------------------------------------------------- /tests/test_tba.py: -------------------------------------------------------------------------------- 1 | from lake.models.tba_model import TBAModel 2 | from lake.modules.transpose_buffer_aggregation import TransposeBufferAggregation 3 | from lake.passes.passes import lift_config_reg 4 | import magma as m 5 | from magma import * 6 | import fault 7 | import tempfile 8 | import kratos as k 9 | import random as rand 10 | import pytest 11 | 12 | 13 | def test_tba(word_width=16, 14 | fetch_width=4, 15 | num_tb=1, 16 | tb_height=1, 17 | max_range=5, 18 | max_range_inner=5): 19 | 20 | model_tba = TBAModel(word_width, 21 | fetch_width, 22 | num_tb, 23 | tb_height, 24 | max_range, 25 | max_range_inner) 26 | 27 | new_config = {} 28 | new_config["range_outer"] = 5 29 | new_config["range_inner"] = 3 30 | new_config["stride"] = 2 31 | new_config["indices"] = [0, 1, 2] 32 | new_config["dimensionality"] = 2 33 | new_config["tb_height"] = 1 34 | new_config["starting_addr"] = 0 35 | 36 | model_tba.set_config(new_config=new_config) 37 | 38 | dut = TransposeBufferAggregation(word_width, 39 | fetch_width, 40 | num_tb, 41 | tb_height, 42 | max_range, 43 | max_range_inner, 44 | max_stride=5, 45 | tb_iterator_support=2) 46 | 47 | lift_config_reg(dut.internal_generator) 48 | 49 | magma_dut = k.util.to_magma(dut, flatten_array=True, check_flip_flop_always_ff=False) 50 | tester = fault.Tester(magma_dut, magma_dut.clk) 51 | 52 | # configuration registers 53 | tester.circuit.tb_0_indices_0 = 0 54 | tester.circuit.tb_0_indices_1 = 1 55 | tester.circuit.tb_0_indices_2 = 2 56 | 57 | tester.circuit.tb_0_range_outer = 5 58 | tester.circuit.tb_0_range_inner = 3 59 | tester.circuit.tb_0_stride = 2 60 | tester.circuit.tb_0_dimensionality = 2 61 | tester.circuit.tb_0_tb_height = 1 62 | tester.circuit.tb_0_starting_addr = 0 63 | 64 | tester.circuit.clk = 0 65 | tester.circuit.rst_n = 1 66 | tester.step(2) 67 | tester.circuit.rst_n = 0 68 | tester.step(2) 69 | tester.circuit.tba_ren = 1 70 | tester.circuit.rst_n = 1 71 | 72 | rand.seed(0) 73 | 74 | num_iters = 100 75 | for i in range(num_iters): 76 | 77 | data = [] 78 | for j in range(fetch_width): 79 | data.append(rand.randint(0, 2**word_width - 1)) 80 | 81 | for j in range(fetch_width): 82 | setattr(tester.circuit, f"SRAM_to_tb_data_{j}", data[j]) 83 | 84 | valid_data = rand.randint(0, 1) 85 | tester.circuit.valid_data = valid_data 86 | 87 | mem_valid_data = rand.randint(0, 1) 88 | tester.circuit.mem_valid_data = mem_valid_data 89 | 90 | tb_index_for_data = 0 91 | tester.circuit.tb_index_for_data = tb_index_for_data 92 | 93 | ack_in = valid_data 94 | tester.circuit.ack_in = ack_in 95 | 96 | model_data, model_valid = \ 97 | model_tba.tba_main(data, valid_data, ack_in, tb_index_for_data, 1, mem_valid_data) 98 | 99 | tester.eval() 100 | tester.circuit.tb_to_interconnect_valid.expect(model_valid) 101 | if model_valid: 102 | tester.circuit.tb_to_interconnect_data.expect(model_data[0]) 103 | 104 | tester.step(2) 105 | 106 | with tempfile.TemporaryDirectory() as tempdir: 107 | tester.compile_and_run(target="verilator", 108 | directory=tempdir, 109 | magma_output="verilog", 110 | flags=["-Wno-fatal"]) 111 | 112 | 113 | if __name__ == "__main__": 114 | test_tba() 115 | -------------------------------------------------------------------------------- /lake/modules/spec/lake_new.py: -------------------------------------------------------------------------------- 1 | import kratos 2 | from kratos import * 3 | from lake.modules.sram_stub import SRAMStub 4 | from lake.modules.addr_gen import AddrGen 5 | from lake.modules.sched_gen import SchedGen 6 | from lake.passes.passes import lift_config_reg 7 | 8 | 9 | class LakeTestTop(Generator): 10 | def __init__(self, 11 | data_width=16, 12 | fetch_width=1, 13 | mem_depth=512, 14 | config_width=16, 15 | input_addr_iterator_support=6, 16 | output_addr_iterator_support=6, 17 | input_sched_iterator_support=6, 18 | output_sched_iterator_support=6 19 | ): 20 | 21 | super().__init__("lake_top_test") 22 | 23 | # generation parameters 24 | 25 | # inputs 26 | self._clk = self.clock("clk") 27 | self._rst_n = self.reset("rst_n") 28 | 29 | self._clk_en = self.input("clk_en", 1) 30 | self._flush = self.input("flush", 1) 31 | 32 | self._data_in = self.input("data_in", data_width, packed=True) 33 | 34 | # outputs 35 | self._data_out = self.output("data_out", data_width, packed=True) 36 | 37 | # local variables 38 | self._write = self.var("write", 1) 39 | self._read = self.var("read", 1) 40 | self._write_addr = self.var("write_addr", config_width) 41 | self._read_addr = self.var("read_addr", config_width) 42 | self._addr = self.var("addr", clog2(mem_depth)) 43 | 44 | # memory module 45 | self.add_child(f"sram", 46 | SRAMStub(data_width, 47 | fetch_width, 48 | mem_depth), 49 | clk=self._clk, 50 | wen=self._write, 51 | cen=self._write | self._read, 52 | addr=self._addr, 53 | data_in=self._data_in, 54 | data_out=self._data_out) 55 | 56 | # addressor modules 57 | self.add_child(f"input_addr_gen", 58 | AddrGen(input_addr_iterator_support, 59 | config_width), 60 | clk=self._clk, 61 | rst_n=self._rst_n, 62 | step=self._write, 63 | addr_out=self._write_addr, 64 | clk_en=self._clk_en, 65 | flush=self._flush) 66 | 67 | self.add_child(f"output_addr_gen", 68 | AddrGen(output_addr_iterator_support, 69 | config_width), 70 | clk=self._clk, 71 | rst_n=self._rst_n, 72 | step=self._read, 73 | addr_out=self._read_addr, 74 | clk_en=self._clk_en, 75 | flush=self._flush) 76 | 77 | # scheduler modules 78 | self.add_child(f"input_sched_gen", 79 | SchedGen(input_sched_iterator_support, 80 | config_width), 81 | clk=self._clk, 82 | rst_n=self._rst_n, 83 | clk_en=self._clk_en, 84 | flush=self._flush, 85 | valid_output=self._write) 86 | 87 | self.add_child(f"output_sched_gen", 88 | SchedGen(output_sched_iterator_support, 89 | config_width), 90 | clk=self._clk, 91 | rst_n=self._rst_n, 92 | clk_en=self._clk_en, 93 | flush=self._flush, 94 | valid_output=self._read) 95 | 96 | lift_config_reg(self.internal_generator) 97 | 98 | self.add_code(self.set_sram_addr) 99 | 100 | @always_comb 101 | def set_sram_addr(self): 102 | if self._write: 103 | self._addr = self._write_addr[clog2(mem_depth) - 1, 0] 104 | else: 105 | self._addr = self._read_addr[clog2(mem_depth) - 1, 0] 106 | 107 | 108 | if __name__ == "__main__": 109 | lake_dut = LakeTestTop() 110 | verilog(lake_dut, filename="lake_new.sv") 111 | --------------------------------------------------------------------------------