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
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
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
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
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
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
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
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)
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
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]
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