From 13b68b1e7dd5bb95516291dc964bf73ad7a21f10 Mon Sep 17 00:00:00 2001 From: morgan Date: Mon, 13 Jan 2025 15:47:49 +0800 Subject: [PATCH] frameline GW: add gearbox docs --- src/gateware/cxp_frame_pipeline.py | 210 ++++++++++++++--------------- 1 file changed, 101 insertions(+), 109 deletions(-) diff --git a/src/gateware/cxp_frame_pipeline.py b/src/gateware/cxp_frame_pipeline.py index 2a1b793..9903131 100644 --- a/src/gateware/cxp_frame_pipeline.py +++ b/src/gateware/cxp_frame_pipeline.py @@ -411,170 +411,159 @@ class Custom_Pixel_Gearbox(Module): # # # - shift_reg_size = lcm(sink_dw, source_dw) + ring_buf_size = lcm(sink_dw, source_dw) # ensure the shift register is at least twice the size of sink/source dw - if (shift_reg_size//sink_dw) < 2: - shift_reg_size = shift_reg_size * 2 - if (shift_reg_size//source_dw) < 2: - shift_reg_size = shift_reg_size * 2 + if (ring_buf_size//sink_dw) < 2: + ring_buf_size = ring_buf_size * 2 + if (ring_buf_size//source_dw) < 2: + ring_buf_size = ring_buf_size * 2 # Control interface reset_reg = Signal() we = Signal() re = Signal() - self.level = Signal(max=shift_reg_size) - self.w_cnt = Signal(max=shift_reg_size//sink_dw) - self.r_cnt = Signal(max=shift_reg_size//source_dw) + level = Signal(max=ring_buf_size) + w_cnt = Signal(max=ring_buf_size//sink_dw) + r_cnt = Signal(max=ring_buf_size//source_dw) self.sync += [ If(reset_reg, - self.level.eq(self.level.reset), + level.eq(level.reset), ).Else( - If(we & ~re, self.level.eq(self.level + sink_dw)), - If(~we & re, self.level.eq(self.level - source_dw)), - If(we & re, self.level.eq(self.level + sink_dw - source_dw)), + If(we & ~re, level.eq(level + sink_dw)), + If(~we & re, level.eq(level - source_dw)), + If(we & re, level.eq(level + sink_dw - source_dw)), ), If(reset_reg, - self.w_cnt.eq(self.w_cnt.reset), - self.r_cnt.eq(self.r_cnt.reset), + w_cnt.eq(w_cnt.reset), + r_cnt.eq(r_cnt.reset), ).Else( If(we, - If(self.w_cnt == ((shift_reg_size//sink_dw) - 1), - self.w_cnt.eq(self.w_cnt.reset), + If(w_cnt == ((ring_buf_size//sink_dw) - 1), + w_cnt.eq(w_cnt.reset), ).Else( - self.w_cnt.eq(self.w_cnt + 1), + w_cnt.eq(w_cnt + 1), ) ), If(re, - If(self.r_cnt == ((shift_reg_size//source_dw) - 1), - self.r_cnt.eq(self.r_cnt.reset), + If(r_cnt == ((ring_buf_size//source_dw) - 1), + r_cnt.eq(r_cnt.reset), ).Else( - self.r_cnt.eq(self.r_cnt + 1), + r_cnt.eq(r_cnt + 1), ) ), ) ] - # FIXME: handle linebreak properly for all bits - # 1) the COPY may or may not stb data out - # 2) the NEWLINE may stb too much - # FIXME: the fsm need to be intellgent enough to know whether it need to stb twice or once at eop - # need a better generic solution - # FIXME: handle last line when the word also are aligned to 4x pixel - - # FACT: - # - self.level is only useful for normal ops (i.e. non-linebreak) - # - it should not be used for last pixels - # - extra_stb should be reserved for non aligned data only - # - after last word, fsm may need to stb 0-2 times - # - which are determined by x_size (varies) & pix size (constant) - # - self.valid should be used instead of extra stb when aligned - - extra_stb = Signal() - match size: - case 8 | 16: - # no need for extra stb as they are aligned to word boundary no matter what - pass - case 10: - stb_cases = { - 5: extra_stb.eq(1), - 6: extra_stb.eq(1), - 9: extra_stb.eq(1), - } - # mod 16 - self.sync += Case(self.x_size[:4], stb_cases) - case 12: - stb_cases = { - 5: extra_stb.eq(1), - } - # mod 8 - self.sync += Case(self.x_size[:3], stb_cases) - case 14: - stb_cases = { - 9: extra_stb.eq(1), - 13: extra_stb.eq(1), - } - # mod 16 - self.sync += Case(self.x_size[:4], stb_cases) + extra_eol_handling = size in [10, 12, 14] + if extra_eol_handling: + # the source need to be stb twice + # (one for level >= source_dw and the other for the remaining pixels) + # when last word of each line packet satisfied the following condition: + # + # if there exist an integers j such that + # sink_dw * i > size * j > source_dw * k + # where i,k are postive integers and source_dw * k - sink_dw * (i-1) > 0 + # + # For example size == 10 + # 32 * 2 > 10 * (5) > 40 * 1 + # 32 * 2 > 10 * (6) > 40 * 1 + # 32 * 3 > 10 * (9) > 40 * 2 + stb_aligned = Signal() + match size: + case 10: + stb_cases = { + 5: stb_aligned.eq(1), + 6: stb_aligned.eq(1), + 9: stb_aligned.eq(1), + } + self.sync += Case(self.x_size[:4], stb_cases) # mod 16 + case 12: + stb_cases = { + 5: stb_aligned.eq(1), + } + self.sync += Case(self.x_size[:3], stb_cases) # mod 8 + case 14: + stb_cases = { + 9: stb_aligned.eq(1), + 13: stb_aligned.eq(1), + } + self.sync += Case(self.x_size[:4], stb_cases) # mod 16 - # NOTE: - # access to levels -> know when 4x pixels are - # access to x_size -> know which pixel at last stb are valid - # access to eop -> know when end of line ares - - self.submodules.fsm = fsm = FSM(reset_state="COPY_ALIGNED") + self.submodules.fsm = fsm = FSM(reset_state="SHIFTING") fsm.act( - "COPY_ALIGNED", + "SHIFTING", self.sink.ack.eq(1), - self.source.stb.eq(self.level >= source_dw), + self.source.stb.eq(level >= source_dw), we.eq(self.sink.stb), re.eq((self.source.stb & self.source.ack)), If(self.sink.stb & self.sink.eop, - If(extra_stb, - NextState("EXTRA_STB"), + (If(stb_aligned, + NextState("MOVE_ALIGNED_PIX"), ).Else( - NextState("NEWLINE"), + NextState("MOVE_REMAINING_PIX"), + ) if extra_eol_handling else + NextState("MOVE_REMAINING_PIX"), ) ), ) - fsm.act( - "EXTRA_STB", - self.source.stb.eq(self.level >= source_dw), - re.eq((self.source.stb & self.source.ack)), - NextState("NEWLINE"), - ) + if extra_eol_handling: + fsm.act( + "MOVE_ALIGNED_PIX", + self.source.stb.eq(1), + re.eq((self.source.stb & self.source.ack)), + NextState("MOVE_REMAINING_PIX"), + ) - # determine to stb 1 or 2 times - # only the last stb will use valid_stb - valid_stb = Signal() + stb_valid = Signal() fsm.act( - "NEWLINE", - self.source.stb.eq(1), + "MOVE_REMAINING_PIX", reset_reg.eq(1), - valid_stb.eq(1), - NextState("COPY_ALIGNED"), + self.source.stb.eq(1), + stb_valid.eq(1), + NextState("SHIFTING"), ) # Data path - self.shift_register = Signal(shift_reg_size, reset_less=True) + ring_buf = Signal(ring_buf_size, reset_less=True) sink_cases = {} - for i in range(shift_reg_size//sink_dw): + for i in range(ring_buf_size//sink_dw): sink_cases[i] = [ - self.shift_register[sink_dw*i:sink_dw*(i+1)].eq(self.sink.data), + ring_buf[sink_dw*i:sink_dw*(i+1)].eq(self.sink.data), ] - self.sync += If(self.sink.stb, Case(self.w_cnt, sink_cases)) + self.sync += If(self.sink.stb, Case(w_cnt, sink_cases)) source_cases = {} - for i in range(shift_reg_size//source_dw): + for i in range(ring_buf_size//source_dw): source_cases[i] = [] for j in range(4): source_cases[i].append( self.source.data[pixel_width * j : pixel_width * (j + 1)].eq( - self.shift_register[(source_dw * i) + (size * j) : (source_dw * i) + (size * (j + 1))] + ring_buf[(source_dw * i) + (size * j) : (source_dw * i) + (size * (j + 1))] ) ) - # precalcule which last pixels are valid - self.valid = Signal(4) + # calcule which last pixels are valid + valid = Signal(4) bit_cases = { - 0: self.valid.eq(0b1111), - 1: self.valid.eq(0b0001), - 2: self.valid.eq(0b0011), - 3: self.valid.eq(0b0111), + 0: valid.eq(0b1111), + 1: valid.eq(0b0001), + 2: valid.eq(0b0011), + 3: valid.eq(0b0111), } self.sync += Case(self.x_size[:2], bit_cases) self.comb += [ - Case(self.r_cnt, source_cases), - If(valid_stb, - self.source.valid.eq(self.valid), + Case(r_cnt, source_cases), + If(stb_valid, + self.source.valid.eq(valid), ).Else( self.source.valid.eq(0b1111), ), @@ -592,17 +581,20 @@ class Frame_Deserializer(Module): self.submodules.eol_inserter = eol_inserter = End_Of_Line_Inserter() self.sync += eol_inserter.l_size.eq(self.l_size), + for i in [8, 10, 12, 14, 16]: + gearbox = Custom_Pixel_Gearbox(i) + self.submodules += gearbox + self.sync += gearbox.x_size.eq(self.x_size), + self.comb += eol_inserter.source.connect(gearbox.sink) + self.comb += gearbox.source.ack.eq(1) # simulated a proper consumer, idk why but without this it will destory timing - # TODO: fix the edge case where 5 x_size is not working - # the issue arises when the 4th pixel is sent with eop (e.g. 10bits & x_size = 5,6) - # eating the rest of the bits - self.submodules.gearbox = gearbox = Custom_Pixel_Gearbox(14) - self.sync += gearbox.x_size.eq(self.x_size), + # self.submodules.gearbox = gearbox = Custom_Pixel_Gearbox(8) + # self.sync += gearbox.x_size.eq(self.x_size), + # self.comb += eol_inserter.source.connect(gearbox.sink) - self.comb += eol_inserter.source.connect(gearbox.sink) self.sink = eol_inserter.sink - self.source = gearbox.source + # self.source = gearbox.source @@ -730,8 +722,8 @@ class ROI_Pipeline(Module): self.sink = self.pipeline[0].sink # DEBUG - self.source = self.pipeline[-1].source - self.comb += self.source.ack.eq(1) # simulated a proper consumer, idk why but without this it will destory timing + # self.source = self.pipeline[-1].source + # self.comb += self.source.ack.eq(1) # simulated a proper consumer, idk why but without this it will destory timing class Frame_Packet_Router(Module): # packet size expressed in bits