mirror of
https://github.com/enjoy-digital/liteeth.git
synced 2025-01-03 03:43:37 -05:00
Merge pull request #74 from david-sawatzke/32bitmacpath
Add toggleable 32 bit support to mac pipeline
This commit is contained in:
commit
7847f72c6b
6 changed files with 180 additions and 69 deletions
|
@ -20,9 +20,10 @@ class LiteEthMAC(Module, AutoCSR):
|
|||
ntxslots = 2,
|
||||
hw_mac = None,
|
||||
timestamp = None,
|
||||
full_memory_we = False):
|
||||
full_memory_we = False,
|
||||
sys_data_path = True):
|
||||
assert interface in ["crossbar", "wishbone", "hybrid"]
|
||||
self.submodules.core = LiteEthMACCore(phy, dw, endianness, with_preamble_crc)
|
||||
self.submodules.core = LiteEthMACCore(phy, dw, endianness, with_preamble_crc, sys_data_path)
|
||||
self.csrs = []
|
||||
if interface == "crossbar":
|
||||
self.submodules.crossbar = LiteEthMACCrossbar(dw)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from liteeth.common import *
|
||||
from liteeth.mac import gap, preamble, crc, padding, last_be
|
||||
from liteeth.mac import gap, preamble, crc, padding, last_be, endian_converter
|
||||
from liteeth.phy.model import LiteEthPHYModel
|
||||
|
||||
from migen.genlib.cdc import PulseSynchronizer
|
||||
|
@ -17,16 +17,32 @@ from litex.soc.interconnect.stream import BufferizeEndpoints, DIR_SOURCE, DIR_SI
|
|||
# MAC Core -----------------------------------------------------------------------------------------
|
||||
|
||||
class LiteEthMACCore(Module, AutoCSR):
|
||||
def __init__(self, phy, dw, endianness="big", with_preamble_crc=True, with_padding=True):
|
||||
if dw < phy.dw:
|
||||
raise ValueError("Core data width({}) must be larger than PHY data width({})".format(dw, phy.dw))
|
||||
def __init__(self, phy, dw,
|
||||
endianness = "big",
|
||||
with_preamble_crc = True,
|
||||
sys_data_path = True,
|
||||
with_padding = True):
|
||||
core_dw = dw
|
||||
|
||||
if core_dw < phy.dw:
|
||||
raise ValueError("Core data width({}) must be larger than PHY data width({})".format(core_dw, phy.dw))
|
||||
|
||||
rx_pipeline = [phy]
|
||||
tx_pipeline = [phy]
|
||||
|
||||
if sys_data_path:
|
||||
# The pipeline for dw>8 only works for little endian
|
||||
self.data_path_converter(tx_pipeline, rx_pipeline, core_dw, phy.dw, "little")
|
||||
cd_tx = cd_rx = "sys"
|
||||
dw = core_dw
|
||||
else:
|
||||
cd_tx = "eth_tx"
|
||||
cd_rx = "eth_rx"
|
||||
dw = phy.dw
|
||||
|
||||
# Interpacket gap
|
||||
tx_gap_inserter = gap.LiteEthMACGap(phy.dw)
|
||||
self.submodules += ClockDomainsRenamer("eth_tx")(tx_gap_inserter)
|
||||
tx_gap_inserter = gap.LiteEthMACGap(dw)
|
||||
self.submodules += ClockDomainsRenamer(cd_tx)(tx_gap_inserter)
|
||||
tx_pipeline += [tx_gap_inserter]
|
||||
|
||||
# Preamble / CRC
|
||||
|
@ -36,65 +52,85 @@ class LiteEthMACCore(Module, AutoCSR):
|
|||
self._preamble_crc = CSRStatus(reset=1)
|
||||
elif with_preamble_crc:
|
||||
self._preamble_crc = CSRStatus(reset=1)
|
||||
self.preamble_errors = CSRStatus(32)
|
||||
self.crc_errors = CSRStatus(32)
|
||||
|
||||
# Preamble insert/check
|
||||
preamble_inserter = preamble.LiteEthMACPreambleInserter(phy.dw)
|
||||
preamble_checker = preamble.LiteEthMACPreambleChecker(phy.dw)
|
||||
self.submodules += ClockDomainsRenamer("eth_tx")(preamble_inserter)
|
||||
self.submodules += ClockDomainsRenamer("eth_rx")(preamble_checker)
|
||||
preamble_inserter = preamble.LiteEthMACPreambleInserter(dw)
|
||||
preamble_checker = preamble.LiteEthMACPreambleChecker(dw)
|
||||
self.submodules += ClockDomainsRenamer(cd_tx)(preamble_inserter)
|
||||
self.submodules += ClockDomainsRenamer(cd_rx)(preamble_checker)
|
||||
tx_pipeline += [preamble_inserter]
|
||||
rx_pipeline += [preamble_checker]
|
||||
self.submodules.ps_preamble_error = PulseSynchronizer(cd_rx, "sys")
|
||||
|
||||
|
||||
# Preamble error counter
|
||||
self.preamble_errors = CSRStatus(32)
|
||||
self.comb += self.ps_preamble_error.i.eq(preamble_checker.error),
|
||||
self.sync += If(self.ps_preamble_error.o,
|
||||
self.preamble_errors.status.eq(self.preamble_errors.status + 1)),
|
||||
|
||||
# CRC insert/check
|
||||
crc32_inserter = BufferizeEndpoints({"sink": DIR_SINK})(crc.LiteEthMACCRC32Inserter(eth_phy_description(phy.dw)))
|
||||
crc32_checker = BufferizeEndpoints({"sink": DIR_SINK})(crc.LiteEthMACCRC32Checker(eth_phy_description(phy.dw)))
|
||||
self.submodules += ClockDomainsRenamer("eth_tx")(crc32_inserter)
|
||||
self.submodules += ClockDomainsRenamer("eth_rx")(crc32_checker)
|
||||
crc32_inserter = BufferizeEndpoints({"sink": DIR_SINK})(crc.LiteEthMACCRC32Inserter(eth_phy_description(dw)))
|
||||
crc32_checker = BufferizeEndpoints({"sink": DIR_SINK})(crc.LiteEthMACCRC32Checker(eth_phy_description(dw)))
|
||||
self.submodules += ClockDomainsRenamer(cd_tx)(crc32_inserter)
|
||||
self.submodules += ClockDomainsRenamer(cd_rx)(crc32_checker)
|
||||
|
||||
tx_pipeline += [preamble_inserter, crc32_inserter]
|
||||
rx_pipeline += [preamble_checker, crc32_checker]
|
||||
tx_pipeline += [crc32_inserter]
|
||||
rx_pipeline += [crc32_checker]
|
||||
|
||||
# Error counters
|
||||
self.submodules.ps_preamble_error = PulseSynchronizer("eth_rx", "sys")
|
||||
self.submodules.ps_crc_error = PulseSynchronizer("eth_rx", "sys")
|
||||
self.comb += [
|
||||
self.ps_preamble_error.i.eq(preamble_checker.error),
|
||||
self.ps_crc_error.i.eq(crc32_checker.error),
|
||||
]
|
||||
self.sync += [
|
||||
If(self.ps_preamble_error.o,
|
||||
self.preamble_errors.status.eq(self.preamble_errors.status + 1)),
|
||||
If(self.ps_crc_error.o,
|
||||
self.crc_errors.status.eq(self.crc_errors.status + 1)),
|
||||
]
|
||||
# CRC error counter
|
||||
self.crc_errors = CSRStatus(32)
|
||||
self.submodules.ps_crc_error = PulseSynchronizer(cd_rx, "sys")
|
||||
self.comb += self.ps_crc_error.i.eq(crc32_checker.error),
|
||||
self.sync += If(self.ps_crc_error.o,
|
||||
self.crc_errors.status.eq(self.crc_errors.status + 1)),
|
||||
|
||||
# Padding
|
||||
if with_padding:
|
||||
padding_inserter = padding.LiteEthMACPaddingInserter(phy.dw, 60)
|
||||
padding_checker = padding.LiteEthMACPaddingChecker(phy.dw, 60)
|
||||
self.submodules += ClockDomainsRenamer("eth_tx")(padding_inserter)
|
||||
self.submodules += ClockDomainsRenamer("eth_rx")(padding_checker)
|
||||
padding_inserter = padding.LiteEthMACPaddingInserter(dw, 60)
|
||||
padding_checker = padding.LiteEthMACPaddingChecker(dw, 60)
|
||||
self.submodules += ClockDomainsRenamer(cd_tx)(padding_inserter)
|
||||
self.submodules += ClockDomainsRenamer(cd_rx)(padding_checker)
|
||||
tx_pipeline += [padding_inserter]
|
||||
rx_pipeline += [padding_checker]
|
||||
|
||||
if sys_data_path:
|
||||
# Since the pipeline only works for little endian when dw > 8,
|
||||
# convert to big endian if necessary
|
||||
if endianness == "big" and dw != 8:
|
||||
tx_converter = endian_converter.LiteEthMACEndianConverter(dw)
|
||||
rx_converter = endian_converter.LiteEthMACEndianConverter(dw)
|
||||
self.submodules += tx_converter, rx_converter
|
||||
tx_pipeline += [tx_converter]
|
||||
rx_pipeline += [rx_converter]
|
||||
else:
|
||||
self.data_path_converter(tx_pipeline, rx_pipeline, core_dw, phy.dw, endianness)
|
||||
|
||||
# Graph
|
||||
self.submodules.tx_pipeline = stream.Pipeline(*reversed(tx_pipeline))
|
||||
self.submodules.rx_pipeline = stream.Pipeline(*rx_pipeline)
|
||||
|
||||
self.sink, self.source = self.tx_pipeline.sink, self.rx_pipeline.source
|
||||
|
||||
def data_path_converter(self, tx_pipeline, rx_pipeline, dw, phy_dw, endianness):
|
||||
# Delimiters
|
||||
if dw != 8:
|
||||
tx_last_be = last_be.LiteEthMACTXLastBE(phy.dw)
|
||||
rx_last_be = last_be.LiteEthMACRXLastBE(phy.dw)
|
||||
tx_last_be = last_be.LiteEthMACTXLastBE(phy_dw)
|
||||
rx_last_be = last_be.LiteEthMACRXLastBE(phy_dw)
|
||||
self.submodules += ClockDomainsRenamer("eth_tx")(tx_last_be)
|
||||
self.submodules += ClockDomainsRenamer("eth_rx")(rx_last_be)
|
||||
tx_pipeline += [tx_last_be]
|
||||
rx_pipeline += [rx_last_be]
|
||||
|
||||
# Converters
|
||||
if dw != phy.dw:
|
||||
if dw != phy_dw:
|
||||
reverse = endianness == "big"
|
||||
tx_converter = stream.StrideConverter(
|
||||
description_from = eth_phy_description(dw),
|
||||
description_to = eth_phy_description(phy.dw),
|
||||
description_to = eth_phy_description(phy_dw),
|
||||
reverse = reverse)
|
||||
rx_converter = stream.StrideConverter(
|
||||
description_from = eth_phy_description(phy.dw),
|
||||
description_from = eth_phy_description(phy_dw),
|
||||
description_to = eth_phy_description(dw),
|
||||
reverse = reverse)
|
||||
self.submodules += ClockDomainsRenamer("eth_tx")(tx_converter)
|
||||
|
@ -108,9 +144,3 @@ class LiteEthMACCore(Module, AutoCSR):
|
|||
self.submodules += tx_cdc, rx_cdc
|
||||
tx_pipeline += [tx_cdc]
|
||||
rx_pipeline += [rx_cdc]
|
||||
|
||||
# Graph
|
||||
self.submodules.tx_pipeline = stream.Pipeline(*reversed(tx_pipeline))
|
||||
self.submodules.rx_pipeline = stream.Pipeline(*rx_pipeline)
|
||||
|
||||
self.sink, self.source = self.tx_pipeline.sink, self.rx_pipeline.source
|
||||
|
|
|
@ -102,8 +102,10 @@ class LiteEthMACCRC32(Module):
|
|||
|
||||
Attributes
|
||||
----------
|
||||
d : in
|
||||
data : in
|
||||
Data input.
|
||||
last_be : in
|
||||
Valid byte in data input (optional).
|
||||
value : out
|
||||
CRC value (used for generator).
|
||||
error : out
|
||||
|
@ -114,22 +116,36 @@ class LiteEthMACCRC32(Module):
|
|||
init = 2**width-1
|
||||
check = 0xC704DD7B
|
||||
def __init__(self, data_width):
|
||||
dw = data_width//8
|
||||
|
||||
self.data = Signal(data_width)
|
||||
self.last_be = Signal(dw)
|
||||
self.value = Signal(self.width)
|
||||
self.error = Signal()
|
||||
# Add a separate last_be signal, to maintain backwards compatability
|
||||
last_be = Signal(data_width//8)
|
||||
|
||||
# # #
|
||||
|
||||
self.submodules.engine = LiteEthMACCRCEngine(data_width, self.width, self.polynom)
|
||||
reg = Signal(self.width, reset=self.init)
|
||||
self.sync += reg.eq(self.engine.next)
|
||||
self.comb += [
|
||||
self.engine.data.eq(self.data),
|
||||
self.engine.last.eq(reg),
|
||||
|
||||
self.value.eq(~reg[::-1]),
|
||||
self.error.eq(self.engine.next != self.check)
|
||||
If(self.last_be != 0,
|
||||
last_be.eq(self.last_be)
|
||||
).Else(
|
||||
last_be.eq(2**(dw-1)))
|
||||
]
|
||||
# Since the data can end at any byte end, indicated by `last_be`
|
||||
# maintain separate engines for each 8 byte increment in the data word
|
||||
engines = [LiteEthMACCRCEngine((e+1)*8, self.width, self.polynom) for e in range(dw)]
|
||||
self.submodules += engines
|
||||
|
||||
reg = Signal(self.width, reset=self.init)
|
||||
self.sync += reg.eq(engines[-1].next)
|
||||
self.comb += [engines[e].data.eq(self.data[:(e+1)*8]) for e in range(dw)],
|
||||
self.comb += [engines[e].last.eq(reg) for e in range(dw)]
|
||||
self.comb += [If(last_be[e],
|
||||
self.value.eq(reverse_bits(~engines[e].next)),
|
||||
self.error.eq(engines[e].next != self.check))
|
||||
for e in range(dw)]
|
||||
|
||||
# MAC CRC Inserter ---------------------------------------------------------------------------------
|
||||
|
||||
|
@ -146,9 +162,9 @@ class LiteEthMACCRCInserter(Module):
|
|||
Attributes
|
||||
----------
|
||||
sink : in
|
||||
Packets octets without CRC.
|
||||
Packet data without CRC.
|
||||
source : out
|
||||
Packets octets with CRC.
|
||||
Packet data with CRC.
|
||||
"""
|
||||
def __init__(self, crc_class, description):
|
||||
self.sink = sink = stream.Endpoint(description)
|
||||
|
@ -157,10 +173,15 @@ class LiteEthMACCRCInserter(Module):
|
|||
# # #
|
||||
|
||||
dw = len(sink.data)
|
||||
assert dw in [8, 32]
|
||||
crc = crc_class(dw)
|
||||
fsm = FSM(reset_state="IDLE")
|
||||
self.submodules += crc, fsm
|
||||
|
||||
# crc packet checksum
|
||||
crc_packet = Signal(crc.width)
|
||||
last_be = Signal().like(sink.last_be)
|
||||
|
||||
fsm.act("IDLE",
|
||||
crc.reset.eq(1),
|
||||
sink.ready.eq(1),
|
||||
|
@ -172,9 +193,23 @@ class LiteEthMACCRCInserter(Module):
|
|||
fsm.act("COPY",
|
||||
crc.ce.eq(sink.valid & source.ready),
|
||||
crc.data.eq(sink.data),
|
||||
crc.last_be.eq(sink.last_be),
|
||||
sink.connect(source),
|
||||
source.last.eq(0),
|
||||
source.last_be.eq(0),
|
||||
If(sink.last,
|
||||
# Fill the empty space of the last data word with the
|
||||
# beginning of the crc value
|
||||
[If(sink.last_be[e],
|
||||
source.data.eq(Cat(sink.data[:(e+1)*8],
|
||||
crc.value)[:dw])) for e in range(dw//8)],
|
||||
).Else(
|
||||
crc.ce.eq(sink.valid & source.ready),
|
||||
),
|
||||
|
||||
If(sink.valid & sink.last & source.ready,
|
||||
NextValue(crc_packet, crc.value),
|
||||
NextValue(last_be, sink.last_be),
|
||||
NextState("CRC"),
|
||||
)
|
||||
)
|
||||
|
@ -184,7 +219,7 @@ class LiteEthMACCRCInserter(Module):
|
|||
cnt_done = Signal()
|
||||
fsm.act("CRC",
|
||||
source.valid.eq(1),
|
||||
chooser(crc.value, cnt, source.data, reverse=True),
|
||||
chooser(crc_packet, cnt, source.data, reverse=True),
|
||||
If(cnt_done,
|
||||
source.last.eq(1),
|
||||
If(source.ready, NextState("IDLE"))
|
||||
|
@ -202,6 +237,9 @@ class LiteEthMACCRCInserter(Module):
|
|||
source.valid.eq(1),
|
||||
source.last.eq(1),
|
||||
source.data.eq(crc.value),
|
||||
source.last_be.eq(last_be),
|
||||
[If(last_be[e],
|
||||
source.data.eq(crc_packet[-(e+1)*8:])) for e in range(dw//8)],
|
||||
If(source.ready, NextState("IDLE"))
|
||||
)
|
||||
|
||||
|
@ -225,9 +263,9 @@ class LiteEthMACCRCChecker(Module):
|
|||
Attributes
|
||||
----------
|
||||
sink : in
|
||||
Packet octets with CRC.
|
||||
Packet data with CRC.
|
||||
source : out
|
||||
Packet octets without CRC and "error" set to 0
|
||||
Packet data without CRC and "error" set to 0
|
||||
on last when CRC OK / set to 1 when CRC KO.
|
||||
error : out
|
||||
Pulses every time a CRC error is detected.
|
||||
|
@ -241,6 +279,7 @@ class LiteEthMACCRCChecker(Module):
|
|||
# # #
|
||||
|
||||
dw = len(sink.data)
|
||||
assert dw in [8, 32]
|
||||
crc = crc_class(dw)
|
||||
self.submodules += crc
|
||||
ratio = crc.width//dw
|
||||
|
@ -268,8 +307,13 @@ class LiteEthMACCRCChecker(Module):
|
|||
source.last.eq(sink.last),
|
||||
fifo.source.ready.eq(fifo_out),
|
||||
source.payload.eq(fifo.source.payload),
|
||||
source.last_be.eq(sink.last_be),
|
||||
|
||||
source.error.eq(sink.error | crc.error),
|
||||
# `source.error` has a width > 1 for dw > 8, but since the crc error
|
||||
# applies to the whole ethernet packet, all the bytes are marked as
|
||||
# containing an error. This way later reducing the data width
|
||||
# doesn't run into issues with missing the error
|
||||
source.error.eq(sink.error | Replicate(crc.error, dw//8)),
|
||||
self.error.eq(source.valid & source.last & crc.error),
|
||||
]
|
||||
|
||||
|
@ -278,7 +322,10 @@ class LiteEthMACCRCChecker(Module):
|
|||
fifo.reset.eq(1),
|
||||
NextState("IDLE"),
|
||||
)
|
||||
self.comb += crc.data.eq(sink.data)
|
||||
self.comb += [
|
||||
crc.data.eq(sink.data),
|
||||
crc.last_be.eq(sink.last_be),
|
||||
]
|
||||
fsm.act("IDLE",
|
||||
If(sink.valid & sink.ready,
|
||||
crc.ce.eq(1),
|
||||
|
|
18
liteeth/mac/endian_converter.py
Normal file
18
liteeth/mac/endian_converter.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
#
|
||||
# This file is part of LiteEth.
|
||||
#
|
||||
# Copyright (c) 2021 David Sawatzke <d-git@sawatzke.dev>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
from liteeth.common import *
|
||||
|
||||
class LiteEthMACEndianConverter(Module):
|
||||
def __init__(self, dw):
|
||||
self.sink = sink = stream.Endpoint(eth_phy_description(dw))
|
||||
self.source = source = stream.Endpoint(eth_phy_description(dw))
|
||||
self.comb += [
|
||||
sink.connect(source),
|
||||
source.data.eq(reverse_bytes(sink.data)),
|
||||
source.last_be.eq(reverse_bits(sink.last_be)),
|
||||
source.error.eq(reverse_bits(sink.error)),
|
||||
]
|
|
@ -14,12 +14,14 @@ from liteeth.common import *
|
|||
|
||||
class LiteEthMACPaddingInserter(Module):
|
||||
def __init__(self, dw, padding):
|
||||
assert dw in [8, 16, 32, 64]
|
||||
self.sink = sink = stream.Endpoint(eth_phy_description(dw))
|
||||
self.source = source = stream.Endpoint(eth_phy_description(dw))
|
||||
|
||||
# # #
|
||||
|
||||
padding_limit = math.ceil(padding/(dw/8))-1
|
||||
last_be = 2**((padding-1)%(dw//8))
|
||||
|
||||
counter = Signal(16)
|
||||
counter_done = Signal()
|
||||
|
@ -33,8 +35,14 @@ class LiteEthMACPaddingInserter(Module):
|
|||
If(sink.last,
|
||||
If(~counter_done,
|
||||
source.last.eq(0),
|
||||
source.last_be.eq(0),
|
||||
NextState("PADDING")
|
||||
).Else(
|
||||
).Elif((counter == padding_limit) & (last_be > sink.last_be),
|
||||
# If the right amount of data words are transmitted, but
|
||||
# too few bytes, transmit more bytes of the word. The
|
||||
# formerly "unused" bytes get transmitted as well
|
||||
source.last_be.eq(last_be)
|
||||
). Else(
|
||||
NextValue(counter, 0),
|
||||
)
|
||||
)
|
||||
|
@ -42,7 +50,9 @@ class LiteEthMACPaddingInserter(Module):
|
|||
)
|
||||
fsm.act("PADDING",
|
||||
source.valid.eq(1),
|
||||
source.last.eq(counter_done),
|
||||
If(counter_done,
|
||||
source.last_be.eq(last_be),
|
||||
source.last.eq(1)),
|
||||
source.data.eq(0),
|
||||
If(source.valid & source.ready,
|
||||
NextValue(counter, counter + 1),
|
||||
|
|
|
@ -25,13 +25,16 @@ class LiteEthMACPreambleInserter(Module):
|
|||
Preamble, SFD, and packet octets.
|
||||
"""
|
||||
def __init__(self, dw):
|
||||
assert dw in [8, 16, 32, 64]
|
||||
self.sink = stream.Endpoint(eth_phy_description(dw))
|
||||
self.source = stream.Endpoint(eth_phy_description(dw))
|
||||
|
||||
# # #
|
||||
|
||||
preamble = Signal(64, reset=eth_preamble)
|
||||
count = Signal(max=(64//dw)-1, reset_less=True)
|
||||
# For 64 bits, `count` doesn't need to change. But migen won't create a
|
||||
# signal with a width of 0 bits, so add an unused bit for 64 bit path
|
||||
count = Signal(max=(64//dw) if dw != 64 else 2, reset_less=True)
|
||||
self.submodules.fsm = fsm = FSM(reset_state="IDLE")
|
||||
fsm.act("IDLE",
|
||||
self.sink.ready.eq(1),
|
||||
|
@ -81,7 +84,7 @@ class LiteEthMACPreambleChecker(Module):
|
|||
Pulses every time a preamble error is detected.
|
||||
"""
|
||||
def __init__(self, dw):
|
||||
assert dw == 8
|
||||
assert dw in [8, 16, 32, 64]
|
||||
self.sink = sink = stream.Endpoint(eth_phy_description(dw))
|
||||
self.source = source = stream.Endpoint(eth_phy_description(dw))
|
||||
|
||||
|
@ -89,10 +92,12 @@ class LiteEthMACPreambleChecker(Module):
|
|||
|
||||
# # #
|
||||
|
||||
preamble = Signal(64, reset=eth_preamble)
|
||||
self.submodules.fsm = fsm = FSM(reset_state="PREAMBLE")
|
||||
fsm.act("PREAMBLE",
|
||||
sink.ready.eq(1),
|
||||
If(sink.valid & ~sink.last & (sink.data == (eth_preamble >> 56)),
|
||||
# Match to end of preamble
|
||||
If(sink.valid & ~sink.last & (sink.data == preamble[-dw:]),
|
||||
NextState("COPY")
|
||||
),
|
||||
If(sink.valid & sink.last, self.error.eq(1))
|
||||
|
|
Loading…
Reference in a new issue