From a2230690669f97f59ffb295b9798edbf4ced1f74 Mon Sep 17 00:00:00 2001 From: Maciej Dudek Date: Thu, 12 Oct 2023 13:12:13 +0200 Subject: [PATCH] Impove BIST hammer only attacks to support any number of rwos up to 32 Signed-off-by: Maciej Dudek --- rowhammer_tester/gateware/bist.py | 78 ++++++++++++++++------- rowhammer_tester/scripts/hw_rowhammer.py | 11 ++-- rowhammer_tester/scripts/rowhammer.py | 34 +++++----- rowhammer_tester/scripts/utils.py | 2 +- tests/test_bist.py | 80 +++++++++++++++++++----- third_party/litedram | 2 +- 6 files changed, 147 insertions(+), 60 deletions(-) diff --git a/rowhammer_tester/gateware/bist.py b/rowhammer_tester/gateware/bist.py index 959206cf9..0d108cdad 100644 --- a/rowhammer_tester/gateware/bist.py +++ b/rowhammer_tester/gateware/bist.py @@ -105,13 +105,15 @@ class BISTModule(Module): the operation. When the operation is ongoing `ready` will be 0. """ def __init__(self, pattern_mem): - self.start = Signal() - self.ready = Signal() - self.count = Signal(32) - self.done = Signal(32) + self.start = Signal() + self.ready = Signal() + self.modulo = Signal() + self.count = Signal(32) + self.done = Signal(32) self.mem_mask = Signal(32) - self.data_mask = Signal(32) + self.data_mask = Signal(max=pattern_mem.data.depth) + self.data_div = Signal(max=pattern_mem.data.depth) self.data_port = pattern_mem.data.get_port(mode=READ_FIRST) self.addr_port = pattern_mem.addr.get_port(mode=READ_FIRST) @@ -121,6 +123,8 @@ def add_csrs(self): self._start = CSR() self._start.description = 'Write to the register starts the transfer (if ready=1)' self._ready = CSRStatus(description='Indicates that the transfer is not ongoing') + self._modulo = CSRStorage(description='When set use modulo to calculate DMA transfers address' + ' rather than bit masking') self._count = CSRStorage(size=len(self.count), description='Desired number of DMA transfers') self._done = CSRStatus(size=len(self.done), description='Number of completed DMA transfers') self._mem_mask = CSRStorage( @@ -128,17 +132,23 @@ def add_csrs(self): description = 'DRAM address mask for DMA transfers' ) self._data_mask = CSRStorage( - size = len(self.mem_mask), + size = len(self.data_mask), description = 'Pattern memory address mask' ) + self._data_div = CSRStorage( + size = len(self.data_mask), + description = 'Pattern memory address divisior-1' + ) self.comb += [ self.start.eq(self._start.re), self._ready.status.eq(self.ready), + self.modulo.eq(self._modulo.storage), self.count.eq(self._count.storage), self._done.status.eq(self.done), self.mem_mask.eq(self._mem_mask.storage), self.data_mask.eq(self._data_mask.storage), + self.data_div.eq(self._data_div.storage), ] @@ -158,11 +168,11 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): """.format(common=BISTModule.__doc__)) dma = LiteDRAMDMAWriter(dram_port, fifo_depth=4, fifo_buffered=True) - self.submodules += dma + self.submodules.dma = dma cmd_counter = Signal(32) - mem_addr = Signal.like(cmd_counter) - dram_addr = Signal.like(dma.sink.address) + mem_addr = Signal.like(self.data_mask) + self.dram_addr = dram_addr = Signal.like(dma.sink.address) wait_counter = Signal(max=3) @@ -177,7 +187,7 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): # DMA data may be inverted using AddressSelector self.submodules.inverter = RowDataInverter( - addr = dma.sink.address, + addr = dram_addr, data_in = self.data_port.dat_r, data_out = dma.sink.data, rowbits = rowbits, @@ -189,11 +199,14 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): self.ready.eq(1), If(self.start, NextValue(cmd_counter, 0), + NextValue(mem_addr, 0), NextState("COMPUTE_MEM_ADDR"), ) ) fsm.act("COMPUTE_MEM_ADDR", - NextValue(mem_addr, cmd_counter & self.data_mask), + If(~self.modulo, + NextValue(mem_addr, cmd_counter[:len(self.data_mask)] & self.data_mask), + ), If(cmd_counter >= self.count, NextState("READY") ).Else( @@ -205,7 +218,8 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): ) fsm.act("COMPUTE_DRAM_ADDR", NextValue(wait_counter, 0), - NextValue(dram_addr, self.addr_port.dat_r + (cmd_counter & self.mem_mask)), + NextValue(dram_addr, self.addr_port.dat_r + + (cmd_counter & self.mem_mask)), NextState("INVERT_COMPUTE"), ) fsm.act("INVERT_COMPUTE", @@ -218,6 +232,11 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): dma.sink.valid.eq(1), If(dma.sink.ready, NextValue(cmd_counter, cmd_counter + 1), + If(self.modulo & (mem_addr == self.data_div), + NextValue(mem_addr, 0), + ).Elif(self.modulo, + NextValue(mem_addr, mem_addr + 1), + ), NextState("COMPUTE_MEM_ADDR") ) ) @@ -225,6 +244,12 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): def add_csrs(self): super().add_csrs() self.inverter.add_csrs() + self._last_address = CSRStatus(size=32, description='Number of completed DMA transfers') + self.sync += [ + If(self.dma.sink.valid & self.dma.sink.ready, + self._last_address.status.eq(self.dram_addr) + ), + ] class Reader(BISTModule, AutoCSR, AutoDoc): @@ -278,7 +303,7 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): # ----------------- Address FSM ----------------- counter_addr = Signal(32) - mem_addr = Signal.like(counter_addr) + mem_addr = Signal.like(self.data_mask) dram_addr = Signal.like(dma.sink.address) self.comb += [ @@ -292,11 +317,14 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): fsm_addr.act("READY", If(self.start, NextValue(counter_addr, 0), + NextValue(mem_addr, 0), NextState("COMPUTE_MEM_ADDR"), ), ) fsm_addr.act("COMPUTE_MEM_ADDR", - NextValue(mem_addr, counter_addr & self.data_mask), + If(~self.modulo, + NextValue(mem_addr, counter_addr[:len(self.data_mask)] & self.data_mask), + ), If(counter_addr >= self.count, NextState("READY"), ).Else( @@ -307,7 +335,8 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): NextState("COMPUTE_DRAM_ADDR"), ) fsm_addr.act("COMPUTE_DRAM_ADDR", - NextValue(dram_addr, self.addr_port.dat_r + (counter_addr & self.mem_mask)), + NextValue(dram_addr, self.addr_port.dat_r + + (counter_addr & self.mem_mask)), NextState("PUSH_TO_FIFO"), ) fsm_addr.act("PUSH_TO_FIFO", @@ -320,13 +349,18 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): dma.sink.valid.eq(1), If(dma.sink.ready, NextValue(counter_addr, counter_addr + 1), + If(self.modulo & (mem_addr == self.data_div), + NextValue(mem_addr, 0), + ).Elif(self.modulo, + NextValue(mem_addr, mem_addr + 1), + ), NextState("COMPUTE_MEM_ADDR") ) ) # ------------- Pattern FSM ---------------- counter_gen = Signal(32) - data_mem_addr = Signal.like(counter_gen) + data_mem_addr = Signal.like(self.data_mask) wait_counter = Signal(max=3) # Unmatched memory offsets @@ -348,12 +382,15 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): self.ready.eq(1), If(self.start, NextValue(counter_gen, 0), + NextValue(data_mem_addr, 0), NextValue(self.error_count, 0), NextState("COMPUTE_MEM_ADDR"), ) ) fsm_pattern.act("COMPUTE_MEM_ADDR", - NextValue(data_mem_addr, counter_gen & self.data_mask), + If(~self.modulo, + NextValue(data_mem_addr, counter_gen & self.data_mask), + ), If(counter_gen >= self.count, NextState("READY") ).Else( @@ -372,13 +409,6 @@ def __init__(self, dram_port, pattern_mem, *, rowbits, row_shift): ), NextValue(wait_counter, wait_counter + 1), ) - fsm_pattern.act("WAIT", # TODO: we could pipeline the access - If(counter_gen >= self.count, - NextState("READY") - ).Else( - NextState("RD_DATA") - ) - ) fsm_pattern.act("RD_DATA", If(dma.source.valid, # we must now change FSM state in single cycle diff --git a/rowhammer_tester/scripts/hw_rowhammer.py b/rowhammer_tester/scripts/hw_rowhammer.py index 18ecde223..cd3a1deb6 100755 --- a/rowhammer_tester/scripts/hw_rowhammer.py +++ b/rowhammer_tester/scripts/hw_rowhammer.py @@ -37,11 +37,14 @@ def attack(self, row_tuple, read_count, progress_header=''): # Do not increment memory address self.wb.regs.reader_mem_mask.write(0x00000000) - self.wb.regs.reader_data_mask.write(len(row_tuple) - 1) + self.wb.regs.reader_modulo.write(1) + self.wb.regs.reader_data_div.write(len(row_tuple) - 1) + #self.wb.regs.reader_data_mask.write(len(row_tuple) - 1) # Attacked addresses - memwrite(self.wb, addresses, base=self.wb.mems.writer_pattern_addr.base) memwrite(self.wb, addresses, base=self.wb.mems.reader_pattern_addr.base) + memwrite(self.wb, ([0xaaaaaaaa]*16), + base=self.wb.mems.reader_pattern_data.base) # how many print('read_count: ' + str(int(read_count))) @@ -63,11 +66,11 @@ def progress(count): progress(r_count) if self.wb.regs.reader_ready.read(): break - else: - time.sleep(10 / 1e3) + time.sleep(10 / 1e3) progress(self.wb.regs.reader_done.read()) # also clears the value print() + self.wb.regs.reader_modulo.write(0) def check_errors(self, row_pattern): dma_data_width = self.settings.phy.dfi_databits * self.settings.phy.nphases diff --git a/rowhammer_tester/scripts/rowhammer.py b/rowhammer_tester/scripts/rowhammer.py index d23af43f4..7a5e8f595 100755 --- a/rowhammer_tester/scripts/rowhammer.py +++ b/rowhammer_tester/scripts/rowhammer.py @@ -216,20 +216,7 @@ def no_attack_sleep(self): print() - def run(self, row_pairs, pattern_generator, read_count, row_progress=16, verify_initial=False): - """ - Main part of the script. - First fills the memory with specified patterns, then optionally checks its integrity. - It disables refreshes if requested. - Next it executes the attack, one row pair at a time. - If refreshes were disabled, it reenables them. - It checks for errors, and if any were found, displays them. - """ - - # TODO: need to invert data when writing/reading, make sure Python integer inversion works correctly - if self.data_inversion: - raise NotImplementedError('Currently only HW rowhammer supports data inversion') - + def prepare_memory(self): print('\nPreparing ...') row_patterns = pattern_generator(self.rows) @@ -252,6 +239,23 @@ def run(self, row_pairs, pattern_generator, read_count, row_progress=16, verify_ self.display_errors(errors, read_count) return + + def run(self, row_pairs, pattern_generator, read_count, row_progress=16, verify_initial=False): + """ + Main part of the script. + First fills the memory with specified patterns, then optionally checks its integrity. + It disables refreshes if requested. + Next it executes the attack, one row pair at a time. + If refreshes were disabled, it reenables them. + It checks for errors, and if any were found, displays them. + """ + + # TODO: need to invert data when writing/reading, make sure Python integer inversion works correctly + if self.data_inversion: + raise NotImplementedError('Currently only HW rowhammer supports data inversion') + + self.prepare_memory() + if self.no_refresh: print('\nDisabling refresh ...') self.wb.regs.controller_settings_refresh.write(0) @@ -416,8 +420,6 @@ def main(row_hammer_cls): args.no_refresh = True if args.hammer_only: - if not args.payload_executor and len(args.hammer_only) != 2: - parser.error("") row_pairs = [tuple(args.hammer_only)] elif args.all_rows: if args.row_pair_distance < 0: diff --git a/rowhammer_tester/scripts/utils.py b/rowhammer_tester/scripts/utils.py index f3a7f134c..b61dbaac8 100755 --- a/rowhammer_tester/scripts/utils.py +++ b/rowhammer_tester/scripts/utils.py @@ -166,7 +166,7 @@ def compare(val, ref, fmt, nbytes=4): def memwrite(wb, data, base=0x40000000, burst=0xff): for i in range(0, len(data), burst): - wb.write(base + 4 * i, data[i:i + burst]) + wb.write(base + 4 * i, data[i:min(i + burst, len(data))]) def memread(wb, n, base=0x40000000, burst=0xff): diff --git a/tests/test_bist.py b/tests/test_bist.py index 0da5f665c..b2210ce2a 100644 --- a/tests/test_bist.py +++ b/tests/test_bist.py @@ -9,10 +9,13 @@ # DUT ---------------------------------------------------------------------------------------------- class BISTDUT(Module): - def __init__(self, address_width=32, data_width=128, pattern_mem_length=32, pattern_init=None, rowbits=5, row_shift=10): + def __init__(self, address_width=32, data_width=128, pattern_mem_length=32, pattern_init=None, rowbits=5, row_shift=10, + writer=False, reader=False): self.address_width = address_width self.data_width = data_width self.pattern_mem_length = pattern_mem_length + self.has_reader = reader + self.has_writer = writer self.submodules.pattern_mem = PatternMemory(data_width, pattern_mem_length, addr_width=address_width, pattern_init=pattern_init) @@ -23,13 +26,15 @@ def __init__(self, address_width=32, data_width=128, pattern_mem_length=32, patt inverter_kwargs = dict(rowbits=rowbits, row_shift=row_shift) - self.read_port = LiteDRAMNativePort(address_width=address_width, data_width=data_width, mode='read') - self.submodules.reader = Reader(self.read_port, self.pattern_mem, **inverter_kwargs) - self.reader.add_csrs() + if reader: + self.read_port = LiteDRAMNativePort(address_width=address_width, data_width=data_width, mode='read') + self.submodules.reader = Reader(self.read_port, self.pattern_mem, **inverter_kwargs) + self.reader.add_csrs() - self.write_port = LiteDRAMNativePort(address_width=address_width, data_width=data_width, mode='write') - self.submodules.writer = Writer(self.write_port, self.pattern_mem, **inverter_kwargs) - self.writer.add_csrs() + if writer: + self.write_port = LiteDRAMNativePort(address_width=address_width, data_width=data_width, mode='write') + self.submodules.writer = Writer(self.write_port, self.pattern_mem, **inverter_kwargs) + self.writer.add_csrs() # storage for port_handler (addr, we, data) self.commands = [] @@ -47,6 +52,8 @@ def default_port_handlers(self): @passive def read_handler(self, rdata_callback=None, read_delay=0): + if not self.has_reader: + return if rdata_callback is None: rdata_callback = lambda addr: 0xbaadc0de if not callable(rdata_callback): # passed a single value to always be returned @@ -81,6 +88,8 @@ def read_handler(self, rdata_callback=None, read_delay=0): @passive def write_handler(self, write_delay=1): + if not self.has_writer: + return while True: while not (yield self.write_port.cmd.valid): yield @@ -153,9 +162,13 @@ def generator(dut): yield from wait_or_timeout(100, module._ready.read) - dut = BISTDUT(pattern_init=pattern) + dut = BISTDUT(pattern_init=pattern, writer='writer' in bist_name, reader='reader' in bist_name) generators = [generator(dut), *dut.default_port_handlers()] - run_simulation(dut, generators) + if pattern == PATTERNS_ADDR_0: + pattern_name = 'PATTERNS_ADDR_0' + else: + pattern_name = 'PATTERNS_ADDR_INC' + run_simulation(dut, generators, vcd_name=bist_name + pattern_name + f'mem_inc:{mem_inc}.vcd') we = 0 if bist_name == 'reader' else 1 pattern_cycle = itertools.cycle(pattern) @@ -209,7 +222,7 @@ def generator(dut): yield from wait_or_timeout(500, dut.writer._ready.read) - dut = BISTDUT(pattern_init=pattern, rowbits=rowbits, row_shift=row_shift) + dut = BISTDUT(pattern_init=pattern, rowbits=rowbits, row_shift=row_shift, writer=True) generators = [generator(dut), *dut.default_port_handlers()] run_simulation(dut, generators) @@ -223,6 +236,45 @@ def generator(dut): expected = 0x55555555555555555555555555555555 if invert else 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa self.assertEqual(data, expected, msg=addr) + def test_multiple_addresses(self): + # specification + rowbits = 5 + row_shift = 12 # assuming just 2 bits for column+bank + divisor_mask = 0b00111 # modulo divisor = 8 + selection_mask = 0b00000000000000000000000010010010 # rows: 1, 4, 7 + + # fill whole memory with the same data + pattern = [(0x00, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa), + (0x40, 0x55555555555555555555555555555555), + (0x1040, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF), + (0x2040, 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB)] + + def generator(dut): + yield from dut.writer._count.write(4) + yield from dut.writer._mem_mask.write(0) + yield from dut.writer._data_mask.write(0x1f) + + yield from dut.writer.inverter._divisor_mask.write(0) + yield from dut.writer.inverter._selection_mask.write(0) + + yield from dut.writer._start.write(1) + yield from dut.writer._start.write(0) + + yield from wait_or_timeout(500, dut.writer._ready.read) + + dut = BISTDUT(pattern_init=pattern, rowbits=rowbits, row_shift=row_shift, writer=True) + generators = [generator(dut), *dut.default_port_handlers()] + run_simulation(dut, generators, vcd_name='test_multiple_addresses.vcd') + + # print() + # for addr, we, data in dut.commands: + # print('0x{:08x} {} 0x{:032x}'.format(addr, we, data)) + + for (addr, we, data), (expected_addr, expected_data) in zip(dut.commands, pattern): + self.assertEqual(we, 1) + self.assertEqual(data, expected_data, msg=addr) + self.assertEqual(addr, expected_addr, msg=addr) + # Reader ------------------------------------------------------------------------------------------- class TestReader(unittest.TestCase): @@ -271,7 +323,7 @@ def generator(dut): yield from wait_or_timeout(50, dut.reader._ready.read) self.assertEqual((yield from dut.reader._done.read()), count) - dut = BISTDUT(pattern_init=pattern) + dut = BISTDUT(pattern_init=pattern, reader=True) generators = [generator(dut), dut.read_handler(rdata_callback)] run_simulation(dut, generators) @@ -301,12 +353,12 @@ def generator(dut): while not (yield from dut.reader._ready.read()): yield - dut = BISTDUT(pattern_init=[(addr, 0) for addr in row_addresses]) + dut = BISTDUT(pattern_init=[(addr, 0) for addr in row_addresses], reader=True) generators = [ generator(dut), dut.read_handler(0), ] - run_simulation(dut, generators) + run_simulation(dut, generators, vcd_name="test_row_hammer_attack_pattern.vcd") # BIST Reader should cycle through the list of addresses we = 0 @@ -370,7 +422,7 @@ def generator(dut): yield from wait_or_timeout(500, dut.reader._ready.read) - dut = BISTDUT(pattern_init=pattern, rowbits=rowbits, row_shift=row_shift) + dut = BISTDUT(pattern_init=pattern, rowbits=rowbits, row_shift=row_shift, reader=True) generators = [generator(dut), dut.read_handler(rdata_callback)] run_simulation(dut, generators, vcd_name='sim.vcd') diff --git a/third_party/litedram b/third_party/litedram index c78de9458..504dae7cf 160000 --- a/third_party/litedram +++ b/third_party/litedram @@ -1 +1 @@ -Subproject commit c78de945880b74c1e3d852760dcc6ad2b1868a55 +Subproject commit 504dae7cf3831e4265306c34da5517da4fdfb84e