def bus_serializer(rst, clk, ra_rdy, ra_vld, ra_data, rd_rdy, rd_vld, rd_data, ls_re, ls_rd): ''' Serializes bus read access to registers: maps registers to address space, guarantees atomic register read ra_rdy, ra_vld, ra_data - input address handshake interface rd_rdy, rd_vld, rd_data - output data handshake interface ls_re - list of read enable signals, one signal per register ls_rd - list of read data signals, on signal per register Current behavior: - Suppose we have: - 20 bit register X - 8 bit read data bus - X is mapped to bus addresses: X[ 7: 0] -> addr i X[15: 8] -> addr i+1 X[19:16] -> addr i+2 - To read X we have to do the following: d0 = read(addr=i) d1 = read(addr=i+1) d2 = read(addr=i+2) note: read(addr) represents bus transactions via the read bus interface (ra_rdy, ra_vld, ra_data, rd_rdy, rd_vld, rd_data) - What happens under the hood: d0 = read(addr=i) - reads the whole X into an intermediate temporary register (atomic read), and returns d0 = X[ 7: 0] d1 = read(addr=i+1) - reads d1 = X[15: 8] from the intermediate temporary register d2 = read(addr=i+2) - reads d2 = X[19:16] from the intermediate temporary register Reading X is triggered by a read from addr i, the first address of a register. If we just read from address i+1 or i+2 without before that reading from address i, we will be reading some dummy data from the intermediate temporary register. TODO: May be, read X into the temporary register every time there is non-sequential access to some of the register addresses. non-sequential access - registers are not accessed sequentially i, i+1, i+2 ''' NUM_STAGES = 1 stage1_en = Signal(bool(0)) stop1_rx = Signal(bool(0)) stop1_tx = Signal(bool(0)) pipe1_ctrl_i = pipeline_control(rst=rst, clk=clk, rx_vld=ra_vld, rx_rdy=ra_rdy, tx_vld=rd_vld, tx_rdy=rd_rdy, stage_enable=stage1_en, stop_rx=[stop1_rx], stop_tx=[stop1_tx]) treg = Signal(intbv(0)[MAX_REG_WRAP * WBUS_WIDTH:]) ls_treg = [ Signal(intbv(0)[WBUS_WIDTH:]) for i in range(MAX_REG_WRAP) ] def assigner(y, x): @always_comb def assigner_comb(): y.next = x return assigner_comb slc1 = [ assigner(ls_treg[i], treg((i + 1) * WBUS_WIDTH, i * WBUS_WIDTH)) for i in range(MAX_REG_WRAP) ] reg_sel = Signal(intbv(0, min=0, max=NUM_REGS)) """ ============================================= = Pipeline Stage 0 ============================================= """ mux_sel = Signal(intbv(0, min=-1, max=MAX_REG_WRAP)) def padder(y, x): @always_comb def padder_comb(): y.next = x return padder_comb ls_rdp = [ Signal(intbv(0)[MAX_REG_WRAP * WBUS_WIDTH:]) for i in range(NUM_REGS) ] pad = [assigner(ls_rdp[i], ls_rd[i]) for i in range(NUM_REGS)] @always(clk.posedge) def S10_proc_rreg(): if (rst): stop1_rx.next = 0 stop1_tx.next = 0 else: if (stage1_en): for i in range(NUM_REGS): ls_re[i].next = 0 if (not stop1_rx): mux_sel.next = -1 for a in range(NUM_ADDR): if (a == ra_data): treg_idx = ls_wreg_idx[a] mux_sel.next = treg_idx re = ls_wreg_re[a] sel = ls_wreg_sel[a] if (re == 1): ls_re[sel].next = 1 reg_sel.next = sel stop1_rx.next = 1 stop1_tx.next = 1 # TODO: Try using hs_mux to handle two data sources: addresses & regs else: treg.next = ls_rdp[reg_sel] stop1_rx.next = 0 stop1_tx.next = 0 @always_comb def dat_mux(): rd_data.next = 0xFFFFFFFF for i in range(MAX_REG_WRAP): if (i == mux_sel): rd_data.next = ls_treg[i] return instances()
def bus_serializer(rst, clk, ra_rdy, ra_vld, ra_data, rd_rdy, rd_vld, rd_data, ls_re, ls_rd): ''' Serializes bus read access to registers: maps registers to address space, guarantees atomic register read ra_rdy, ra_vld, ra_data - input address handshake interface rd_rdy, rd_vld, rd_data - output data handshake interface ls_re - list of read enable signals, one signal per register ls_rd - list of read data signals, on signal per register Current behavior: - Suppose we have: - 20 bit register X - 8 bit read data bus - X is mapped to bus addresses: X[ 7: 0] -> addr i X[15: 8] -> addr i+1 X[19:16] -> addr i+2 - To read X we have to do the following: d0 = read(addr=i) d1 = read(addr=i+1) d2 = read(addr=i+2) note: read(addr) represents bus transactions via the read bus interface (ra_rdy, ra_vld, ra_data, rd_rdy, rd_vld, rd_data) - What happens under the hood: d0 = read(addr=i) - reads the whole X into an intermediate temporary register (atomic read), and returns d0 = X[ 7: 0] d1 = read(addr=i+1) - reads d1 = X[15: 8] from the intermediate temporary register d2 = read(addr=i+2) - reads d2 = X[19:16] from the intermediate temporary register Reading X is triggered by a read from addr i, the first address of a register. If we just read from address i+1 or i+2 without before that reading from address i, we will be reading some dummy data from the intermediate temporary register. TODO: May be, read X into the temporary register every time there is non-sequential access to some of the register addresses. non-sequential access - registers are not accessed sequentially i, i+1, i+2 ''' NUM_STAGES = 1 stage1_en = Signal(bool(0)) stop1_rx = Signal(bool(0)) stop1_tx = Signal(bool(0)) pipe1_ctrl_i = pipeline_control( rst = rst, clk = clk, rx_vld = ra_vld, rx_rdy = ra_rdy, tx_vld = rd_vld, tx_rdy = rd_rdy, stage_enable = stage1_en, stop_rx = [stop1_rx], stop_tx = [stop1_tx] ) treg = Signal(intbv(0)[MAX_REG_WRAP*WBUS_WIDTH:]) ls_treg = [Signal(intbv(0)[WBUS_WIDTH:]) for i in range(MAX_REG_WRAP)] def assigner(y,x): @always_comb def assigner_comb(): y.next = x return assigner_comb slc1 = [assigner(ls_treg[i], treg((i+1)*WBUS_WIDTH,i*WBUS_WIDTH)) for i in range(MAX_REG_WRAP)] reg_sel = Signal(intbv(0, min=0, max=NUM_REGS)) """ ============================================= = Pipeline Stage 0 ============================================= """ mux_sel = Signal(intbv(0, min=-1, max=MAX_REG_WRAP)) def padder(y, x): @always_comb def padder_comb(): y.next = x return padder_comb ls_rdp = [Signal(intbv(0)[MAX_REG_WRAP*WBUS_WIDTH:]) for i in range(NUM_REGS)] pad = [assigner(ls_rdp[i], ls_rd[i]) for i in range(NUM_REGS)] @always(clk.posedge) def S10_proc_rreg(): if (rst): stop1_rx.next = 0 stop1_tx.next = 0 else: if (stage1_en): for i in range(NUM_REGS): ls_re[i].next = 0 if (not stop1_rx): mux_sel.next = -1 for a in range(NUM_ADDR): if (a == ra_data): treg_idx = ls_wreg_idx[a] mux_sel.next = treg_idx re = ls_wreg_re[a] sel = ls_wreg_sel[a] if (re==1): ls_re[sel].next = 1 reg_sel.next = sel stop1_rx.next = 1 stop1_tx.next = 1 # TODO: Try using hs_mux to handle two data sources: addresses & regs else: treg.next = ls_rdp[reg_sel] stop1_rx.next = 0 stop1_tx.next = 0 @always_comb def dat_mux(): rd_data.next = 0xFFFFFFFF for i in range(MAX_REG_WRAP): if (i == mux_sel): rd_data.next = ls_treg[i] return instances()
def bus_deserializer(rst, clk, wr_rdy, wr_vld, wr_addr, wr_data, ls_we, ls_wd): ''' Deserializes bus write access to registers: registers to address space, guarantees atomic register write wr_rdy, wr_vld, wr_addr, wr_data - handshake interface for the write bus ls_we - list of write enable signals, one signal per register ls_wd - list of write data signals, one signal per register Current behavior: - Suppose we have: - 20 bit register X - 8 bit write data bus - X is mapped to bus addresses: X[ 7: 0] -> addr i X[15: 8] -> addr i+1 X[19:16] -> addr i+2 - To write to X we have to do the following: write(addr=i, data=d0) write(addr=i+1, data=d1) write(addr=i+2, data=d2) note: write(addr, data) represents bus transactions via the write bus interface (wr_rdy, wr_vld, wr_addr, wr_data) - What happens under the hood: write(addr=i, data=X[ 7: 0]) - d0=X[ 7: 0] is written in an intermediate temporary register 0 write(addr=i+1, data=X[15: 8]) - d1=X[15: 8] is written in an intermediate temporary register 1 write(addr=i+2, data=X[19:16]) - d2=X[19:16] is written in an intermediate temporary register 2, and d0, d1, d2 are transferred from the temporary registers to X (atomic write) Write to X is triggered by write to addr i+2, the top address of a register. If we just write to i+2, without before that writing to i and i+1, X will still be written with incorrect data from the temporary registers TODO: May be, protect X such that write to it is executed only after i, i+1, i+2 are written sequentially ''' NUM_STAGES = 1 stage0_en = Signal(bool(0)) stop0_rx = Signal(bool(0)) stop0_tx = Signal(bool(0)) pipe0_tx_vld = Signal(bool(0)) pipe0_tx_rdy = Signal(bool(0)) pipe0_ctrl_i = pipeline_control(rst=rst, clk=clk, rx_vld=wr_vld, rx_rdy=wr_rdy, tx_vld=pipe0_tx_vld, tx_rdy=pipe0_tx_rdy, stage_enable=stage0_en, stop_rx=[stop0_rx], stop_tx=[stop0_tx]) # Temporary reg where the data is deserialized before writing it to the real register ls_treg = [ Signal(intbv(0)[WBUS_WIDTH:]) for i in range(MAX_REG_WRAP) ] treg = ConcatSignal( *reversed(ls_treg)) if (len(ls_treg) > 1) else ls_treg[0] # Selects the register being written reg_sel = Signal(intbv(0, min=0, max=NUM_REGS)) """ ============================================= = Pipeline Stage 0 ============================================= """ @always(clk.posedge) def S00_proc_wreg(): ''' Deserialize ''' if (rst): stop0_rx.next = 0 stop0_tx.next = 1 reg_sel.next = 0 else: if (stage0_en): stop0_tx.next = 1 for a in range(NUM_ADDR): if (a == wr_addr): # Deser treg_idx = ls_wreg_idx[a] we = ls_wreg_we[a] sel = ls_wreg_sel[a] ls_treg[treg_idx].next = wr_data if (we == 1): reg_sel.next = sel stop0_tx.next = 0 def slicer(y, x): l = len(y) @always_comb def slicer_comb(): y.next = x[l:] return slicer_comb slc = [slicer(ls_wd[i], treg) for i in range(NUM_REGS)] @always_comb def we_demux(): pipe0_tx_rdy.next = 1 for i in range(NUM_REGS): if (i == reg_sel): ls_we[i].next = pipe0_tx_vld else: ls_we[i].next = 0 return instances()
def bus_deserializer(rst, clk, wr_rdy, wr_vld, wr_addr, wr_data, ls_we, ls_wd): ''' Deserializes bus write access to registers: registers to address space, guarantees atomic register write wr_rdy, wr_vld, wr_addr, wr_data - handshake interface for the write bus ls_we - list of write enable signals, one signal per register ls_wd - list of write data signals, one signal per register Current behavior: - Suppose we have: - 20 bit register X - 8 bit write data bus - X is mapped to bus addresses: X[ 7: 0] -> addr i X[15: 8] -> addr i+1 X[19:16] -> addr i+2 - To write to X we have to do the following: write(addr=i, data=d0) write(addr=i+1, data=d1) write(addr=i+2, data=d2) note: write(addr, data) represents bus transactions via the write bus interface (wr_rdy, wr_vld, wr_addr, wr_data) - What happens under the hood: write(addr=i, data=X[ 7: 0]) - d0=X[ 7: 0] is written in an intermediate temporary register 0 write(addr=i+1, data=X[15: 8]) - d1=X[15: 8] is written in an intermediate temporary register 1 write(addr=i+2, data=X[19:16]) - d2=X[19:16] is written in an intermediate temporary register 2, and d0, d1, d2 are transferred from the temporary registers to X (atomic write) Write to X is triggered by write to addr i+2, the top address of a register. If we just write to i+2, without before that writing to i and i+1, X will still be written with incorrect data from the temporary registers TODO: May be, protect X such that write to it is executed only after i, i+1, i+2 are written sequentially ''' NUM_STAGES = 1 stage0_en = Signal(bool(0)) stop0_rx = Signal(bool(0)) stop0_tx = Signal(bool(0)) pipe0_tx_vld = Signal(bool(0)) pipe0_tx_rdy = Signal(bool(0)) pipe0_ctrl_i = pipeline_control( rst = rst, clk = clk, rx_vld = wr_vld, rx_rdy = wr_rdy, tx_vld = pipe0_tx_vld, tx_rdy = pipe0_tx_rdy, stage_enable = stage0_en, stop_rx = [stop0_rx], stop_tx = [stop0_tx] ) # Temporary reg where the data is deserialized before writing it to the real register ls_treg = [Signal(intbv(0)[WBUS_WIDTH:]) for i in range(MAX_REG_WRAP)] treg = ConcatSignal(*reversed(ls_treg)) if (len(ls_treg) > 1) else ls_treg[0] # Selects the register being written reg_sel = Signal(intbv(0, min=0, max=NUM_REGS)) """ ============================================= = Pipeline Stage 0 ============================================= """ @always(clk.posedge) def S00_proc_wreg(): ''' Deserialize ''' if (rst): stop0_rx.next = 0 stop0_tx.next = 1 reg_sel.next = 0 else: if (stage0_en): stop0_tx.next = 1 for a in range(NUM_ADDR): if (a == wr_addr): # Deser treg_idx = ls_wreg_idx[a] we = ls_wreg_we[a] sel = ls_wreg_sel[a] ls_treg[treg_idx].next = wr_data if (we==1): reg_sel.next = sel stop0_tx.next = 0 def slicer(y,x): l = len(y) @always_comb def slicer_comb(): y.next = x[l:] return slicer_comb slc = [slicer(ls_wd[i], treg) for i in range(NUM_REGS)] @always_comb def we_demux(): pipe0_tx_rdy.next = 1 for i in range(NUM_REGS): if (i == reg_sel): ls_we[i].next = pipe0_tx_vld else: ls_we[i].next = 0 return instances()