def test_valid_generation(): io_core = IOCoreValid() io_core_circuit = io_core.circuit() tester = BasicTester(io_core_circuit, io_core_circuit.clk, io_core_circuit.reset) max_cycle = 16 # mode set to 1 # max cycle set to 16 config_data = [ io_core.get_config_data("mode", 1), io_core.get_config_data("max_cycle", max_cycle) ] config_data = compress_config_data(config_data) tester.zero_inputs() tester.poke(io_core_circuit.stall, 1) for addr, data in config_data: tester.configure(addr, data) tester.config_read(addr) tester.eval() tester.expect(io_core_circuit.read_config_data, data) # un-stall tester.poke(io_core_circuit.stall, 0) # it should stay low regardless of the IO as long as the reset signal is never high for _f2io_16 in [random_bv(16) for _ in range(10)]: tester.poke(io_core_circuit.f2io_16, _f2io_16) tester.eval() tester.expect(io_core_circuit.io2glb_16, _f2io_16) tester.expect(io_core_circuit.io2glb_1, 0) # hit the start signal tester.poke(io_core_circuit.glb2io_1, 1) tester.eval() # rising clock edge tester.step(2) # de-assert the reset/start signal tester.poke(io_core_circuit.glb2io_1, 0) tester.eval() # from now on it should output 1 until it reaches max cycle for _ in range(max_cycle): tester.expect(io_core_circuit.io2glb_1, 1) tester.step(2) # then stay low for _ in range(max_cycle): tester.expect(io_core_circuit.io2glb_1, 0) tester.step(2) with tempfile.TemporaryDirectory() as tempdir: tester.compile_and_run(target="verilator", magma_output="coreir-verilog", directory=tempdir, flags=["-Wno-fatal"])
def test_comparess_config_data(): # test out compressing bitstream config_data = [(0, 1), (0, 2)] config_data = compress_config_data(config_data) assert len(config_data) == 1 assert config_data[0][0] == 0 assert config_data[0][1] == 1 | 2 # test out duplicate without zeroing out, which is required to get rom # working under current lake design config_data = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 0), (1, 0)] config_data = compress_config_data(config_data, skip_compression=[1]) assert len(config_data) == 1 + 3 assert config_data[0][0] == 0 assert config_data[0][1] == 1 | 2 for i in range(1, 4): config_data[i][0] == 1 config_data[i][1] == 0
def test_valid_generation(run_tb): io_core = IOCoreValid() io_core_circuit = io_core.circuit() tester = BasicTester(io_core_circuit, io_core_circuit.clk, io_core_circuit.reset) max_cycle = 16 # mode set to 1 # max cycle set to 16 config_data = [io_core.get_config_data("mode", 1), io_core.get_config_data("max_cycle", max_cycle)] config_data = compress_config_data(config_data) tester.zero_inputs() tester.reset() tester.poke(io_core_circuit.stall, 1) for addr, data in config_data: tester.configure(addr, data) tester.config_read(addr) tester.eval() tester.expect(io_core_circuit.read_config_data, data) # un-stall tester.poke(io_core_circuit.stall, 0) # it should stay low regardless of the IO as long as the reset signal is never high for _f2io_16 in [random_bv(16) for _ in range(10)]: tester.poke(io_core_circuit.f2io_16, _f2io_16) tester.eval() tester.expect(io_core_circuit.io2glb_16, _f2io_16) tester.expect(io_core_circuit.io2glb_1, 0) # hit the start signal tester.poke(io_core_circuit.glb2io_1, 1) tester.eval() # rising clock edge tester.step(2) # de-assert the reset/start signal tester.poke(io_core_circuit.glb2io_1, 0) tester.eval() # from now on it should output 1 until it reaches max cycle for _ in range(max_cycle): tester.expect(io_core_circuit.io2glb_1, 1) tester.step(2) # then stay low for _ in range(max_cycle): tester.expect(io_core_circuit.io2glb_1, 0) tester.step(2) run_tb(tester)
def test_sb(num_tracks: int, bit_width: int, sb_ctor, reg: Tuple[bool, int]): """It only tests whether the circuit created matched with the graph representation. """ addr_width = 8 data_width = 32 switchbox = sb_ctor(0, 0, num_tracks, bit_width) reg_mode, batch_size = reg # insert registers to every sides and tracks if reg_mode: for side in SwitchBoxSide: for track in range(num_tracks): switchbox.add_pipeline_register(side, track) sb_circuit = SB(switchbox, addr_width, data_width) circuit = sb_circuit.circuit() # test the sb routing as well tester = BasicTester(circuit, circuit.clk, circuit.reset) # generate the addr based on mux names, which is used to sort the addr config_names = list(sb_circuit.registers.keys()) config_names.sort() # some of the sb nodes may turn into a pass-through wire. we still # need to test them. # we generate a pair of config data and expected values. if it's a # pass-through wire, we don't configure them, yet we still evaluate the # outcome to see if it's connected config_data = [] test_data = [] all_sbs = switchbox.get_all_sbs() for sb in all_sbs: mux_sel_name = get_mux_sel_name(sb) if mux_sel_name not in config_names: assert sb.io == SwitchBoxIO.SB_IN connected_sbs = sb.get_conn_in() # for a switch box where each SB_IN connects to 3 different # SN_OUT, the SB_IN won't have any incoming edges assert len(connected_sbs) == 0 input_sb_name = create_name(str(sb)) # as a result, we configure the fan-out sbs to see if they # can receive the signal. notice that this is overlapped with the # if statement above # we also wanted to test if the register mode can be turned on for connected_sb in sb: # type: SwitchBoxNode entry = [] mux_sel_name = get_mux_sel_name(connected_sb) assert mux_sel_name in config_names assert connected_sb.io == SwitchBoxIO.SB_OUT index = connected_sb.get_conn_in().index(sb) entry.append(sb_circuit.get_config_data(mux_sel_name, index)) # we will also configure the register, if connected reg_node, reg_mux_node = find_reg_mux_node(connected_sb) if reg_mux_node is not None: mux_sel_name = get_mux_sel_name(reg_mux_node) assert mux_sel_name in config_names index = reg_mux_node.get_conn_in().index(reg_node) entry.append( sb_circuit.get_config_data(mux_sel_name, index)) config_data.append(entry) # get port output_sb_name = create_name(str(connected_sb)) entry = [] for _ in range(batch_size): entry.append((circuit.interface.ports[input_sb_name], circuit.interface.ports[output_sb_name], fault.random.random_bv(bit_width))) test_data.append(entry) # compress the config data for i in range(len(config_data)): config_data[i] = compress_config_data(config_data[i]) # poke and test, without registers configured assert len(config_data) == len(test_data) for i in range(len(config_data)): tester.reset() configs = config_data[i] data = test_data[i] for addr, index in configs: index = BitVector[data_width](index) tester.configure(BitVector[addr_width](addr), index) tester.configure(BitVector[addr_width](addr), index + 1, False) tester.config_read(BitVector[addr_width](addr)) tester.eval() tester.expect(circuit.read_config_data, index) if len(data) == 1: # this is pass through mode for input_port, output_port, value in data: tester.poke(input_port, value) tester.eval() tester.expect(output_port, value) else: for j in range(len(data)): if j != 0: tester.eval() tester.expect(data[j - 1][1], data[j - 1][2]) input_port, _, value = data[j] tester.poke(input_port, value) tester.step(2) with tempfile.TemporaryDirectory() as tempdir: tester.compile_and_run(target="verilator", magma_output="coreir-verilog", directory=tempdir, flags=["-Wno-fatal"])
def test_double_buffer(): addr_width = 8 data_width = 32 bit_widths = [1, 16] num_tracks = 5 tile_id_width = 16 x = 0 y = 0 dummy_core = DummyCore() core = CoreInterface(dummy_core) tiles: Dict[int, Tile] = {} for bit_width in bit_widths: # we use disjoint switch here switchbox = DisjointSwitchBox(x, y, num_tracks, bit_width) tile = Tile(x, y, bit_width, switchbox) tiles[bit_width] = tile # set the core and core connection # here all the input ports are connect to SB_IN and all output ports are # connected to SB_OUT input_connections = [] for track in range(num_tracks): for side in SwitchBoxSide: input_connections.append( SBConnectionType(side, track, SwitchBoxIO.SB_IN)) output_connections = [] for track in range(num_tracks): for side in SwitchBoxSide: output_connections.append( SBConnectionType(side, track, SwitchBoxIO.SB_OUT)) for bit_width, tile in tiles.items(): tile.set_core(core) input_port_name = f"data_in_{bit_width}b" output_port_name = f"data_out_{bit_width}b" tile.set_core_connection(input_port_name, input_connections) tile.set_core_connection(output_port_name, output_connections) tile_circuit = TileCircuit(tiles, addr_width, data_width, tile_id_width=tile_id_width, double_buffer=True) tile_circuit.finalize() circuit = tile_circuit.circuit() bit_width = 16 # find corresponding sb sb_circuit: SB = None for _, sb in tile_circuit.sbs.items(): if sb.switchbox.width == bit_width: sb_circuit = sb break assert sb_circuit is not None # find that connection box input_port_name = f"data_in_{bit_width}b" cb_circuit: CB = None for _, cb in tile_circuit.cbs.items(): if cb.node.name == input_port_name: cb_circuit = cb break assert cb_circuit output_port_name = f"data_out_{bit_width}b" out_port_node = tile_circuit.tiles[bit_width].ports[output_port_name] in_port_node = tile_circuit.tiles[bit_width].ports[input_port_name] input_1 = sb_circuit.switchbox.get_sb(SwitchBoxSide.NORTH, 0, SwitchBoxIO.SB_IN) input_1_name = create_name(str(input_1)) input_2 = sb_circuit.switchbox.get_sb(SwitchBoxSide.EAST, 1, SwitchBoxIO.SB_IN) input_2_name = create_name(str(input_2)) output_sb = sb_circuit.switchbox.get_sb(SwitchBoxSide.SOUTH, 2, SwitchBoxIO.SB_OUT) output_name = create_name(str(output_sb)) input_1_bitstream = tile_circuit.get_route_bitstream_config( input_1, in_port_node) input_2_bitstream = tile_circuit.get_route_bitstream_config( input_2, in_port_node) output_bitstream = tile_circuit.get_route_bitstream_config( out_port_node, output_sb) # notice that both of them will be configured using the double buffer scheme def get_config_data(config_data, reg_data): for reg_addr, feat_addr, config_value in reg_data: reg_addr = reg_addr << tile_circuit.feature_config_slice.start feat_addr = feat_addr << tile_circuit.tile_id_width addr = reg_addr | feat_addr addr = BitVector[data_width](addr) | BitVector[data_width](0) config_data.append((addr, config_value)) input1_config_data = [] input2_config_data = [] get_config_data(input1_config_data, [input_1_bitstream, output_bitstream]) get_config_data(input2_config_data, [input_2_bitstream, output_bitstream]) input1_config_data = compress_config_data(input1_config_data) input2_config_data = compress_config_data(input2_config_data) tester = BasicTester(circuit, circuit.clk, circuit.reset) tester.poke(circuit.tile_id, 0) for addr, config_value in input1_config_data: tester.configure(addr, config_value) tester.config_read(addr) tester.eval() tester.expect(circuit.read_config_data, config_value) # configure the double buffer register tester.poke(circuit.config_db, 1) for addr, config_value in input2_config_data: tester.configure(addr, config_value) tester.config_read(addr) tester.eval() tester.expect(circuit.read_config_data, config_value) # the route should still be input 1 port = circuit.interface.ports[input_1_name] tester.poke(port, 42) port = circuit.interface.ports[input_2_name] tester.poke(port, 43) tester.eval() tester.expect(circuit.interface.ports[output_name], 42) # now use the double buffer tester.poke(circuit.use_db, 1) tester.eval() tester.expect(circuit.interface.ports[output_name], 43) with tempfile.TemporaryDirectory() as tempdir: tester.compile_and_run(target="verilator", magma_output="coreir-verilog", directory=tempdir, flags=["-Wno-fatal"])
def test_tile(num_tracks: int, add_additional_core: bool): import random random.seed(0) addr_width = 8 data_width = 32 bit_widths = [1, 16] tile_id_width = 16 x = 0 y = 0 dummy_core = DummyCore() core = CoreInterface(dummy_core) if add_additional_core: c = AdditionalDummyCore() additional_core = CoreInterface(c) else: additional_core = None tiles: Dict[int, Tile] = {} for bit_width in bit_widths: # we use disjoint switch here switchbox = DisjointSwitchBox(x, y, num_tracks, bit_width) tile = Tile(x, y, bit_width, switchbox) tiles[bit_width] = tile # set the core and core connection # here all the input ports are connect to SB_IN and all output ports are # connected to SB_OUT input_connections = [] for track in range(num_tracks): for side in SwitchBoxSide: input_connections.append( SBConnectionType(side, track, SwitchBoxIO.SB_IN)) output_connections = [] for track in range(num_tracks): for side in SwitchBoxSide: output_connections.append( SBConnectionType(side, track, SwitchBoxIO.SB_OUT)) for bit_width, tile in tiles.items(): tile.set_core(core) if add_additional_core: connection_type = CoreConnectionType.Core | CoreConnectionType.CB tile.add_additional_core(additional_core, connection_type) input_port_name = f"data_in_{bit_width}b" input_port_name_extra = f"data_in_{bit_width}b_extra" output_port_name = f"data_out_{bit_width}b" tile.set_core_connection(input_port_name, input_connections) tile.set_core_connection(output_port_name, output_connections) if add_additional_core: tile.set_core_connection(input_port_name_extra, input_connections) tile_circuit = TileCircuit(tiles, addr_width, data_width, tile_id_width=tile_id_width) # finalize it tile_circuit.finalize() circuit = tile_circuit.circuit() # set up the configuration and test data # there are several things we are interested in the tile level and # need to test # 1. given an input to SB_IN, and configure it to CB, will the core # receive the data or not # 2. given an output signal from core, and configure it to SB, will the # SB_OUT receive the data or not # However, because we can only poke input ports, we cannot test #2 in the # current environment. As a result, we will combined these 2 together, that # is: # given an SB_IN signal, we configure the CB to the data_in, then configure # the SB_OUT to receive the signal raw_config_data = [] config_data = [] test_data = [] tile_id = fault.random.random_bv(tile_id_width) for bit_width in bit_widths: # find corresponding sb sb_circuit: SB = None for _, sb in tile_circuit.sbs.items(): if sb.switchbox.width == bit_width: sb_circuit = sb break assert sb_circuit is not None # input if add_additional_core: input_port_name = f"data_in_{bit_width}b_extra" else: input_port_name = f"data_in_{bit_width}b" in_port_node = tile_circuit.tiles[bit_width].ports[input_port_name] # find that connection box cb_circuit: CB = None for _, cb in tile_circuit.cbs.items(): if cb.node.name == input_port_name: cb_circuit = cb break assert cb_circuit output_port_name = f"data_out_{bit_width}b" out_port_node = tile_circuit.tiles[bit_width].ports[output_port_name] all_sbs = sb_circuit.switchbox.get_all_sbs() for in_sb_node in all_sbs: if in_sb_node.io != SwitchBoxIO.SB_IN: continue for out_sb_node in all_sbs: if out_sb_node.io != SwitchBoxIO.SB_OUT: continue # find the output node's index to that switch box node data0 = tile_circuit.get_route_bitstream_config( in_sb_node, in_port_node) data1 = tile_circuit.get_route_bitstream_config( out_port_node, out_sb_node) raw_config_data.append(data0) raw_config_data.append(data1) # configure the cb to route data from additional core to the # main core if add_additional_core: input_port_name = f"data_in_{bit_width}b" output_port_name = f"data_out_{bit_width}b_extra" additional_in_port_node = \ tile_circuit.tiles[bit_width].ports[input_port_name] additional_out_port_node = \ tile_circuit.tiles[bit_width].ports[output_port_name] data2 = tile_circuit.get_route_bitstream_config( additional_out_port_node, additional_in_port_node) raw_config_data.append(data2) in_sb_name = create_name(str(in_sb_node)) out_sb_name = create_name(str(out_sb_node)) test_data.append( (circuit.interface.ports[in_sb_name], circuit.interface.ports[out_sb_name], fault.random.random_bv(bit_width), in_sb_node)) if add_additional_core: assert len(raw_config_data) / 3 == len(test_data) else: assert len(raw_config_data) / 2 == len(test_data) # process the raw config data and change it into the actual config addr for reg_addr, feat_addr, config_value in raw_config_data: reg_addr = reg_addr << tile_circuit.feature_config_slice.start feat_addr = feat_addr << tile_circuit.tile_id_width addr = reg_addr | feat_addr addr = BitVector[data_width](addr) | BitVector[data_width](tile_id) config_data.append((addr, config_value)) # actual tests tester = BasicTester(circuit, circuit.clk, circuit.reset) tester.poke(circuit.tile_id, tile_id) stride = 3 if add_additional_core else 2 for i in range(0, len(config_data), stride): tester.reset() c_data = config_data[i:i + stride] c_data = compress_config_data(c_data) for addr, config_value in c_data: tester.configure(addr, config_value) tester.configure(addr, config_value + 1, False) tester.config_read(addr) tester.eval() tester.expect(circuit.read_config_data, config_value) input_port, output_port, value, in_node = test_data[i // stride] tester.poke(input_port, value) tester.eval() # add additional error to check, i.e. sending random junk data to # all unrelated ports for bit_width in bit_widths: sb = tile_circuit.sbs[bit_width] sbs = sb.switchbox.get_all_sbs() for sb in sbs: if sb == in_node or sb.io == SwitchBoxIO.SB_OUT: continue port_name = create_name(str(sb)) port = circuit.interface.ports[port_name] tester.poke(port, fault.random.random_bv(bit_width)) tester.eval() tester.expect(output_port, value) with tempfile.TemporaryDirectory() as tempdir: tester.compile_and_run(target="verilator", magma_output="coreir-verilog", directory=tempdir, flags=["-Wno-fatal"])
def test_interconnect(num_tracks: int, chip_size: int, reg_mode: bool, wiring: GlobalSignalWiring): bit_widths, data_width, ics, interconnect = create_dummy_cgra(chip_size, num_tracks, reg_mode, wiring) # assert tile coordinates for (x, y), tile_circuit in interconnect.tile_circuits.items(): for _, tile in tile_circuit.tiles.items(): assert_tile_coordinate(tile, x, y) circuit = interconnect.circuit() tester = BasicTester(circuit, circuit.clk, circuit.reset) config_data = [] test_data = [] # we have a 2x2 (for instance) chip as follows # |-------| # | A | B | # |---|---| # | C | D | # --------- # we need to test all the line-tile routes. that is, per row and per column # A <-> B, A <-> C, B <-> D, C <-> D # TODO: add core input/output as well vertical_conns = [[SwitchBoxSide.NORTH, SwitchBoxIO.SB_IN, SwitchBoxSide.SOUTH, SwitchBoxIO.SB_OUT, 0, chip_size - 1], [SwitchBoxSide.SOUTH, SwitchBoxIO.SB_IN, SwitchBoxSide.NORTH, SwitchBoxIO.SB_OUT, chip_size - 1, 0]] horizontal_conns = [[SwitchBoxSide.WEST, SwitchBoxIO.SB_IN, SwitchBoxSide.EAST, SwitchBoxIO.SB_OUT, 0, chip_size - 1], [SwitchBoxSide.EAST, SwitchBoxIO.SB_IN, SwitchBoxSide.WEST, SwitchBoxIO.SB_OUT, chip_size - 1, 0]] for bit_width in bit_widths: for track in range(num_tracks): # vertical for x in range(chip_size): for side_io in vertical_conns: from_side, from_io, to_side, to_io, start, end = side_io src_node = None dst_node = None config_entry = [] for y in range(chip_size): tile_circuit = interconnect.tile_circuits[(x, y)] tile = tile_circuit.tiles[bit_width] pre_node = tile.get_sb(from_side, track, from_io) tile_circuit = interconnect.tile_circuits[(x, y)] tile = tile_circuit.tiles[bit_width] next_node = tile.get_sb(to_side, track, to_io) if y == start: src_node = pre_node if y == end: dst_node = next_node entry = \ interconnect.get_node_bitstream_config(pre_node, next_node) config_entry.append(entry) config_entry = compress_config_data(config_entry) assert src_node is not None and dst_node is not None config_data.append(config_entry) value = fault.random.random_bv(bit_width) src_name = interconnect.get_top_port_name(src_node) dst_name = interconnect.get_top_port_name(dst_node) test_data.append((circuit.interface[src_name], circuit.interface[dst_name], value)) # horizontal connections for y in range(chip_size): for side_io in horizontal_conns: from_side, from_io, to_side, to_io, start, end = side_io src_node = None dst_node = None config_entry = [] for x in range(chip_size): tile_circuit = interconnect.tile_circuits[(x, y)] tile = tile_circuit.tiles[bit_width] pre_node = tile.get_sb(from_side, track, from_io) tile_circuit = interconnect.tile_circuits[(x, y)] tile = tile_circuit.tiles[bit_width] next_node = tile.get_sb(to_side, track, to_io) if x == start: src_node = pre_node if x == end: dst_node = next_node entry = \ interconnect.get_node_bitstream_config(pre_node, next_node) config_entry.append(entry) config_entry = compress_config_data(config_entry) assert src_node is not None and dst_node is not None config_data.append(config_entry) value = fault.random.random_bv(bit_width) src_name = interconnect.get_top_port_name(src_node) dst_name = interconnect.get_top_port_name(dst_node) test_data.append((circuit.interface[src_name], circuit.interface[dst_name], value)) # the actual test assert len(config_data) == len(test_data) # NOTE: # we don't test the configuration read here for i in range(len(config_data)): tester.reset() input_port, output_port, value = test_data[i] for addr, index in config_data[i]: tester.configure(BitVector[data_width](addr), index) tester.configure(BitVector[data_width](addr), index + 1, False) tester.config_read(addr) tester.eval() tester.expect(circuit.read_config_data, index) tester.poke(input_port, value) tester.eval() tester.expect(output_port, value) with tempfile.TemporaryDirectory() as tempdir: tester.compile_and_run(target="verilator", magma_output="coreir-verilog", directory=tempdir, flags=["-Wno-fatal"]) # also test the interconnect clone new_interconnect = interconnect.clone() with tempfile.TemporaryDirectory() as tempdir_old: interconnect.dump_pnr(tempdir_old, "old") with tempfile.TemporaryDirectory() as tempdir_new: new_interconnect.dump_pnr(tempdir_new, "new") # they should be exactly the same graph1_old = os.path.join(tempdir_old, "1.graph") graph1_new = os.path.join(tempdir_new, "1.graph") assert filecmp.cmp(graph1_old, graph1_new) graph16_old = os.path.join(tempdir_old, "16.graph") graph16_new = os.path.join(tempdir_new, "16.graph") assert filecmp.cmp(graph16_old, graph16_new) layout_old = os.path.join(tempdir_old, "old.layout") layout_new = os.path.join(tempdir_new, "new.layout") assert filecmp.cmp(layout_old, layout_new)