frameline GW: add gearbox docs

This commit is contained in:
morgan 2025-01-13 15:47:49 +08:00
parent d53a393f0d
commit 13b68b1e7d

View File

@ -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()
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 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),
5: stb_aligned.eq(1),
6: stb_aligned.eq(1),
9: stb_aligned.eq(1),
}
# mod 16
self.sync += Case(self.x_size[:4], stb_cases)
self.sync += Case(self.x_size[:4], stb_cases) # mod 16
case 12:
stb_cases = {
5: extra_stb.eq(1),
5: stb_aligned.eq(1),
}
# mod 8
self.sync += Case(self.x_size[:3], stb_cases)
self.sync += Case(self.x_size[:3], stb_cases) # mod 8
case 14:
stb_cases = {
9: extra_stb.eq(1),
13: extra_stb.eq(1),
9: stb_aligned.eq(1),
13: stb_aligned.eq(1),
}
# mod 16
self.sync += Case(self.x_size[:4], stb_cases)
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"),
)
),
)
if extra_eol_handling:
fsm.act(
"EXTRA_STB",
self.source.stb.eq(self.level >= source_dw),
"MOVE_ALIGNED_PIX",
self.source.stb.eq(1),
re.eq((self.source.stb & self.source.ack)),
NextState("NEWLINE"),
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),
# 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)
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
# 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.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