Esempio n. 1
0
 def to_program(self) -> Program:
     '''Write a series of disjoint snippets to make a program'''
     # Find the size of the memory that we can access. Both memories start
     # at address 0: a strict Harvard architecture. (mems[x][0] is the LMA
     # for memory x, not the VMA)
     mems = get_memory_layout()
     imem_lma, imem_size = mems['IMEM']
     dmem_lma, dmem_size = mems['DMEM']
     program = Program(imem_lma, imem_size, dmem_lma, dmem_size)
     self.insert_into_program(program)
     return program
Esempio n. 2
0
def gen_program(start_addr: int, size: int,
                insns_file: InsnsFile) -> Tuple[InitData, List[Snippet]]:
    '''Generate a random program for OTBN

    start_addr is the reset address (the value that should be programmed into
    the START_ADDR register). size gives a rough upper bound for the number of
    instructions that will be executed by the generated program.

    Returns (init_data, snippets, program). init_data is a dict mapping (4-byte
    aligned) address to u32s that should be loaded into data memory before
    starting the program. snippets is a list of instruction snippets. program
    is the generated program (from flattening them both).

    '''

    # Find the size of the memory that we can access. Both memories start
    # at address 0: a strict Harvard architecture. (mems[x][0] is the LMA
    # for memory x, not the VMA)
    mems = get_memory_layout()
    imem_lma, imem_size = mems['IMEM']
    dmem_lma, dmem_size = mems['DMEM']

    assert start_addr <= imem_size - 4
    assert start_addr & 3 == 0

    program = Program(imem_lma, imem_size, dmem_lma, dmem_size)
    model = Model(dmem_size, start_addr)

    # Generate some initialised data to start with. Otherwise, it takes a while
    # before we start issuing loads (because we need stores to happen first).
    # Tell the model that we've done so.
    init_data = InitData.gen(dmem_size)
    for addr in init_data.keys():
        model.touch_mem('dmem', addr, 4)

    generators = SnippetGens(insns_file)
    snippets = []

    while size > 0:
        snippet, done, new_size = generators.gen(size, model, program)
        snippets.append(snippet)
        if done:
            break

        # Each new snippet should consume some of size to guarantee
        # termination.
        assert new_size < size
        size = new_size

    return init_data, snippets
Esempio n. 3
0
def load_elf(sim: OTBNSim, path: str) -> Optional[int]:
    '''Load ELF file at path and inject its contents into sim

    Returns the expected end address, if set, otherwise None.

    '''
    mems = get_memory_layout()
    imem_desc = mems['IMEM']
    dmem_desc = mems['DMEM']

    imem_bytes, dmem_bytes, exp_end = _read_elf(path, imem_desc, dmem_desc)
    imem_insns = decode_bytes(0, imem_bytes)

    sim.load_program(imem_insns)
    sim.load_data(dmem_bytes)
    return exp_end
Esempio n. 4
0
    def __init__(self) -> None:
        self.gprs = GPRs()
        self.wdrs = RegFile('w', 256, 32)

        self.wsrs = WSRFile()
        self.csrs = CSRFile()

        self.pc = 0
        self._pc_next_override = None  # type: Optional[int]

        _, imem_size = get_memory_layout()['IMEM']
        self.imem_size = imem_size

        self.dmem = Dmem()

        self.fsm_state = FsmState.IDLE

        self.loop_stack = LoopStack()
        self.ext_regs = OTBNExtRegs()

        self._err_bits = 0
        self.pending_halt = False

        self.rnd_256b_counter = 0
        self.urnd_256b_counter = 0

        self.rnd_set_flag = False

        self.rnd_req = 0
        self.rnd_cdc_pending = False
        self.urnd_cdc_pending = False

        self.rnd_cdc_counter = 0
        self.urnd_cdc_counter = 0

        self.rnd_256b = 0
        self.urnd_256b = 4 * [0]
        self.urnd_64b = 0

        # To simulate injecting integrity errors, we set a flag to say that
        # IMEM is no longer readable without getting an error. This can't take
        # effect instantly because the RTL's prefetch stage (which we don't
        # model except for matching its timing) has a copy of the next
        # instruction. Make this a counter, decremented once per cycle. When we
        # get to zero, we set the flag.
        self._time_to_imem_invalidation = None  # type: Optional[int]
        self.invalidated_imem = False
Esempio n. 5
0
def gen_program(config: Config, fuel: int,
                insns_file: InsnsFile) -> Tuple[InitData, Snippet, int]:
    '''Generate a random program for OTBN

    fuel gives a rough upper bound for the number of instructions that will be
    executed by the generated program.

    Returns (init_data, snippet, end_addr). init_data is a dict mapping (4-byte
    aligned) address to u32s that should be loaded into data memory before
    starting the program. snippets is a tree of instruction snippets. end_addr
    is the expected end address.

    '''

    # Find the size of the memory that we can access. Both memories start
    # at address 0: a strict Harvard architecture. (mems[x][0] is the LMA
    # for memory x, not the VMA)
    mems = get_memory_layout()
    imem_lma, imem_size = mems['IMEM']
    dmem_lma, dmem_bus_size = mems['DMEM']

    # The actual size of DMEM is twice what we get from reggen
    dmem_size = 2 * dmem_bus_size

    program = Program(imem_lma, imem_size, dmem_lma, dmem_size)
    model = Model(dmem_size, fuel)

    # Generate some initialised data to start with. Otherwise, it takes a while
    # before we start issuing loads (because we need stores to happen first).
    # Tell the model that we've done so.
    #
    # Note that we only use the first half of DMEM for initialised data,
    # because it needs to be loaded over the bus and only the first half is
    # visible.
    init_data = InitData.gen(dmem_bus_size)
    for addr in init_data.keys():
        model.touch_mem('dmem', addr, 4)

    try:
        gens = SnippetGens(config, insns_file)
    except ValueError as err:
        raise RuntimeError(
            'Failed to initialise snippet generators: {}'.format(
                err)) from None

    snippet, end_addr = gens.gen_program(model, program)
    return init_data, snippet, end_addr
Esempio n. 6
0
def gen_program(config: Config, start_addr: int, fuel: int,
                insns_file: InsnsFile) -> Tuple[InitData, Snippet]:
    '''Generate a random program for OTBN

    start_addr is the reset address (the value that should be programmed into
    the START_ADDR register). fuel gives a rough upper bound for the number of
    instructions that will be executed by the generated program.

    Returns (init_data, snippets, program). init_data is a dict mapping (4-byte
    aligned) address to u32s that should be loaded into data memory before
    starting the program. snippets is a list of instruction snippets. program
    is the generated program (from flattening them both).

    '''

    # Find the size of the memory that we can access. Both memories start
    # at address 0: a strict Harvard architecture. (mems[x][0] is the LMA
    # for memory x, not the VMA)
    mems = get_memory_layout()
    imem_lma, imem_size = mems['IMEM']
    dmem_lma, dmem_size = mems['DMEM']

    assert start_addr <= imem_size - 4
    assert start_addr & 3 == 0

    program = Program(imem_lma, imem_size, dmem_lma, dmem_size)
    model = Model(dmem_size, start_addr, fuel)

    # Generate some initialised data to start with. Otherwise, it takes a while
    # before we start issuing loads (because we need stores to happen first).
    # Tell the model that we've done so.
    init_data = InitData.gen(dmem_size)
    for addr in init_data.keys():
        model.touch_mem('dmem', addr, 4)

    try:
        gens = SnippetGens(config, insns_file)
    except ValueError as err:
        raise RuntimeError(
            'Failed to initialise snippet generators: {}'.format(
                err)) from None

    snippet = gens.gen_rest(model, program)
    return init_data, snippet
Esempio n. 7
0
def gen_program(start_addr: int, size: int,
                insns_file: InsnsFile) -> Tuple[List[Snippet], Program]:
    '''Generate a random program for OTBN

    start_addr is the reset address (the value that should be programmed into
    the START_ADDR register). size gives a rough upper bound for the number of
    instructions that will be executed by the generated program.

    Returns (snippets, program) where snippets is a list of instruction
    snippets and program is the generated program.

    '''

    # Find the size of the memory that we can access. Both memories start
    # at address 0: a strict Harvard architecture. (mems[x][0] is the LMA
    # for memory x, not the VMA)
    mems = get_memory_layout()
    imem_lma, imem_size = mems['IMEM']
    dmem_size = mems['DMEM'][1]

    assert start_addr <= imem_size - 4
    assert start_addr & 3 == 0

    program = Program(imem_lma, imem_size)
    model = Model(dmem_size, start_addr)

    generators = SnippetGens(insns_file)
    snippets = []

    while size > 0:
        snippet, done, new_size = generators.gen(size, model, program)
        snippets.append(snippet)
        if done:
            break

        # Each new snippet should consume some of size to guarantee
        # termination.
        assert new_size < size
        size = new_size

    return snippets, program
Esempio n. 8
0
def load_elf(sim: Simulator, path: str) -> None:
    '''Load contents of ELF file at path'''
    mems = get_memory_layout()
    imem_desc = mems['IMEM']
    dmem_desc = mems['DMEM']

    imem_segs, dmem_segs = _get_elf_segments(path, imem_desc, dmem_desc)

    imem_bytes = _flatten_segments(imem_segs, imem_desc)
    dmem_bytes = _flatten_segments(dmem_segs, dmem_desc)

    assert len(imem_bytes) <= imem_desc[1]
    if len(imem_bytes) & 3:
        raise RuntimeError('ELF file at {!r} has imem data of length {}: '
                           'not a multiple of 4.'.format(
                               path, len(imem_bytes)))

    imem_insns = decode_bytes(imem_bytes, RV32IXotbn)

    sim.load_program(imem_insns)
    sim.load_data(dmem_bytes)
Esempio n. 9
0
    def __init__(self) -> None:
        self.gprs = GPRs()
        self.wdrs = RegFile('w', 256, 32)

        self.wsrs = WSRFile()
        self.csrs = CSRFile()

        self.pc = 0
        self.pc_next = None  # type: Optional[int]

        self.start_addr = None  # type: Optional[int]

        _, imem_size = get_memory_layout()['IMEM']
        self.imem_size = imem_size

        self.dmem = Dmem()

        # Stalling support: Instructions can indicate they should stall by
        # returning false from OTBNInsn.pre_execute. For non instruction related
        # stalls setting self.non_insn_stall will produce a stall.
        #
        # As a special case, we stall until the URND reseed is completed then
        # stall for one more cycle before fetching the first instruction (to
        # match the behaviour of the RTL). This is modelled by setting
        # self._start_stall, self._urnd_stall and self.non_insn_stall
        self.non_insn_stall = False
        self._start_stall = False
        self._urnd_stall = False

        self.loop_stack = LoopStack()
        self.ext_regs = OTBNExtRegs()
        self.running = False

        self._err_bits = 0
        self.pending_halt = False

        self._new_rnd_data = None  # type: Optional[int]
        self._urnd_reseed_complete = False
Esempio n. 10
0
    def __init__(self) -> None:
        _, dmem_bus_size = get_memory_layout()['DMEM']

        # DMEM is actually twice as big as the layout from reggen would have us
        # believe!
        dmem_size = 2 * dmem_bus_size

        # Check the arguments look sensible, to avoid allocating massive chunks
        # of memory. We know we won't have more than 1 MiB of DMEM.
        if dmem_size > 1024 * 1024:
            raise RuntimeError(
                'Implausibly large DMEM size: {}'.format(dmem_size))

        # The native width for the OTBN wide side is 256 bits. This means that
        # dmem_size needs to be divisible by 32.
        if dmem_size % 32:
            raise RuntimeError(
                'DMEM size ({}) is not divisible by 32.'.format(dmem_size))

        # We represent the contents of DMEM as a list of 32-bit words (unlike
        # the RTL, which uses a 256-bit array). An entry in self.data is None
        # if the word has invalid integrity bits and we'll get an error if we
        # try to read it. Otherwise, we store the integer value.
        num_words = dmem_size // 4
        self.data = [None] * num_words  # type: List[Optional[int]]

        # Because it's an actual memory, stores to DMEM take two cycles in the
        # RTL. We wouldn't need to model this except that a DMEM invalidation
        # (used by the testbench to model integrity errors) shouldn't trash
        # pending writes. We do things in two steps: firstly, we do the
        # trace/commit dance that all the other blocks do. A memory write will
        # generate a trace entry which will appear in changes() at the end of
        # this cycle. However, the first commit() will then move it to the
        # self.pending list. Entries here will only make it to self.data on the
        # next commit().
        self.trace = []  # type: List[TraceDmemStore]
        self.pending = {}  # type: Dict[int, int]
Esempio n. 11
0
    def __init__(self) -> None:
        self.gprs = GPRs()
        self.wdrs = RegFile('w', 256, 32)

        self.wsrs = WSRFile()
        self.csrs = CSRFile()

        self.pc = 0
        self._pc_next_override = None  # type: Optional[int]

        _, imem_size = get_memory_layout()['IMEM']
        self.imem_size = imem_size

        self.dmem = Dmem()

        self._fsm_state = FsmState.IDLE
        self._next_fsm_state = FsmState.IDLE

        self.loop_stack = LoopStack()
        self.ext_regs = OTBNExtRegs()

        self._err_bits = 0
        self.pending_halt = False

        self.rnd_256b_counter = 0
        self.urnd_256b_counter = 0

        self.rnd_set_flag = False

        self.rnd_req = 0
        self.rnd_cdc_pending = False
        self.urnd_cdc_pending = False

        self.rnd_cdc_counter = 0
        self.urnd_cdc_counter = 0

        self.rnd_256b = 0
        self.urnd_256b = 4 * [0]
        self.urnd_64b = 0

        self.imem_req_pending = 0
        self.dmem_req_pending = 0

        # To simulate injecting integrity errors, we set a flag to say that
        # IMEM is no longer readable without getting an error. This can't take
        # effect instantly because the RTL's prefetch stage (which we don't
        # model except for matching its timing) has a copy of the next
        # instruction. Make this a counter, decremented once per cycle. When we
        # get to zero, we set the flag.
        self._time_to_imem_invalidation = None  # type: Optional[int]
        self.invalidated_imem = False

        # This flag controls whether we do the secure wipe sequence after
        # finishing an operation. Eventually it will be unconditionally enabled
        # (once everything works together properly).
        self.secure_wipe_enabled = False

        # This is the number of cycles left for wiping. When we're in the
        # WIPING_GOOD or WIPING_BAD state, this should be a non-negative
        # number. Initialise to -1 to catch bugs if we forget to set it.
        self.wipe_cycles = -1

        # If this is nonzero, there's been an error injected from outside. The
        # Sim.step function will OR these bits into err_bits and stop at the
        # end of the next cycle. (We don't just call stop_at_end_of_cycle
        # because this runs earlier in the cycle than step(), which would mean
        # we wouldn't see the final instruction get executed and then
        # cancelled).
        self.injected_err_bits = 0

        # If this is set, all software errors should result in the model status
        # being locked.
        self.software_errs_fatal = False