diff --git a/hdc_exp/char_recog.py b/hdc_exp/char_recog.py index 123d0d8..261ead5 100644 --- a/hdc_exp/char_recog.py +++ b/hdc_exp/char_recog.py @@ -121,6 +121,7 @@ def encode_character( ortho_im, character_input, threshold, + debug=False, encode_mode="indexed", hv_type="binary", ): @@ -129,12 +130,23 @@ def encode_character( # Cycle through every pixel if encode_mode == "bind": + # This is a nasty hard fix but the idea is to + # reverse the order because the tool python list reads + # the list in a reverse way... + character_input = character_input[::-1] for i in range(len(character_input)): pixel_val_hv = ortho_im[character_input[i]] pixel_pos_hv = ortho_im[2 + i] char_hv += bind_hv(pixel_val_hv, pixel_pos_hv) + if debug: + print(f"character_input: {character_input[i]}") + print(f"position: {i}") + print(f"pixel_val_hv: {pixel_val_hv}") + print(f"pixel_pos_hv: {pixel_pos_hv}") + print(f"char_hv: {pixel_val_hv}") + else: for i in range(len(character_input)): # If pixel value is a 1 @@ -165,6 +177,7 @@ def encode_with_bind( ortho_im, character_input, threshold, + debug=False, encode_mode="indexed", hv_type="binary", ): @@ -174,6 +187,7 @@ def encode_with_bind( ortho_im, character_input, threshold, + debug=debug, encode_mode=encode_mode, hv_type=hv_type, ) @@ -433,11 +447,11 @@ def exp_distort_test(): if __name__ == "__main__": # Some working parameters seed_size = 32 - hv_dim = 512 - num_total_im = 1024 + hv_dim = 256 + num_total_im = 512 num_per_im_bank = int(hv_dim // 4) threshold = THRESHOLD - encode_mode = "indexed" + encode_mode = "bind" # Get original data set file_path = "./data_set/char_recog/characters.txt" @@ -481,6 +495,16 @@ def exp_distort_test(): dataset, hv_dim, ortho_im, threshold, encode_mode=encode_mode, hv_type="binary" ) + # query_hv = encode_with_bind( + # hv_dim, + # ortho_im, + # dataset[0], + # threshold, + # debug=True, + # encode_mode=encode_mode, + # hv_type="binary", + # ) + # Encode query_hv_list query_hv_set = [] diff --git a/rtl/csr/csr.sv b/rtl/csr/csr.sv index b6af59b..9283891 100644 --- a/rtl/csr/csr.sv +++ b/rtl/csr/csr.sv @@ -85,13 +85,6 @@ module csr import csr_addr_pkg::*; #( output logic [InstMemAddrWidth-1:0] csr_loop_count_addr2_o, output logic [InstMemAddrWidth-1:0] csr_loop_count_addr3_o, output logic [ ObservableWidth-1:0] csr_obs_logic_o, - // Data source configurations - output logic csr_src_mux_a_o, - output logic csr_src_mux_b_o, - output logic [ CsrDataWidth-1:0] csr_auto_count_start_a_o, - output logic [ CsrDataWidth-1:0] csr_auto_count_start_b_o, - output logic [ CsrDataWidth-1:0] csr_auto_count_num_a_o, - output logic [ CsrDataWidth-1:0] csr_auto_count_num_b_o, // Data slicer configurations output logic [ SlicerModeWidth-1:0] csr_data_slice_mode_a_o, output logic [ SlicerModeWidth-1:0] csr_data_slice_mode_b_o, diff --git a/rtl/hypercorex_top.sv b/rtl/hypercorex_top.sv index 12e1f20..8ad8686 100644 --- a/rtl/hypercorex_top.sv +++ b/rtl/hypercorex_top.sv @@ -37,6 +37,7 @@ module hypercorex_top # ( parameter int unsigned HoldFifoDepth = 2, parameter bit EnableRomIM = 1'b0, parameter bit FifoFallthrough = 1'b0, + parameter bit FifoFallthroughSlicer = 1'b1, //--------------------------- // Instruction Memory Parameters //--------------------------- @@ -536,7 +537,7 @@ module hypercorex_top # ( .SlicerFifoDepth ( SlicerFifoDepth ), .CsrDataWidth ( CsrDataWidth ), // Don't touch! - .FifoFallthrough ( FifoFallthrough ), + .FifoFallthrough ( FifoFallthroughSlicer ), .ModeWidth ( SlicerModeWidth ) ) i_data_slicer_a ( // Clocks and reset @@ -564,7 +565,7 @@ module hypercorex_top # ( .SlicerFifoDepth ( SlicerFifoDepth ), .CsrDataWidth ( CsrDataWidth ), // Don't touch! - .FifoFallthrough ( FifoFallthrough ), + .FifoFallthrough ( FifoFallthroughSlicer ), .ModeWidth ( SlicerModeWidth ) ) i_data_slicer_b ( // Clocks and reset diff --git a/rtl/tb/tb_hypercorex.sv b/rtl/tb/tb_hypercorex.sv index d350ad1..9078797 100644 --- a/rtl/tb/tb_hypercorex.sv +++ b/rtl/tb/tb_hypercorex.sv @@ -78,16 +78,16 @@ module tb_hypercorex # ( input logic highdim_mode_i, // Low dim signals input logic [TbMemAddrWidth-1:0] im_a_lowdim_wr_addr_i, - input logic [ CsrDataWidth-1:0] im_a_lowdim_wr_data_i, + input logic [ LowDimWidth-1:0] im_a_lowdim_wr_data_i, input logic im_a_lowdim_wr_en_i, input logic [TbMemAddrWidth-1:0] im_a_lowdim_rd_addr_i, - output logic [ CsrDataWidth-1:0] im_a_lowdim_rd_data_o, + output logic [ LowDimWidth-1:0] im_a_lowdim_rd_data_o, input logic [TbMemAddrWidth-1:0] im_b_lowdim_wr_addr_i, - input logic [ CsrDataWidth-1:0] im_b_lowdim_wr_data_i, + input logic [ LowDimWidth-1:0] im_b_lowdim_wr_data_i, input logic im_b_lowdim_wr_en_i, input logic [TbMemAddrWidth-1:0] im_b_lowdim_rd_addr_i, - output logic [ CsrDataWidth-1:0] im_b_lowdim_rd_data_o, + output logic [ LowDimWidth-1:0] im_b_lowdim_rd_data_o, // High dim signals input logic [TbMemAddrWidth-1:0] im_a_highdim_wr_addr_i, input logic [ HVDimension-1:0] im_a_highdim_wr_data_i, @@ -159,7 +159,7 @@ module tb_hypercorex # ( // Memory module for low dimensional memory IM A tb_rd_memory # ( - .DataWidth ( CsrDataWidth ), + .DataWidth ( LowDimWidth ), .AddrWidth ( CsrAddrWidth ), .MemDepth ( TbMemDepth ) ) i_im_a_lowdim_memory ( @@ -215,7 +215,7 @@ module tb_hypercorex # ( // Memory module for low dimensional memory IM B tb_rd_memory # ( - .DataWidth ( CsrDataWidth ), + .DataWidth ( LowDimWidth ), .AddrWidth ( CsrAddrWidth ), .MemDepth ( TbMemDepth ) ) i_im_b_lowdim_memory ( diff --git a/sw/asm/train_char_recog_data_slice.asm b/sw/asm/train_char_recog_data_slice.asm new file mode 100644 index 0000000..4ad6642 --- /dev/null +++ b/sw/asm/train_char_recog_data_slice.asm @@ -0,0 +1,5 @@ +imab_bind_bunda +mv_bunda_qhv +am_load +am_search +clr_bunda \ No newline at end of file diff --git a/tests/test_hypercorex_char_recog_data_slice.py b/tests/test_hypercorex_char_recog_data_slice.py new file mode 100644 index 0000000..abd1a0d --- /dev/null +++ b/tests/test_hypercorex_char_recog_data_slice.py @@ -0,0 +1,391 @@ +""" + Copyright 2024 KU Leuven + Ryan Antonio + + Description: + This tests the hypercorex's CSR RW functionality. +""" + +import set_parameters +from util import ( + # Filelist management + get_dir, + get_bender_filelist, + # General imports + get_root, + setup_and_run, + hvlist2num, + clock_and_time, + check_result, + check_result_array, + clear_tb_inputs, + write_csr, + read_csr, + load_im_list, + load_am_list, + read_qhv, + read_predict, + numbin2list, + config_inst_ctrl, +) + +import cocotb +from cocotb.clock import Clock +import sys +import pytest +import numpy as np + +# Add hdc utility functions +hdc_util_path = get_root() + "/hdc_exp/" +print(f"Adding HDC utility functions from: {hdc_util_path}") +sys.path.append(hdc_util_path) + +from hdc_util import gen_ca90_im_set # noqa: E402 +from char_recog import ( # noqa: E402 + train_char_recog, + char_recog_dataset, +) + +compiler_path = get_root() + "/sw/" +print(f"Adding SW functions from: {compiler_path}") +sys.path.append(compiler_path) + +from hypercorex_compiler import compile_hypercorex_asm # noqa: E402 + +# Some parameters about the character recognition set +TOTAL_PIXEL_FEATURES = 35 +THRESHOLD = TOTAL_PIXEL_FEATURES / 2 +NUM_CLASSES = 26 + + +# Actual test routines +@cocotb.test() +async def tb_hypercorex_dut(dut): + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Testing Character Recognition ") + cocotb.log.info(" ------------------------------------------ ") + + # Extract data set + dataset_file_path = get_dir() + "/../hdc_exp/data_set/char_recog/characters.txt" + cocotb.log.info(f"Extracting data from: {dataset_file_path}") + dataset = char_recog_dataset(dataset_file_path) + + for i in dataset: + print(i) + + dataset_compressed = [] + for i in range(len(dataset)): + dataset_compressed.append(hvlist2num(dataset[i])) + + # Extract asm file + inst_file_path = get_dir() + "/../sw/asm/train_char_recog_data_slice.asm" + cocotb.log.info(f"Extracting instructions from: {inst_file_path}") + inst_code_list, control_code_list = compile_hypercorex_asm(inst_file_path) + + # Convert each instruction to integers for input + for i in range(len(inst_code_list)): + inst_code_list[i] = hvlist2num(np.array(inst_code_list[i])) + + # Get a CA90 seed that's working + im_seed_list, ortho_im, conf_mat = gen_ca90_im_set( + seed_size=set_parameters.SEED_DIM, + hv_dim=set_parameters.HV_DIM, + num_total_im=set_parameters.NUM_TOT_IM, + num_per_im_bank=set_parameters.NUM_PER_IM_BANK, + base_seeds=set_parameters.ORTHO_IM_SEEDS, + gen_seed=True, + ca90_mode=set_parameters.CA90_MODE, + ) + + # Train the character recognition model + assoc_mem = train_char_recog( + dataset=dataset, + hv_dim=set_parameters.HV_DIM, + ortho_im=ortho_im, + threshold=THRESHOLD, + encode_mode="bind", + hv_type="binary", + ) + + # Assoc mem integer list + assoc_mem_int = [] + for i in range(len(assoc_mem)): + assoc_mem_int.append(hvlist2num(assoc_mem[i])) + + # Correct data set + correct_set = list(range(NUM_CLASSES)) + + # Initialize input values + clear_tb_inputs(dut) + + # Reset always + dut.rst_ni.value = 0 + + # Initialize hard static values + dut.enable_mem_i.value = 0 + + # This needs to be the number of classes to check + dut.am_auto_loop_addr_i.value = NUM_CLASSES - 1 + + # Initialize clock always + clock = Clock(dut.clk_i, 10, units="ns") + cocotb.start_soon(clock.start(start_high=False)) + + # Wait one cycle for reset + await clock_and_time(dut.clk_i) + + # Release reset + dut.rst_ni.value = 1 + + # Assume CSR response is always ready to receive + dut.csr_rsp_ready_i.value = 1 + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Load Data to LowDim IMA ") + cocotb.log.info(" ------------------------------------------ ") + + # Load list to A + await load_im_list(dut, dataset_compressed, 0, "A", "low") + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Load Data to AM ") + cocotb.log.info(" ------------------------------------------ ") + + await load_am_list(dut, assoc_mem_int, 0) + + # Enable memories when done loading + dut.enable_mem_i.value = 1 + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Write to Number of Predictions ") + cocotb.log.info(" ------------------------------------------ ") + + num_predict = NUM_CLASSES + await write_csr(dut, set_parameters.AM_NUM_PREDICT_REG_ADDR, num_predict) + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Write to Instruction Memory ") + cocotb.log.info(" ------------------------------------------ ") + + # Enable first the write mode and debug mode + inst_ctrl_code = 0x0000_0003 + await write_csr(dut, set_parameters.INST_CTRL_REG_ADDR, inst_ctrl_code) + + # Write to instruction memory + # While writing we can check the current program counter + for i in range(len(inst_code_list)): + read_inst_addr = await read_csr(dut, set_parameters.INST_PC_ADDR_REG_ADDR) + check_result(i, read_inst_addr) + await write_csr(dut, set_parameters.INST_WRITE_DATA_REG_ADDR, inst_code_list[i]) + + # Using the debug address we can read the instructions + for i in range(len(inst_code_list)): + await write_csr(dut, set_parameters.INST_RDDBG_ADDR_REG_ADDR, i) + read_inst = await read_csr(dut, set_parameters.INST_INST_AT_ADDR_ADDR_REG_ADDR) + check_result(inst_code_list[i], read_inst) + + # Deactivate debug mode and clear program counter + inst_ctrl_code = 0x0000_0004 + await write_csr(dut, set_parameters.INST_CTRL_REG_ADDR, inst_ctrl_code) + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Encoding Loops and Jumps ") + cocotb.log.info(" ------------------------------------------ ") + + # Write loop control + loop_ctrl_code = 0x0000_0002 + await write_csr(dut, set_parameters.INST_LOOP_CTRL_REG_ADDR, loop_ctrl_code) + + # Write loop jump address + # Combination of loop 1, loop 2, and loop 3 + loop_jump_addr = await config_inst_ctrl( + dut=dut, + reg_addr=set_parameters.INST_LOOP_JUMP_ADDR_REG_ADDR, + val1=0, + val2=0, + val3=0, + data_width=set_parameters.INST_MEM_ADDR_WIDTH, + ) + + # Write loop end address + loop_end_addr = await config_inst_ctrl( + dut=dut, + reg_addr=set_parameters.INST_LOOP_END_ADDR_REG_ADDR, + val1=0, + val2=4, + val3=0, + data_width=set_parameters.INST_MEM_ADDR_WIDTH, + ) + + # Write loop count + loop_count = await config_inst_ctrl( + dut=dut, + reg_addr=set_parameters.INST_LOOP_COUNT_REG_ADDR, + val1=TOTAL_PIXEL_FEATURES, + val2=set_parameters.TEST_RUNS, + val3=0, + data_width=set_parameters.INST_MEM_ADDR_WIDTH, + ) + + # Check if the loop control is written correctly + read_loop_ctrl = await read_csr(dut, set_parameters.INST_LOOP_CTRL_REG_ADDR) + check_result(loop_ctrl_code, read_loop_ctrl) + + # Check if the loop jump address is written correctly + read_loop_jump_addr = await read_csr( + dut, set_parameters.INST_LOOP_JUMP_ADDR_REG_ADDR + ) + check_result(loop_jump_addr, read_loop_jump_addr) + + # Check if the loop end address is written correctly + read_loop_end_addr = await read_csr(dut, set_parameters.INST_LOOP_END_ADDR_REG_ADDR) + check_result(loop_end_addr, read_loop_end_addr) + + # Check if the loop count is written correctly + read_loop_count = await read_csr(dut, set_parameters.INST_LOOP_COUNT_REG_ADDR) + check_result(loop_count, read_loop_count) + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Configure Data Slice Mode ") + cocotb.log.info(" ------------------------------------------ ") + + # Set port A to be the data slicing mode in 1bit sequence + # Set port B to be coming from the auto generator + data_src_ctrl = 0x0000_0021 + await write_csr(dut, set_parameters.DATA_SRC_CTRL_REG_ADDR, data_src_ctrl) + + # Check if write is correct + read_data_src_ctrl = await read_csr(dut, set_parameters.DATA_SRC_CTRL_REG_ADDR) + check_result(data_src_ctrl, read_data_src_ctrl) + + # Configure A number of elements to be auto-sliced + data_slice_num_elem_a = TOTAL_PIXEL_FEATURES + await write_csr( + dut, set_parameters.DATA_SLICE_NUM_ELEM_A_REG_ADDR, data_slice_num_elem_a + ) + + # Check if write is correct + read_data_slice_num_elem_a = await read_csr( + dut, set_parameters.DATA_SLICE_NUM_ELEM_A_REG_ADDR + ) + check_result(data_slice_num_elem_a, read_data_slice_num_elem_a) + + # Configure B for auto counting + # We start at 2 because the first 2 elements + # Will be dedicated to 0 or 1 pixel values + # The goal is to bundle these things + data_src_auto_start_b = 2 + data_src_auto_num_b = TOTAL_PIXEL_FEATURES + + await write_csr( + dut, set_parameters.DATA_SRC_AUTO_START_B_REG_ADDR, data_src_auto_start_b + ) + await write_csr( + dut, set_parameters.DATA_SRC_AUTO_NUM_B_REG_ADDR, data_src_auto_num_b + ) + + # Check if write is correct + read_data_src_auto_start_b = await read_csr( + dut, set_parameters.DATA_SRC_AUTO_START_B_REG_ADDR + ) + check_result(data_src_auto_start_b, read_data_src_auto_start_b) + + # Check if write is correct + read_data_src_auto_num_b = await read_csr( + dut, set_parameters.DATA_SRC_AUTO_NUM_B_REG_ADDR + ) + check_result(data_src_auto_num_b, read_data_src_auto_num_b) + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Start the Core ") + cocotb.log.info(" ------------------------------------------ ") + + # Write to control registers + core_ctrl_code = 0x0000_0001 + await write_csr(dut, set_parameters.CORE_SET_REG_ADDR, core_ctrl_code) + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Poll until Core Finishes ") + cocotb.log.info(" ------------------------------------------ ") + + while True: + busy_signal = await read_csr(dut, set_parameters.CORE_SET_REG_ADDR) + busy_signal = (busy_signal >> 1) & 0x0000_0001 + + if not busy_signal: + break + + # There are two ways to check the output + # one is from the QHV memory, while the other is from + # the lowdim AM prediction memory + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Reading from QHV Memory ") + cocotb.log.info(" ------------------------------------------ ") + + for i in range(set_parameters.TEST_RUNS): + qhv_val = await read_qhv(dut, i) + qhv_val = numbin2list(qhv_val, set_parameters.HV_DIM) + check_result_array(assoc_mem[i], qhv_val) + + cocotb.log.info(" ------------------------------------------ ") + cocotb.log.info(" Reading from Predict Memory ") + cocotb.log.info(" ------------------------------------------ ") + + for i in range(set_parameters.TEST_RUNS): + predict_val = await read_predict(dut, i) + check_result(predict_val, correct_set[i]) + + # Some trailing cycles only + for i in range(100): + await clock_and_time(dut.clk_i) + + +# Config and run +@pytest.mark.parametrize( + "parameters", + [ + { + # Enable ROM IM + "EnableRomIM": str(set_parameters.ENABLE_ROM_IM), + # General parameters + "HVDimension": str(set_parameters.HV_DIM), + "LowDimWidth": str(set_parameters.NARROW_DATA_WIDTH), + # CSR parameters + "CsrDataWidth": str(set_parameters.REG_FILE_WIDTH), + "CsrAddrWidth": str(set_parameters.REG_FILE_WIDTH), + # Item memory parameters + "NumTotIm": str(set_parameters.NUM_TOT_IM), + "NumPerImBank": str(set_parameters.NUM_PER_IM_BANK), + "ImAddrWidth": str(set_parameters.REG_FILE_WIDTH), + "SeedWidth": str(set_parameters.REG_FILE_WIDTH), + "HoldFifoDepth": str(set_parameters.IM_FIFO_DEPTH), + # Instruction memory parameters + "InstMemDepth": str(set_parameters.INST_MEM_DEPTH), + # HDC encoder parameters + "BundCountWidth": str(set_parameters.BUNDLER_COUNT_WIDTH), + "BundMuxWidth": str(set_parameters.BUNDLER_MUX_WIDTH), + "ALUMuxWidth": str(set_parameters.ALU_MUX_WIDTH), + "ALUMaxShiftAmt": str(set_parameters.ALU_MAX_SHIFT), + "RegMuxWidth": str(set_parameters.REG_MUX_WIDTH), + "QvMuxWidth": str(set_parameters.QHV_MUX_WIDTH), + "RegNum": str(set_parameters.REG_NUM), + } + ], +) +def test_hypercorex_char_recog_data_slice(simulator, parameters, waves): + bender_path = bender_path = get_dir() + "/../." + bender_filelist = get_bender_filelist(bender_path) + verilog_sources = bender_filelist + toplevel = "tb_hypercorex" + + module = "test_hypercorex_char_recog_data_slice" + + setup_and_run( + verilog_sources=verilog_sources, + toplevel=toplevel, + module=module, + simulator=simulator, + parameters=parameters, + waves=waves, + bender_filelist=True, + )