def setup_class(self): clock = Signal(bool(0)) reset = ResetSignal(0, active=1, async=True) # DECODER SIGNALS instr_hi = Signal(intbv(0)[8:]) pipe_dec_sig = decSignal() pipe_alu_op = Signal(alu_op_type.NOP) # Input Signals to Execute in_imm = Signal(intbv(0)[16:]) in_dm_addr = Signal(intbv(0)[DM_BITS:]) in_pc = Signal(intbv(0)[IM_BITS:]) out_acc = Signal(intbv(0)[16:]) out_dm_data = Signal(intbv(0)[16:]) fwd_accu = Signal(intbv(0)[16:]) ioin = inpSignal() self.signals = clock, reset, instr_hi, pipe_dec_sig, in_imm, \ in_dm_addr, in_pc, out_acc, out_dm_data self.decode_inst = decoder.pyleros_decoder(instr_hi, pipe_alu_op, pipe_dec_sig) self.exec_inst = execute.pyleros_exec(clock, reset, pipe_alu_op, pipe_dec_sig, in_imm, in_dm_addr, in_pc, \ out_acc, out_dm_data, fwd_accu, ioin, True) return self.decode_inst, self.exec_inst
def conv_decoder(): dec_sig = decSignal() alu_op = Signal(alu_op_type.NOP) instr_hi = Signal(intbv(0)[8:]) inst_dec = pyleros_decoder(instr_hi, alu_op, dec_sig) inst_dec.convert(hdl = 'VHDL', path = PATH)
def conv_decoder(): dec_sig = decSignal() alu_op = Signal(alu_op_type.NOP) instr_hi = Signal(intbv(0)[8:]) inst_dec = pyleros_decoder(instr_hi, alu_op, dec_sig) inst_dec.convert(hdl='VHDL', path=PATH)
def pyleros_fedec(clk, reset, back_acc, back_dm_data, fwd_accu, pipe_alu_op, pipe_dec, pipe_imme, pipe_dm_addr, pipe_pc, filename=None, debug=False): """The fedec module for pyleros, that is, the fetch and decode pipeline stage. The modules is purely combinatorial, except for the updating the pipeline register. The IM is instantied and only accessed in this stage. The main functions done here are decoding of instruction, setting up branch control signals, selection of other control signals(including the ALU ones), selection of DM address. an ALU operation on two local variables takes two cycles to execute and another cycle if the result needs to written back to a local variable Arguments (ports): clk: IN Clock signal reset: IN Async reset signal back_acc: IN Acc value accessed here, not written. This is needed for setting the branch control signals and for memory addredd of JAL back_dm_data: IN The data read from the DM, which is needed for an direct add or and indirect load/ store(which follows) fwd_accu: IN The value of the accumulator, forwarded from the execute stage to provide proper branching. Currently unused. pipe_dec: OUT List of the decode signals, pass on to the execute stage pipe_imme: OUT Immediate value, as taken from the lower bits of the instruction, pass on to execute stage pipe_dm_addr: OUT DM read addr, pipeline register pipe_pc: OUT the value of PC, pipeline register Parameters: filename: Name of the file or a list containing the instructions debug: Debugging mode, the processor prints various error messages """ im_addr = Signal(intbv(0)[IM_BITS:]) instr = Signal(intbv(0)[16:]) instr_hi = Signal(intbv(0)[8:]) branch_en = Signal(bool(0)) # PC start from 0x00 in this design, and each instruction is executed exactly once. # In the original design, PC started from 1. However, 0x00 is typically NOP pc = Signal(intbv(0)[IM_BITS:]) pc_next = Signal(intbv(0)[IM_BITS:]) pc_add = Signal(intbv(0)[IM_BITS:]) pc_op = Signal(intbv(0, min=-2**(IM_BITS - 1), max=2**(IM_BITS - 1) - 1)) decode = decSignal() alu_op = Signal(alu_op_type.NOP) # Instantiate the instruction memory im_inst = rom.pyleros_im(im_addr, instr, filename, debug) # Instantiate the decoder dec_inst = decoder.pyleros_decoder(instr_hi, alu_op, decode, debug) @always_comb def sync_sig(): # if __debug__: # if debug: # print("hi_bits:",instr[16:8]) instr_hi.next = instr[16:8] im_addr.next = pc @always_comb def mux_dm_addr(): offset_addr = intbv(back_dm_data + instr[8:0])[DM_BITS:] if decode.indls: # Indirect Addressing(with offset) # for indirect load/store # if __debug__: # if debug: # print("offset address: " + str(int(offset_addr))) pipe_dm_addr.next = offset_addr[DM_BITS:] else: # Direct Addressing # if __debug__: # if debug: # print("direct address: " + str(int(instr[DM_BITS:]))) pipe_dm_addr.next = instr[DM_BITS:] @always_comb def branch_sel(): acc_z = True # if not reset == reset.active: if back_acc == 0: acc_z = True else: acc_z = False branch_en.next = 0 if decode.br_op: br_type = instr[11:8] if br_type == 0b000: # BRANCH branch_en.next = True elif br_type == 0b001: # BRZ if acc_z: branch_en.next = True else: branch_en.next = False elif br_type == 0b010: # BRNZ if not acc_z: branch_en.next = True else: branch_en.next = False elif br_type == 0b011: # BRP if not back_acc[15]: branch_en.next = True else: branch_en.next = False elif br_type == 0b100: # BRN if back_acc[15]: branch_en.next = True else: branch_en.next = False # For selection of next PC address @always_comb def pc_addr(): # if __debug__: # if debug: # print('start', pc, pc_op, instr, back_acc, pc_add, instr) if branch_en == 1: # Sign extend the low 8 bits # of instruction pc_op.next = instr[8:].signed() else: pc_op.next = 1 # if __debug__: # if debug: # print(pc, pc_op) @always_comb def pc_next_set(): pc_add.next = intbv(pc + pc_op)[IM_BITS:] @always_comb def pc_mux(): # Add 1 or branch offset OR set the add # to the jump addr if decode.jal: pc_next.next = back_acc[IM_BITS:] else: pc_next.next = pc_add # if __debug__: # if debug: # print('end', pc, pc_op, instr, back_acc, pc_add, instr) @always_seq(clk.posedge, reset) def intr_pipe(): # if decode.add_sub == True: # Set the immediate value if decode.loadh: if __debug__: pipe_imme.next = intbv(0)[16:] pipe_imme.next[16:8] = instr[8:] pipe_imme.next[8:0] = intbv(0)[8:] else: pipe_imme.next = instr[8:] pipe_pc.next = pc_add pipe_dec.al_ena.next = decode.al_ena pipe_dec.ah_ena.next = decode.ah_ena pipe_dec.log_add.next = decode.log_add pipe_dec.add_sub.next = decode.add_sub pipe_dec.shr.next = decode.shr pipe_dec.sel_imm.next = decode.sel_imm pipe_dec.store.next = decode.store pipe_dec.outp.next = decode.outp pipe_dec.inp.next = decode.inp pipe_dec.br_op.next = decode.br_op pipe_dec.jal.next = decode.jal pipe_dec.loadh.next = decode.loadh pipe_dec.indls.next = decode.indls pipe_alu_op.next = alu_op pc.next = pc_next return instances()
def tb_fedec_top(): """Test the fetch/ decode module in pyleros """ def create_instr_list(): instr_list, bin_list = [], [] for instr in codes: if (not codes[instr][2]) or (instr == 'NOP') or (instr == 'LOADH') or (instr == 'STORE'): continue for trie in range(20): op1 = randrange(2**15) # 8-bit imm opd op2 = randrange(2**7) bin_code = codes[instr][0] # Immediate version bin_imme = bin_code | 0x01 instr_list.append([instr, op1, op2]) #Add operand op2 to instr bin_code = (bin_imme << 8) | (op2 & 0xff) bin_list.append(bin_code) return instr_list, bin_list clock = Signal(bool(0)) reset = ResetSignal(0, active=1, async=True) # FEDEC SIGNALS back_acc, back_dm_data = [Signal(intbv(0)[16:])] * 2 pipe_imme = Signal(intbv(0)[16:]) pipe_dm_addr = Signal(intbv(0)[DM_BITS:]) pipe_pc = Signal(intbv(0)[IM_BITS:]) fwd_accu = Signal(intbv(0)[16:]) out_dec = decSignal() test_dec = decSignal() alu_op = Signal(alu_op_type.NOP) pipe_alu_op = Signal(alu_op_type.NOP) instr_hi = Signal(intbv(0)[8:]) dec_inst = decoder.pyleros_decoder(instr_hi, alu_op, test_dec) ioin = inpSignal() # ALU SIGNALS # out_list alu_acc = Signal(intbv(0)[16:]) alu_opd = Signal(intbv(0)[16:]) alu_res = Signal(intbv(0)[16:]) instr_list, bin_list = create_instr_list() alu_inst = alu.pyleros_alu(pipe_alu_op, out_dec, alu_acc, alu_opd, alu_res, ioin) fedec_inst = fedec.pyleros_fedec(clock, reset, back_acc, back_dm_data, fwd_accu, pipe_alu_op, out_dec, pipe_imme, pipe_dm_addr, pipe_pc, filename=bin_list, debug = True) @always(delay(100)) def tbclk(): clock.next = not clock @instance def tbstim(): # To start the fetch/decoding # reset.next = not reset.active print("bin_list") for i in range(10): print(bin_list[i], instr_list[i][2]) # In the first cycle nothing happens since # only the instuction is updated, and the # decoder, the output from fedec doesn't change # till after the second cycle. yield clock.posedge ninstr = len(instr_list) for addr in range(1,ninstr - 1): # if addr == 10: # raise StopSimulation print(addr) # for the alu, op1 signifies the acc adn # op2 the opd instr, op1, op2 = instr_list[addr] instr_hi.next = (codes[instr][0] | 0x01) yield clock.posedge yield delay(3) for sig in dlist: assert test_dec.signals[int(sig)] == out_dec.signals[int(sig)] print("Cmp Imm", op2, pipe_imme) alu_acc.next = op1 alu_opd.next = pipe_imme yield delay(3) # print("alu ops", op1, alu_acc, pipe_imme, alu_opd) # print("alu types", type(op1), type(alu_acc), type(pipe_imme), type(alu_opd)) # print(out_dec[int(dec_op_type.add_sub)], out_dec[int(dec_op_type.log_add)]) #check for correct result if instr == 'NOP': pass elif instr == 'ADD': assert alu_res == int(op1) + int(op2) elif instr == 'SUB': assert alu_res == ((op1 - op2) & 0xffff) elif instr == 'SHR': assert alu_res == (op1 & 0xffff) >> 1 elif instr == 'AND': assert alu_res == (op1 & op2) & 0xffff elif instr == 'OR': assert alu_res == (op1 | op2) & 0xffff elif instr == 'XOR': assert alu_res == (op1 ^ op2) & 0xffff elif instr == 'LOAD': assert alu_res == op2 & 0xffff raise StopSimulation return instances()
def tb_alu_top(imen=False): """Test the alu module in pyleros """ clock = Signal(bool(0)) reset = ResetSignal(0, active=1, async=True) # DECODER SIGNALS instr_hi = Signal(intbv(0)[8:]) dec_signal = decSignal() alu_op = Signal(alu_op_type.NOP) ioin = inpSignal() decode_inst = decoder.pyleros_decoder(instr_hi, alu_op, dec_signal) # ALU SIGNALS # dec_signal alu_acc = Signal(intbv(0)[16:]) alu_opd = Signal(intbv(0)[16:]) alu_res = Signal(intbv(0)[16:]) alu_inst = alu.pyleros_alu(alu_op, dec_signal, alu_acc, alu_opd, alu_res, ioin) rd_addr = Signal(intbv(0)[IM_BITS:]) rd_data = Signal(intbv(0)[16:]) instr_list, bin_list = [], [] # Create instruction memory if enabled. if imen: for instr in codes: for trie in range(20): op1 = randrange(2**15) op2 = randrange(2**15) bin_code = codes[instr][0] instr_list.append([instr, op1, op2]) bin_code = (bin_code << 8) bin_list.append(bin_code) inst_im = rom.pyleros_im(rd_addr, rd_data, IM_array=tuple(bin_list)) @always(delay(10)) def tbclk(): clock.next = not clock # def _bench_alu(): @instance def tbstim(): for i in range(5): yield clock.posedge if imen: ninstr = len(instr_list) for addr in range(ninstr): instr, op1, op2 = instr_list[addr] rd_addr.next = intbv(addr)[IM_BITS:] yield clock.posedge yield delay(2) assert rd_data == bin_list[rd_addr] upp = (int(rd_data)) >> 8 assert upp == codes[instr][0] instr_hi.next = intbv(upp)[8:] alu_acc.next = op1 alu_opd.next = op2 yield delay(33) #check for correct result if instr == 'NOP': pass elif instr == 'ADD': assert alu_res == ((op1 + op2) & 0xffff) elif instr == 'SUB': assert alu_res == ((op1 - op2) & 0xffff) elif instr == 'SHR': assert alu_res == (op1 & 0xffff) >> 1 elif instr == 'AND': assert alu_res == (op1 & op2) & 0xffff elif instr == 'OR': assert alu_res == (op1 | op2) & 0xffff elif instr == 'XOR': assert alu_res == (op1 ^ op2) & 0xffff elif instr == 'LOAD': assert alu_res == op2 & 0xffff else: for instr in codes: for i in range(10): # Choose random operands op1 = randrange(2**15) op2 = randrange(2**15) # Set the decoder input instr_op = codes[instr][0] instr_hi.next = instr_op alu_acc.next = op1 alu_opd.next = op2 # Wait for operation yield delay(33) #check for correct result if instr == 'NOP': pass elif instr == 'ADD': assert alu_res == ((op1 + op2) & 0xffff) elif instr == 'SUB': assert alu_res == ((op1 - op2) & 0xffff) elif instr == 'SHR': assert alu_res == (op1 & 0xffff) >> 1 elif instr == 'AND': assert alu_res == (op1 & op2) & 0xffff elif instr == 'OR': assert alu_res == (op1 | op2) & 0xffff elif instr == 'XOR': assert alu_res == (op1 ^ op2) & 0xffff elif instr == 'LOAD': assert alu_res == op2 & 0xffff raise StopSimulation return instances() #, decode_inst
def tb_dec_top(args=None): """Test the decoder module in pyleros """ clock = Signal(bool(0)) reset = ResetSignal(0, active=0, async=True) # the high 8 bits of the instruction instr_hi = Signal(intbv(0)[8:]) dec_signal = decSignal() alu_op = Signal(alu_op_type.NOP) @always(delay(10)) def tbclk(): clock.next = not clock # instantiate the decoder decode_inst = decoder.pyleros_decoder(instr_hi, alu_op, dec_signal) @instance def tbstim(): for i in range(5): yield clock.posedge for instr in codes: # set the input to the decoder instr_op = codes[instr][0] instr_hi.next = instr_op yield delay(33) # check for correct decode for cs in dlist: if cs in codes[instr][1]: assert dec_signal.signals[int(cs)] == True else: assert dec_signal.signals[int(cs)] == False yield delay(33) if codes[instr][2] == True: instr_imm = instr_op | 0x01 instr_hi.next = instr_imm yield delay(33) # check for correct decode for cs in dlist: if (cs in codes[instr][1]) or (cs == dec_op_type.sel_imm): assert dec_signal.signals[int(cs)] == True else: assert dec_signal.signals[int(cs)] == False for ii in range(5): yield clock.posedge raise StopSimulation return instances()
def pyleros_fedec(clk, reset, back_acc, back_dm_data, fwd_accu, pipe_alu_op, pipe_dec, pipe_imme, pipe_dm_addr, pipe_pc, filename=None, debug=False): """The fedec module for pyleros, that is, the fetch and decode pipeline stage. The modules is purely combinatorial, except for the updating the pipeline register. The IM is instantied and only accessed in this stage. The main functions done here are decoding of instruction, setting up branch control signals, selection of other control signals(including the ALU ones), selection of DM address. an ALU operation on two local variables takes two cycles to execute and another cycle if the result needs to written back to a local variable Arguments (ports): clk: IN Clock signal reset: IN Async reset signal back_acc: IN Acc value accessed here, not written. This is needed for setting the branch control signals and for memory addredd of JAL back_dm_data: IN The data read from the DM, which is needed for an direct add or and indirect load/ store(which follows) fwd_accu: IN The value of the accumulator, forwarded from the execute stage to provide proper branching. Currently unused. pipe_dec: OUT List of the decode signals, pass on to the execute stage pipe_imme: OUT Immediate value, as taken from the lower bits of the instruction, pass on to execute stage pipe_dm_addr: OUT DM read addr, pipeline register pipe_pc: OUT the value of PC, pipeline register Parameters: filename: Name of the file or a list containing the instructions debug: Debugging mode, the processor prints various error messages """ im_addr = Signal(intbv(0)[IM_BITS:]) instr = Signal(intbv(0)[16:]) instr_hi = Signal(intbv(0)[8:]) branch_en = Signal(bool(0)) # PC start from 0x00 in this design, and each instruction is executed exactly once. # In the original design, PC started from 1. However, 0x00 is typically NOP pc = Signal(intbv(0)[IM_BITS:]) pc_next = Signal(intbv(0)[IM_BITS:]) pc_add = Signal(intbv(0)[IM_BITS:]) pc_op = Signal(intbv(0, min = -2**(IM_BITS - 1), max = 2**(IM_BITS - 1) - 1)) decode = decSignal() alu_op = Signal(alu_op_type.NOP) # Instantiate the instruction memory im_inst = rom.pyleros_im(im_addr, instr, filename, debug) # Instantiate the decoder dec_inst = decoder.pyleros_decoder(instr_hi, alu_op, decode, debug) @always_comb def sync_sig(): # if __debug__: # if debug: # print("hi_bits:",instr[16:8]) instr_hi.next = instr[16:8] im_addr.next = pc @always_comb def mux_dm_addr(): offset_addr = intbv(back_dm_data + instr[8:0])[DM_BITS:] if decode.indls: # Indirect Addressing(with offset) # for indirect load/store # if __debug__: # if debug: # print("offset address: " + str(int(offset_addr))) pipe_dm_addr.next = offset_addr[DM_BITS:] else: # Direct Addressing # if __debug__: # if debug: # print("direct address: " + str(int(instr[DM_BITS:]))) pipe_dm_addr.next = instr[DM_BITS:] @always_comb def branch_sel(): acc_z = True # if not reset == reset.active: if back_acc == 0: acc_z = True else: acc_z = False branch_en.next = 0 if decode.br_op: br_type = instr[11:8] if br_type == 0b000: # BRANCH branch_en.next = True elif br_type == 0b001: # BRZ if acc_z: branch_en.next = True else: branch_en.next = False elif br_type == 0b010: # BRNZ if not acc_z: branch_en.next = True else: branch_en.next = False elif br_type == 0b011: # BRP if not back_acc[15]: branch_en.next = True else: branch_en.next = False elif br_type == 0b100: # BRN if back_acc[15]: branch_en.next = True else: branch_en.next = False # For selection of next PC address @always_comb def pc_addr(): # if __debug__: # if debug: # print('start', pc, pc_op, instr, back_acc, pc_add, instr) if branch_en == 1: # Sign extend the low 8 bits # of instruction pc_op.next = instr[8:].signed() else: pc_op.next = 1 # if __debug__: # if debug: # print(pc, pc_op) @always_comb def pc_next_set(): pc_add.next = intbv(pc + pc_op)[IM_BITS:] @always_comb def pc_mux(): # Add 1 or branch offset OR set the add # to the jump addr if decode.jal: pc_next.next = back_acc[IM_BITS:] else: pc_next.next = pc_add # if __debug__: # if debug: # print('end', pc, pc_op, instr, back_acc, pc_add, instr) @always_seq(clk.posedge, reset) def intr_pipe(): # if decode.add_sub == True: # Set the immediate value if decode.loadh: if __debug__: pipe_imme.next = intbv(0)[16:] pipe_imme.next[16:8] = instr[8:] pipe_imme.next[8:0] = intbv(0)[8:] else: pipe_imme.next = instr[8:] pipe_pc.next = pc_add pipe_dec.al_ena.next = decode.al_ena pipe_dec.ah_ena.next = decode.ah_ena pipe_dec.log_add.next = decode.log_add pipe_dec.add_sub.next = decode.add_sub pipe_dec.shr.next = decode.shr pipe_dec.sel_imm.next = decode.sel_imm pipe_dec.store.next = decode.store pipe_dec.outp.next = decode.outp pipe_dec.inp.next = decode.inp pipe_dec.br_op.next = decode.br_op pipe_dec.jal.next = decode.jal pipe_dec.loadh.next = decode.loadh pipe_dec.indls.next = decode.indls pipe_alu_op.next = alu_op pc.next = pc_next return instances()
def tb_fedec_top(): """Test the fetch/ decode module in pyleros """ def create_instr_list(): instr_list, bin_list = [], [] for instr in codes: if (not codes[instr][2]) or (instr == 'NOP') or ( instr == 'LOADH') or (instr == 'STORE'): continue for trie in range(20): op1 = randrange(2**15) # 8-bit imm opd op2 = randrange(2**7) bin_code = codes[instr][0] # Immediate version bin_imme = bin_code | 0x01 instr_list.append([instr, op1, op2]) #Add operand op2 to instr bin_code = (bin_imme << 8) | (op2 & 0xff) bin_list.append(bin_code) return instr_list, bin_list clock = Signal(bool(0)) reset = ResetSignal(0, active=1, async=True) # FEDEC SIGNALS back_acc, back_dm_data = [Signal(intbv(0)[16:])] * 2 pipe_imme = Signal(intbv(0)[16:]) pipe_dm_addr = Signal(intbv(0)[DM_BITS:]) pipe_pc = Signal(intbv(0)[IM_BITS:]) fwd_accu = Signal(intbv(0)[16:]) out_dec = decSignal() test_dec = decSignal() alu_op = Signal(alu_op_type.NOP) pipe_alu_op = Signal(alu_op_type.NOP) instr_hi = Signal(intbv(0)[8:]) dec_inst = decoder.pyleros_decoder(instr_hi, alu_op, test_dec) ioin = inpSignal() # ALU SIGNALS # out_list alu_acc = Signal(intbv(0)[16:]) alu_opd = Signal(intbv(0)[16:]) alu_res = Signal(intbv(0)[16:]) instr_list, bin_list = create_instr_list() alu_inst = alu.pyleros_alu(pipe_alu_op, out_dec, alu_acc, alu_opd, alu_res, ioin) fedec_inst = fedec.pyleros_fedec(clock, reset, back_acc, back_dm_data, fwd_accu, pipe_alu_op, out_dec, pipe_imme, pipe_dm_addr, pipe_pc, filename=bin_list, debug=True) @always(delay(100)) def tbclk(): clock.next = not clock @instance def tbstim(): # To start the fetch/decoding # reset.next = not reset.active print("bin_list") for i in range(10): print(bin_list[i], instr_list[i][2]) # In the first cycle nothing happens since # only the instuction is updated, and the # decoder, the output from fedec doesn't change # till after the second cycle. yield clock.posedge ninstr = len(instr_list) for addr in range(1, ninstr - 1): # if addr == 10: # raise StopSimulation print(addr) # for the alu, op1 signifies the acc adn # op2 the opd instr, op1, op2 = instr_list[addr] instr_hi.next = (codes[instr][0] | 0x01) yield clock.posedge yield delay(3) for sig in dlist: assert test_dec.signals[int(sig)] == out_dec.signals[int( sig)] print("Cmp Imm", op2, pipe_imme) alu_acc.next = op1 alu_opd.next = pipe_imme yield delay(3) # print("alu ops", op1, alu_acc, pipe_imme, alu_opd) # print("alu types", type(op1), type(alu_acc), type(pipe_imme), type(alu_opd)) # print(out_dec[int(dec_op_type.add_sub)], out_dec[int(dec_op_type.log_add)]) #check for correct result if instr == 'NOP': pass elif instr == 'ADD': assert alu_res == int(op1) + int(op2) elif instr == 'SUB': assert alu_res == ((op1 - op2) & 0xffff) elif instr == 'SHR': assert alu_res == (op1 & 0xffff) >> 1 elif instr == 'AND': assert alu_res == (op1 & op2) & 0xffff elif instr == 'OR': assert alu_res == (op1 | op2) & 0xffff elif instr == 'XOR': assert alu_res == (op1 ^ op2) & 0xffff elif instr == 'LOAD': assert alu_res == op2 & 0xffff raise StopSimulation return instances()