From 5d6cc6c0c64939c1befd88b09104f88ac81549da Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 13 Jun 2025 16:27:22 +0200 Subject: [PATCH 001/159] Add a separate test case for each test file Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 2 +- xls/modules/zstd/zstd_dec_test.x | 169 ++++++++++++++++++++++++++++--- 2 files changed, 156 insertions(+), 15 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 86a5269059..29902bdbb0 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1443,7 +1443,7 @@ xls_dslx_library( xls_dslx_test( name = "zstd_dec_dslx_test", - size = "large", + size = "enormous", srcs = [ "data/comp_frame.x", "data/comp_frame_fse_comp.x", diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 2db5105f38..854bfa06e2 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -28,10 +28,11 @@ import xls.modules.zstd.parallel_rams; import xls.modules.zstd.literals_buffer; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.ram_mux; -// import xls.modules.zstd.zstd_frame_testcases as comp_frame; -// import xls.modules.zstd.data.comp_frame_huffman as comp_frame; -// import xls.modules.zstd.data.comp_frame_fse_comp as comp_frame; -// import xls.modules.zstd.data.comp_frame_fse_repeated as comp_frame; + +import xls.modules.zstd.data.comp_frame_huffman; +import xls.modules.zstd.data.comp_frame_huffman_fse; +import xls.modules.zstd.data.comp_frame_fse_comp; +import xls.modules.zstd.data.comp_frame_fse_repeated; import xls.modules.zstd.data.comp_frame; const TEST_WINDOW_LOG_MAX = u32:30; @@ -153,8 +154,9 @@ fn csr_addr(c: zstd_dec::Csr) -> uN[TEST_AXI_ADDR_W] { (c as uN[TEST_AXI_ADDR_W]) << 3 } -#[test_proc] -proc ZstdDecoderTest { +type TestFrames = common::DataArray[1]; + +proc ZstdDecoderTester { type CsrAxiAr = axi::AxiAr; type CsrAxiR = axi::AxiR; type CsrAxiAw = axi::AxiAw; @@ -258,7 +260,8 @@ proc ZstdDecoderTest { type LitBufRamWrReq = ram::WriteReq; type LitBufRamWrResp = ram::WriteResp; - terminator: chan out; + start_r: chan<()> in; + finished_s: chan<()> out; csr_axi_aw_s: chan out; csr_axi_w_s: chan out; @@ -318,7 +321,7 @@ proc ZstdDecoderTest { init {} - config(terminator: chan out) { + config(start_r: chan<()> in, finished_s: chan<()> out) { let (csr_axi_aw_s, csr_axi_aw_r) = chan("csr_axi_aw"); let (csr_axi_w_s, csr_axi_w_r) = chan("csr_axi_w"); @@ -702,7 +705,7 @@ proc ZstdDecoderTest { >(raw_axi_ar_r, raw_axi_r_s, raw_ram_rd_req_s, raw_ram_rd_resp_r); ( - terminator, + start_r, finished_s, csr_axi_aw_s, csr_axi_w_s, csr_axi_b_r, csr_axi_ar_s, csr_axi_r_r, fh_axi_ar_r, fh_axi_r_s, fh_ram_wr_req_s, fh_ram_wr_resp_r, bh_axi_ar_r, bh_axi_r_s, bh_ram_wr_req_s, bh_ram_wr_resp_r, @@ -721,10 +724,12 @@ proc ZstdDecoderTest { } next (state: ()) { + let tok = join(); + let (tok, _) = recv(tok, start_r); + trace_fmt!("Test start"); - let frames_count = array_size(comp_frame::FRAMES); + let frames_count = array_size(FRAMES); - let tok = join(); // FILL THE LL DEFAULT RAM trace_fmt!("Filling LL default FSE table"); @@ -779,7 +784,7 @@ proc ZstdDecoderTest { let tok = unroll_for! (test_i, tok): (u32, token) in u32:0..frames_count { trace_fmt!("Loading testcase {:x}", test_i + u32:1); - let frame = comp_frame::FRAMES[test_i]; + let frame = FRAMES[test_i]; let tok = for (i, tok): (u32, token) in u32:0..frame.array_length { let req = RamWrReq { addr: i as uN[TEST_RAM_ADDR_W], @@ -839,7 +844,7 @@ proc ZstdDecoderTest { }); let (tok, _) = recv(tok, csr_axi_b_r); - let decomp_frame = comp_frame::DECOMPRESSED_FRAMES[test_i]; + let decomp_frame = DECOMPRESSED_FRAMES[test_i]; // Test ZstdDecoder memory output interface // Mock the output memory buffer as a DSLX array // It is required to handle AXI write transactions and to write the incoming data to @@ -858,7 +863,7 @@ proc ZstdDecoderTest { // The maximal number if beats in AXI burst transaction let MAX_AXI_TRANSFERS = u32:256; // Actual size of decompressed payload for current test - let DECOMPRESSED_BYTES = comp_frame::DECOMPRESSED_FRAMES[test_i].length; + let DECOMPRESSED_BYTES = DECOMPRESSED_FRAMES[test_i].length; trace_fmt!("ZstdDecTest: Start receiving output"); let (tok, final_output_memory, final_output_memory_id, final_transfered_bytes) = for (axi_transaction, (tok, output_memory, output_memory_id, transfered_bytes)): @@ -913,6 +918,142 @@ proc ZstdDecoderTest { tok }(tok); + send(tok, finished_s, ()); + } +} + +#[test_proc] +proc RawLiteralsPredefinedSequencesTest { + terminator: chan out; + start_s: chan<()> out; + finished_r: chan<()> in; + + init {} + + config (terminator: chan out,) { + const FRAMES = comp_frame::FRAMES; + const DECOMPRESSED_FRAMES = comp_frame::DECOMPRESSED_FRAMES; + + let (start_s, start_r) = chan<()>("start"); + let (finished_s, finished_r) = chan<()>("finished"); + + spawn ZstdDecoderTester(start_r, finished_s); + (terminator, start_s, finished_r) + } + + next(state: ()) { + let tok = send(join(), start_s, ()); + let (tok, _) = recv(tok, finished_r); + send(tok, terminator, true); + } +} + +#[test_proc] +proc RleLiteralsRepeatedSequencesTest { + terminator: chan out; + start_s: chan<()> out; + finished_r: chan<()> in; + + init {} + + config (terminator: chan out,) { + + const FRAMES = comp_frame_fse_repeated::FRAMES; + const DECOMPRESSED_FRAMES = comp_frame_fse_repeated::DECOMPRESSED_FRAMES; + + let (start_s, start_r) = chan<()>("start"); + let (finished_s, finished_r) = chan<()>("finished"); + + spawn ZstdDecoderTester(start_r, finished_s); + (terminator, start_s, finished_r) + } + + next(state: ()) { + let tok = send(join(), start_s, ()); + let (tok, _) = recv(tok, finished_r); + send(tok, terminator, true); + } +} + +// TODO: Tests with the `_skip` suffix are disabled in CI +// due to high memory usage. Re-enable them when the DSLX +// interpreter becomes more memory-efficient. + +#[test_proc] +proc RawHuffmanLiteralsPredefinedSequencesTest_skip { + terminator: chan out; + start_s: chan<()> out; + finished_r: chan<()> in; + + init {} + + config (terminator: chan out,) { + const FRAMES = comp_frame_huffman::FRAMES; + const DECOMPRESSED_FRAMES = comp_frame_huffman::DECOMPRESSED_FRAMES; + + let (start_s, start_r) = chan<()>("start"); + let (finished_s, finished_r) = chan<()>("finished"); + + spawn ZstdDecoderTester(start_r, finished_s); + (terminator, start_s, finished_r) + } + + next(state: ()) { + let tok = send(join(), start_s, ()); + let (tok, _) = recv(tok, finished_r); + send(tok, terminator, true); + } +} + +#[test_proc] +proc FseHuffmanLiteralsPredefinedSequencesTest_skip { + terminator: chan out; + start_s: chan<()> out; + finished_r: chan<()> in; + + init {} + + config (terminator: chan out,) { + const FRAMES = comp_frame_huffman_fse::FRAMES; + const DECOMPRESSED_FRAMES = comp_frame_huffman_fse::DECOMPRESSED_FRAMES; + + let (start_s, start_r) = chan<()>("start"); + let (finished_s, finished_r) = chan<()>("finished"); + + spawn ZstdDecoderTester(start_r, finished_s); + (terminator, start_s, finished_r) + } + + next(state: ()) { + let tok = send(join(), start_s, ()); + let (tok, _) = recv(tok, finished_r); + send(tok, terminator, true); + } +} + +#[test_proc] +proc RawHuffmanLiteralsCompressedSequencesTest_skip { + terminator: chan out; + start_s: chan<()> out; + finished_r: chan<()> in; + + init {} + + config (terminator: chan out,) { + + const FRAMES = comp_frame_fse_comp::FRAMES; + const DECOMPRESSED_FRAMES = comp_frame_fse_comp::DECOMPRESSED_FRAMES; + + let (start_s, start_r) = chan<()>("start"); + let (finished_s, finished_r) = chan<()>("finished"); + + spawn ZstdDecoderTester(start_r, finished_s); + (terminator, start_s, finished_r) + } + + next(state: ()) { + let tok = send(join(), start_s, ()); + let (tok, _) = recv(tok, finished_r); send(tok, terminator, true); } } From b7e37417f6b5be38b87599b2bce363e3c80ae320 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Fri, 23 May 2025 12:19:08 +0200 Subject: [PATCH 002/159] Add "pytype binary, library" hint --- xls/modules/zstd/BUILD | 1 + xls/modules/zstd/memory/BUILD | 1 + xls/modules/zstd/rtl/BUILD | 1 + 3 files changed, 3 insertions(+) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 29902bdbb0..413092f58e 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -14,6 +14,7 @@ # Build rules for XLS ZSTD codec implementation. +# pytype binary, library load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") load("@rules_hdl//verilog:providers.bzl", "verilog_library") diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 9ffabd741e..712cc6a212 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +# pytype binary, library load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") load("@rules_hdl//verilog:providers.bzl", "verilog_library") diff --git a/xls/modules/zstd/rtl/BUILD b/xls/modules/zstd/rtl/BUILD index 4447877821..64557ce107 100644 --- a/xls/modules/zstd/rtl/BUILD +++ b/xls/modules/zstd/rtl/BUILD @@ -23,6 +23,7 @@ The sources contain: """ +# pytype binary, library load("@rules_hdl//verilog:providers.bzl", "verilog_library") package( From ce75b89a478a24150cad8ed6cf9ef3af646d35b7 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Mon, 29 Dec 2025 09:42:15 +0100 Subject: [PATCH 003/159] Add cocotb tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add cocotb testing utilities: - XLSStruct for easier handling and serializing/deserializing XLS structs - XLSChannel that serves as a dummy receiving channel - XLSMonitor that monitors transactions on an XLS channel - XLSDriver that can send data on an XLS channel - LatencyScoreboard that can measure latency between corresponding transactions on input and output buses - File-backed AXI memory python model Add cocotb tests for: - modules/zstd/memory/MemReader - modules/zstd/memory/AxiWriter - modules/zstd/memory/MemWriter Co-authred-by: Pawel Czarnecki Co-authred-by: Robert Winkler Co-authred-by: Michal Czyz Signed-off-by: Michal Czyz Signed-off-by: Pawel Czarnecki Signed-off-by: Robert Winkler Signed-off-by: Krzysztof Obłonczek --- dependency_support/pip_requirements.in | 10 + dependency_support/pip_requirements_lock.txt | 70 +- xls/modules/zstd/cocotb/BUILD | 66 ++ xls/modules/zstd/cocotb/channel.py | 95 +++ xls/modules/zstd/cocotb/memory.py | 43 ++ xls/modules/zstd/cocotb/scoreboard.py | 69 ++ xls/modules/zstd/cocotb/utils.py | 57 ++ xls/modules/zstd/cocotb/xlsstruct.py | 175 +++++ xls/modules/zstd/memory/BUILD | 82 +++ .../zstd/memory/axi_writer_cocotb_test.py | 245 +++++++ xls/modules/zstd/memory/axi_writer_wrapper.v | 119 ++++ .../zstd/memory/mem_reader_cocotb_test.py | 271 +++++++ xls/modules/zstd/memory/mem_reader_wrapper.v | 111 +++ .../zstd/memory/mem_writer_cocotb_test.py | 667 ++++++++++++++++++ xls/modules/zstd/memory/mem_writer_wrapper.v | 193 +++++ 15 files changed, 2272 insertions(+), 1 deletion(-) create mode 100644 xls/modules/zstd/cocotb/BUILD create mode 100644 xls/modules/zstd/cocotb/channel.py create mode 100644 xls/modules/zstd/cocotb/memory.py create mode 100644 xls/modules/zstd/cocotb/scoreboard.py create mode 100644 xls/modules/zstd/cocotb/utils.py create mode 100644 xls/modules/zstd/cocotb/xlsstruct.py create mode 100644 xls/modules/zstd/memory/axi_writer_cocotb_test.py create mode 100644 xls/modules/zstd/memory/axi_writer_wrapper.v create mode 100644 xls/modules/zstd/memory/mem_reader_cocotb_test.py create mode 100644 xls/modules/zstd/memory/mem_reader_wrapper.v create mode 100644 xls/modules/zstd/memory/mem_writer_cocotb_test.py create mode 100644 xls/modules/zstd/memory/mem_writer_wrapper.v diff --git a/dependency_support/pip_requirements.in b/dependency_support/pip_requirements.in index 9d86ed7c11..6046107abe 100644 --- a/dependency_support/pip_requirements.in +++ b/dependency_support/pip_requirements.in @@ -15,3 +15,13 @@ pyyaml==6.0.1 # We build most of z3 ourselves but building python is really complicated. Just # use pypi version z3-solver==4.14.0.0 +pytest==8.2.2 +cocotb==1.9.0 +cocotbext-axi==0.1.24 +cocotb_bus==0.2.1 + +# Note: numpy and scipy version availability seems to differ between Ubuntu +# versions that we want to support (e.g. 18.04 vs 20.04), so we accept a +# range that makes successful installation on those platforms possible. +numpy>=1.21 +scipy>=1.5.4,<=1.10.1 diff --git a/dependency_support/pip_requirements_lock.txt b/dependency_support/pip_requirements_lock.txt index 65c9ff6478..703976edf1 100644 --- a/dependency_support/pip_requirements_lock.txt +++ b/dependency_support/pip_requirements_lock.txt @@ -14,6 +14,56 @@ click==8.1.3 \ # via # -r dependency_support/pip_requirements.in # flask +cocotb==1.9.0 \ + --hash=sha256:02a58ef6c941114964096e7c039bdd4e67e63816cfd2f6a9af6a34cd92b00e8e \ + --hash=sha256:0819794ef5e8fd14fee0b265933226cf600e85edc2f1a749b4d5f8fa2d31ce4e \ + --hash=sha256:0ba35617a677ff65a1273411a3dfdfc5f587128ad8cb9e941ab0eb17ec8fb3e2 \ + --hash=sha256:17556e3a23562f64d577d0eb117fe02e384aedee997b29497b5c395f5010ff82 \ + --hash=sha256:19b4e27b53a16e0b9c4cc5227c7f9d4dccac06e431a4f937e9f5513350196333 \ + --hash=sha256:1a0381ced5590a726032ba2265c6b70ac12cfb49edb152be86a081bb7d104751 \ + --hash=sha256:1aff68cf77059448a9a3278079037e34b50c8c2aee466d984295fa7fe699d390 \ + --hash=sha256:277281420fd6fc3002bb85d6bec497bd20ff3a3905d4b5f1301faf975f750ede \ + --hash=sha256:2daf743320331615f4e8ffb877ab0b04e6f913b911bb11bf9dbc1d876d9c4220 \ + --hash=sha256:2e9bcdbfba3e99c9297bd0d74ba781772d89d2c86e893980784ada252bd1a0f8 \ + --hash=sha256:3058c977f9d4e1f6333d505947f34b9142910719f1d8631c40a151dd86bad727 \ + --hash=sha256:5832d894419a9e8fe5c242e3ac86588e16e2cb379822dcb154bfec8544ae858e \ + --hash=sha256:598b841ed0809e5c64d8c383b8035f6ace5a6f9013f680cdc6981221911c005d \ + --hash=sha256:5a5c91027d7652aaf10e101743edd6b1e832039a19af75fca301275ef30f01d4 \ + --hash=sha256:61418f619af72c8cca8de622785b4f4bfc17ace09981de6eb44feae560cf3bbb \ + --hash=sha256:784c914c8df3fd79cfb148d2bcd17c4b2703c89af1278ed98773afb57ceea3e6 \ + --hash=sha256:87a19d3012f505ba7fda37483b851ef0ca40290ad8a9b28a820b84f8574287bb \ + --hash=sha256:89503f0749362d36b6fab8636710f1848943c21f9d488672921bac21e9edd29f \ + --hash=sha256:89e5189fd393918c27af2daefdcb13df4d52fa761f065d5964d2c4ff5c0642fb \ + --hash=sha256:8cb4b0edf8f0b47c3b604b461cb574fc75fd97efa893cbaf828f4f2f71cf459e \ + --hash=sha256:94e884e16186899ad5b4d131c3f7ff0a2277e67ea0660754e8810a4bbf2d610e \ + --hash=sha256:997dbca2a2cd933fd0a44d9fadeebc1e8a40701db15ea06f207811933dceb350 \ + --hash=sha256:a7cea13cb2fe4f5ca735490846342885117778a73008a67ed9cac667aaaf3f0d \ + --hash=sha256:a84edfbfa57dc6e16845a55feb0b4e1c8b6bbfa5ef1ab6768beba8d81e0546aa \ + --hash=sha256:a95b5e5708a3629d319d2b655d11345cc7e97fea9bdc9bc1df7435926ac30966 \ + --hash=sha256:aa6818c39ca1ce699e4bb1d84899c4f98c2d25c7671bd6c7beee3b1ee9d68834 \ + --hash=sha256:ab99bf7e055780b57419d4133fd4dca9c72a03b766a3e2200552f10498eb8845 \ + --hash=sha256:b966f5560a494fd99f95a1562f9326ca20c35bb118d4e6b50db41da8e4a6f718 \ + --hash=sha256:bc44a7708a5a63d3059a622c2fb90831dc33534c3343e971f5a6c78905097baa \ + --hash=sha256:c11e21d291ba2f889e33c21d76e9aec6ffdfb5666053dc34452666579daa675b \ + --hash=sha256:c848de13583478d71cc91e528e17c051ca6a3b92e89d703ac5015f17cab1287b \ + --hash=sha256:d944aa5509a0f0786d6f30554a2f8b1f229847f9ac9988879d7a05497739f668 \ + --hash=sha256:f50862153e1364f6edeaef9d70505093549fa097e9b2555ea46d1e4f94ac3287 \ + --hash=sha256:f74c598e230e1035103f6e3a97dd7a0e1bcacf7f3ea7481cd3bcde477b74e379 \ + --hash=sha256:fcb81c6c37e11b0729768dd8e192a9cfb809778699ab1fe89f4d92ba0beb3092 \ + --hash=sha256:ff2ddc8b304eb7076ceead2534a1b9828df771798fa9c2601ea983c86d23ec08 + # via + # -r dependency_support/pip_requirements.in + # cocotb-bus + # cocotbext-axi +cocotb-bus==0.2.1 \ + --hash=sha256:a197aa4b0e0ad28469c8877b41b3fb2ec0206da9f491b9276d1578ce6dd8aa8d + # via + # -r dependency_support/pip_requirements.in + # cocotbext-axi +cocotbext-axi==0.1.24 \ + --hash=sha256:3ed62dcaf9448833176826507c5bc5c346431c4846a731e409d87c862d960593 \ + --hash=sha256:533ba6c7503c6302bdb9ef86e43a549ad5da876eafb1adce23d39751c54cced4 + # via -r dependency_support/pip_requirements.in contourpy==1.3.1 \ --hash=sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1 \ --hash=sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda \ @@ -74,6 +124,10 @@ cycler==0.12.1 \ --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \ --hash=sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c # via matplotlib +find-libpython==0.4.0 \ + --hash=sha256:034a4253bd57da3408aefc59aeac1650150f6c1f42e10fdd31615cf1df0842e3 \ + --hash=sha256:46f9cdcd397ddb563b2d7592ded3796a41c1df5222443bd9d981721c906c03e6 + # via cocotb flask==2.3.2 \ --hash=sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0 \ --hash=sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef @@ -130,6 +184,10 @@ fonttools==4.55.8 \ --hash=sha256:f089e8da0990cfe2d67e81d9cf581ff372b48dc5acf2782701844211cd1f0eb3 \ --hash=sha256:f971aa5f50c22dc4b63a891503624ae2c77330429b34ead32f23c2260c5618cd # via matplotlib +iniconfig==2.0.0 \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 + # via pytest itsdangerous==2.1.2 \ --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a @@ -392,7 +450,9 @@ numpy==2.3.4 \ packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f - # via matplotlib + # via + # matplotlib + # pytest pillow==11.1.0 \ --hash=sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83 \ --hash=sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96 \ @@ -466,6 +526,10 @@ pillow==11.1.0 \ --hash=sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9 \ --hash=sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761 # via matplotlib +pluggy==1.5.0 \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 + # via pytest portpicker==1.3.1 \ --hash=sha256:d2cdc776873635ed421315c4d22e63280042456bbfa07397817e687b142b9667 # via -r dependency_support/pip_requirements.in @@ -486,6 +550,10 @@ pyparsing==3.2.1 \ --hash=sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1 \ --hash=sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a # via matplotlib +pytest==8.2.2 \ + --hash=sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343 \ + --hash=sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977 + # via -r dependency_support/pip_requirements.in python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 diff --git a/xls/modules/zstd/cocotb/BUILD b/xls/modules/zstd/cocotb/BUILD new file mode 100644 index 0000000000..23b1a843c3 --- /dev/null +++ b/xls/modules/zstd/cocotb/BUILD @@ -0,0 +1,66 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@xls_pip_deps//:requirements.bzl", "requirement") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +py_library( + name = "channel", + srcs = ["channel.py"], + deps = [ + ":xlsstruct", + requirement("cocotb"), + requirement("cocotb_bus"), + ], +) + +py_library( + name = "memory", + srcs = ["memory.py"], + deps = [ + requirement("cocotbext-axi"), + ], +) + +py_library( + name = "scoreboard", + srcs = ["scoreboard.py"], + deps = [ + ":channel", + ":xlsstruct", + requirement("cocotb"), + ], +) + +py_library( + name = "utils", + srcs = ["utils.py"], + deps = [ + requirement("cocotb"), + "//xls/common:runfiles", + ], +) + +py_library( + name = "xlsstruct", + srcs = ["xlsstruct.py"], + deps = [ + requirement("cocotb"), + ], +) diff --git a/xls/modules/zstd/cocotb/channel.py b/xls/modules/zstd/cocotb/channel.py new file mode 100644 index 0000000000..0970ab6e9b --- /dev/null +++ b/xls/modules/zstd/cocotb/channel.py @@ -0,0 +1,95 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Sequence, Type, Union + +import cocotb +from cocotb.handle import SimHandleBase +from cocotb.triggers import RisingEdge +from cocotb_bus.bus import Bus +from cocotb_bus.drivers import BusDriver +from cocotb_bus.monitors import BusMonitor + +from xls.modules.zstd.cocotb.xlsstruct import XLSStruct + +Transaction = Union[XLSStruct, Sequence[XLSStruct]] + +XLS_CHANNEL_SIGNALS = ["data", "rdy", "vld"] +XLS_CHANNEL_OPTIONAL_SIGNALS = [] + + +class XLSChannel(Bus): + _signals = XLS_CHANNEL_SIGNALS + _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS + + def __init__(self, entity, name, clk, *, start_now=False, **kwargs: Any): + super().__init__(entity, name, self._signals, self._optional_signals, **kwargs) + self.clk = clk + if start_now: + self.start_recv_loop() + + @cocotb.coroutine + async def recv_channel(self): + """Cocotb coroutine that acts as a proc receiving data from a channel""" + self.rdy.setimmediatevalue(1) + while True: + await RisingEdge(self.clk) + + def start_recv_loop(self): + cocotb.start_soon(self.recv_channel()) + + +class XLSChannelDriver(BusDriver): + _signals = XLS_CHANNEL_SIGNALS + _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS + + def __init__(self, entity: SimHandleBase, name: str, clock: SimHandleBase, **kwargs: Any): + BusDriver.__init__(self, entity, name, clock, **kwargs) + + self.bus.data.setimmediatevalue(0) + self.bus.vld.setimmediatevalue(0) + + async def _driver_send(self, transaction: Transaction, sync: bool = True, **kwargs: Any) -> None: + if sync: + await RisingEdge(self.clock) + + data_to_send = (transaction if isinstance(transaction, Sequence) else [transaction]) + + for word in data_to_send: + self.bus.vld.value = 1 + self.bus.data.value = word.binaryvalue + + while True: + await RisingEdge(self.clock) + if self.bus.rdy.value: + break + + self.bus.vld.value = 0 + + +class XLSChannelMonitor(BusMonitor): + _signals = XLS_CHANNEL_SIGNALS + _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS + + def __init__(self, entity: SimHandleBase, name: str, clock: SimHandleBase, struct: Type[XLSStruct], **kwargs: Any): + BusMonitor.__init__(self, entity, name, clock, **kwargs) + self.struct = struct + + @cocotb.coroutine + async def _monitor_recv(self) -> None: + while True: + await RisingEdge(self.clock) + if self.bus.rdy.value and self.bus.vld.value: + vec = self.struct.from_int(self.bus.data.value.integer) + self._recv(vec) diff --git a/xls/modules/zstd/cocotb/memory.py b/xls/modules/zstd/cocotb/memory.py new file mode 100644 index 0000000000..52e512e053 --- /dev/null +++ b/xls/modules/zstd/cocotb/memory.py @@ -0,0 +1,43 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from cocotbext.axi.axi_ram import AxiRam, AxiRamRead, AxiRamWrite +from cocotbext.axi.sparse_memory import SparseMemory + + +def init_axi_mem(path: os.PathLike, kwargs): + with open(path, "rb") as f: + sparse_mem = SparseMemory(size=kwargs["size"]) + sparse_mem.write(0x0, f.read()) + kwargs["mem"] = sparse_mem + + +class AxiRamReadFromFile(AxiRamRead): + def __init__(self, *args, path: os.PathLike, **kwargs): + init_axi_mem(path, kwargs) + super().__init__(*args, **kwargs) + + +class AxiRamFromFile(AxiRam): + def __init__(self, *args, path: os.PathLike, **kwargs): + init_axi_mem(path, kwargs) + super().__init__(*args, **kwargs) + + +class AxiRamWriteFromFile(AxiRamWrite): + def __init__(self, *args, path: os.PathLike, **kwargs): + init_axi_mem(path, kwargs) + super().__init__(*args, **kwargs) diff --git a/xls/modules/zstd/cocotb/scoreboard.py b/xls/modules/zstd/cocotb/scoreboard.py new file mode 100644 index 0000000000..b9b64ca6e2 --- /dev/null +++ b/xls/modules/zstd/cocotb/scoreboard.py @@ -0,0 +1,69 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass +from queue import Queue + +from cocotb.clock import Clock +from cocotb.log import SimLog +from cocotb.utils import get_sim_time + +from xls.modules.zstd.cocotb.channel import XLSChannelMonitor +from xls.modules.zstd.cocotb.xlsstruct import XLSStruct + + +@dataclass +class LatencyQueueItem: + transaction: XLSStruct + timestamp: int + + +class LatencyScoreboard: + def __init__(self, dut, clock: Clock, req_monitor: XLSChannelMonitor, resp_monitor: XLSChannelMonitor): + self.dut = dut + self.log = SimLog(f"zstd.cocotb.scoreboard.{self.dut._name}") + self.clock = clock + self.req_monitor = req_monitor + self.resp_monitor = resp_monitor + self.pending_req = Queue() + self.results = [] + + self.req_monitor.add_callback(self._req_callback) + self.resp_monitor.add_callback(self._resp_callback) + + def _current_cycle(self): + return get_sim_time(units='step') / self.clock.period + + def _req_callback(self, transaction: XLSStruct): + self.pending_req.put(LatencyQueueItem(transaction, self._current_cycle())) + + def _resp_callback(self, transaction: XLSStruct): + latency_item = self.pending_req.get() + self.results.append(self._current_cycle() - latency_item.timestamp) + + def average_latency(self): + return sum(self.results)/len(self.results) + + def report_result(self): + if not self.pending_req.empty(): + self.log.warning(f"There are unfulfilled requests from channel {self.req_monitor.name}") + while not self.pending_req.empty(): + self.log.warning(f"Unfulfilled request: {self.pending_req.get()}") + if len(self.results) > 0: + self.log.info(f"Latency report - 1st latency: {self.results[0]}") + if len(self.results) > 1: + self.log.info(f"Latency report - 2nd latency: {self.results[1]}") + if len(self.results) > 2: + avg = sum(self.results[2:])/len(self.results[2:]) + self.log.info(f"Latency report - rest of the latencies (average): {avg}") diff --git a/xls/modules/zstd/cocotb/utils.py b/xls/modules/zstd/cocotb/utils.py new file mode 100644 index 0000000000..0930a92932 --- /dev/null +++ b/xls/modules/zstd/cocotb/utils.py @@ -0,0 +1,57 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from pathlib import Path + +import cocotb +from cocotb.runner import check_results_file, get_runner +from cocotb.triggers import ClockCycles + +from xls.common import runfiles + + +def setup_com_iverilog(): + iverilog_path = Path(runfiles.get_path("iverilog", repository = "com_icarus_iverilog")) + vvp_path = Path(runfiles.get_path("vvp", repository = "com_icarus_iverilog")) + os.environ["PATH"] += os.pathsep + str(iverilog_path.parent) + os.environ["PATH"] += os.pathsep + str(vvp_path.parent) + build_dir = Path(os.environ['BUILD_WORKING_DIRECTORY'], "sim_build") + return build_dir + +def run_test(toplevel, test_module, verilog_sources): + build_dir = setup_com_iverilog() + runner = get_runner("icarus") + runner.build( + verilog_sources=verilog_sources, + hdl_toplevel=toplevel, + timescale=("1ns", "1ps"), + build_dir=build_dir, + defines={"SIMULATION": "1"}, + waves=True, + ) + + results_xml = runner.test( + hdl_toplevel=toplevel, + test_module=test_module, + waves=True, + ) + check_results_file(results_xml) + +@cocotb.coroutine +async def reset(clk, rst, cycles=1): + """Cocotb coroutine that performs the reset""" + rst.value = 1 + await ClockCycles(clk, cycles) + rst.value = 0 diff --git a/xls/modules/zstd/cocotb/xlsstruct.py b/xls/modules/zstd/cocotb/xlsstruct.py new file mode 100644 index 0000000000..a2d686a8af --- /dev/null +++ b/xls/modules/zstd/cocotb/xlsstruct.py @@ -0,0 +1,175 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import random +from dataclasses import asdict, astuple, dataclass, fields + +from cocotb.binary import BinaryValue + + +class TruncationError(Exception): + pass + +def xls_dataclass(cls): + """ + Class decorator for XLS structs. + Usage: + + @xls_dataclass + class MyStruct(XLSStruct): + ... + """ + return dataclass(cls, repr=False) + +@dataclass +class XLSStruct: + """ + Represents XLS struct on the Python side, allowing serialization/deserialization + to/from common formats and usage with XLS{Driver, Monitor}. + + The intended way to use this class is to inherit from it, specify the fields with + : [= ] syntax and decorate the inheriting class with + @XLSDataclass. Objects of this class can be instantiated and used like usual + dataclass objects, with a few extra methods and properties available. They can also + be passed as arguments to XLSChannelDriver.send and will be serialized to expected + bit vector. Class can be passed to XLSChannelMonitor ``struct`` constructor argument + to automatically deserialize all transfers to the provided struct. + + Example: + + from xlsstruct import XLSDataclass, XLSStruct + + @XLSDataclass + class MyStruct(XLSStruct): + data: 32 + ok: 1 + id: 4 = 0 + + monitor = XLSChannelMonitor(dut, CHANNEL_PREFIX, dut.clk, MyStruct) + + driver = XLSChannelDriver(dut, CHANNEL_PREFIX, dut.clk) + driver.send(MyStruct( + data = 0xdeadbeef, + ok = 1, + id = 3, + )) + # struct fields can also be randomized + driver.send(MyStruct.randomize()) + """ + + @classmethod + def _masks(cls): + """ + Returns a list of field-sized bitmasks. + + For example for fields of widths 2, 3, 4 + returns [2'b11, 3'b111, 4'b1111]. + """ + masks = [] + for field in fields(cls): + width = field.type + masks += [(1 << width) - 1] + return masks + + @classmethod + def _positions(cls): + """ + Returns a list of start positions in a bit vector for + struct's fields. + + For example for fields of widths 1, 2, 3, 4, 5, 6 + returns [20, 18, 15, 11, 6, 0] + """ + positions = [] + for i, field in enumerate(fields(cls)): + width = field.type + if i == 0: + positions += [cls.total_width - width] + else: + positions += [positions[i-1] - width] + return positions + + @classmethod + @property + def total_width(cls): + """ + Returns total bit width of the struct + """ + return sum(field.type for field in fields(cls)) + + @property + def value(self): + """ + Returns struct's value as a Python integer + """ + value = 0 + masks = self._masks() + positions = self._positions() + for field_val, mask, pos in zip(astuple(self), masks, positions): + if field_val > mask: + raise TruncationError(f"Signal value is wider than its bit width") + value |= (field_val & mask) << pos + return value + + @property + def binaryvalue(self): + """ + Returns struct's value as a cocotb.binary.BinaryValue + """ + return BinaryValue(self.binstr) + + @property + def binstr(self): + """ + Returns struct's value as a string with its binary representation + """ + return f"{self.value:>0{self.total_width}b}" + + @property + def hexstr(self): + """ + Returns struct's value as a string with its hex representation + (without leading "0x") + """ + return f"{self.value:>0{self.total_width // 4}x}" + + @classmethod + def from_int(cls, value): + """ + Returns an instance of the struct from Python integer + """ + instance = {} + masks = cls._masks() + positions = cls._positions() + for field, mask, pos in zip(fields(cls), masks, positions): + instance[field.name] = (value >> pos) & mask + return cls(**instance) + + @classmethod + def randomize(cls): + """ + Returns an instance of the struct with all fields' values randomized + """ + instance = {} + for field in fields(cls): + instance[field.name] = random.randrange(0, 2**field.type) + return cls(**instance) + + def __str__(self): + return self.__repr__() + + def __repr__(self): + classname = self.__class__.__name__ + fields = [f"{name}={hex(value)}" for name, value in asdict(self).items()] + return f"{classname}({', '.join(fields)})" diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 712cc6a212..3fd85d42d7 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -16,6 +16,7 @@ load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") load("@rules_hdl//verilog:providers.bzl", "verilog_library") +load("@xls_pip_deps//:requirements.bzl", "requirement") load( "//xls/build_rules:xls_build_defs.bzl", "xls_benchmark_ir", @@ -547,6 +548,33 @@ place_and_route( target_die_utilization_percentage = "10", ) +py_test( + name = "mem_reader_cocotb_test", + srcs = ["mem_reader_cocotb_test.py"], + data = [ + ":mem_reader_adv.v", + ":mem_reader_wrapper.v", + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("cocotb"), + requirement("cocotbext-axi"), + requirement("pytest"), + "//xls/common:runfiles", + "//xls/modules/zstd/cocotb:channel", + "//xls/modules/zstd/cocotb:memory", + "//xls/modules/zstd/cocotb:utils", + "//xls/modules/zstd/cocotb:xlsstruct", + "@abseil-py//absl:app", + "@abseil-py//absl/flags", + "@com_google_protobuf//:protobuf_python", + ], +) + xls_dslx_library( name = "axi_writer_dslx", srcs = ["axi_writer.x"], @@ -616,6 +644,33 @@ place_and_route( target_die_utilization_percentage = "10", ) +py_test( + name = "axi_writer_cocotb_test", + srcs = ["axi_writer_cocotb_test.py"], + data = [ + ":axi_writer.v", + ":axi_writer_wrapper.v", + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, + imports = ["."], + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("cocotb"), + requirement("cocotbext-axi"), + requirement("pytest"), + "//xls/common:runfiles", + "//xls/modules/zstd/cocotb:channel", + "//xls/modules/zstd/cocotb:utils", + "//xls/modules/zstd/cocotb:xlsstruct", + "@abseil-py//absl:app", + "@abseil-py//absl/flags", + "@com_google_protobuf//:protobuf_python", + ], +) + xls_dslx_library( name = "axi_stream_add_empty_dslx", srcs = ["axi_stream_add_empty.x"], @@ -754,3 +809,30 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + +py_test( + name = "mem_writer_cocotb_test", + srcs = ["mem_writer_cocotb_test.py"], + data = [ + ":mem_writer.v", + ":mem_writer_wrapper.v", + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, + imports = ["."], + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("cocotb"), + requirement("cocotbext-axi"), + requirement("pytest"), + "//xls/common:runfiles", + "//xls/modules/zstd/cocotb:channel", + "//xls/modules/zstd/cocotb:utils", + "//xls/modules/zstd/cocotb:xlsstruct", + "@abseil-py//absl:app", + "@abseil-py//absl/flags", + "@com_google_protobuf//:protobuf_python", + ], +) diff --git a/xls/modules/zstd/memory/axi_writer_cocotb_test.py b/xls/modules/zstd/memory/axi_writer_cocotb_test.py new file mode 100644 index 0000000000..b30876a687 --- /dev/null +++ b/xls/modules/zstd/memory/axi_writer_cocotb_test.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import random +import logging +from pathlib import Path + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, Event +from cocotb.binary import BinaryValue +from cocotb_bus.scoreboard import Scoreboard + +from cocotbext.axi.axis import AxiStreamSource, AxiStreamBus, AxiStreamFrame +from cocotbext.axi.axi_channels import AxiAWBus, AxiWBus, AxiBBus, AxiWriteBus, AxiAWMonitor, AxiWMonitor, AxiBMonitor, AxiBTransaction, AxiBSource, AxiBSink +from cocotbext.axi.axi_ram import AxiRamWrite +from cocotbext.axi.sparse_memory import SparseMemory + +from xls.modules.zstd.cocotb.channel import ( + XLSChannel, + XLSChannelDriver, + XLSChannelMonitor, +) +from xls.modules.zstd.cocotb.utils import reset, run_test +from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass + +ID_WIDTH = 4 +ADDR_WIDTH = 16 + +# Override default widths of AXI response signals +signal_widths = {"bresp": 3} +AxiBBus._signal_widths = signal_widths +AxiBTransaction._signal_widths = signal_widths +AxiBSource._signal_widths = signal_widths +AxiBSink._signal_widths = signal_widths +AxiBMonitor._signal_widths = signal_widths + +@xls_dataclass +class AxiWriterRespStruct(XLSStruct): + status: 1 + +@xls_dataclass +class WriteRequestStruct(XLSStruct): + address: ADDR_WIDTH + length: ADDR_WIDTH + +def set_termination_event(monitor, event, transactions): + def terminate_cb(_): + if monitor.stats.received_transactions == transactions: + event.set() + monitor.add_callback(terminate_cb) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def ram_test(dut): + GENERIC_ADDR_REQ_CHANNEL = "write_req" + GENERIC_ADDR_RESP_CHANNEL = "write_resp" + AXI_STREAM_CHANNEL = "axi_st_read" + AXI_AW_CHANNEL = "axi_aw" + AXI_W_CHANNEL = "axi_w" + AXI_B_CHANNEL = "axi_b" + + terminate = Event() + + mem_size = 2**ADDR_WIDTH + test_count = 200 + + (addr_req_input, axi_st_input, addr_resp_expect, memory_verification, expected_memory) = generate_test_data_random(test_count, mem_size) + + dut.rst.setimmediatevalue(0) + + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + resp_bus = XLSChannel(dut, GENERIC_ADDR_RESP_CHANNEL, dut.clk, start_now=True) + + driver_addr_req = XLSChannelDriver(dut, GENERIC_ADDR_REQ_CHANNEL, dut.clk) + driver_axi_st = AxiStreamSource(AxiStreamBus.from_prefix(dut, AXI_STREAM_CHANNEL), dut.clk, dut.rst) + + bus_axi_aw = AxiAWBus.from_prefix(dut, AXI_AW_CHANNEL) + bus_axi_w = AxiWBus.from_prefix(dut, AXI_W_CHANNEL) + bus_axi_b = AxiBBus.from_prefix(dut, AXI_B_CHANNEL) + bus_axi_write = AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) + + monitor_addr_req = XLSChannelMonitor(dut, GENERIC_ADDR_REQ_CHANNEL, dut.clk, WriteRequestStruct) + monitor_addr_resp = XLSChannelMonitor(dut, GENERIC_ADDR_RESP_CHANNEL, dut.clk, AxiWriterRespStruct) + monitor_axi_aw = AxiAWMonitor(bus_axi_aw, dut.clk, dut.rst) + monitor_axi_w = AxiWMonitor(bus_axi_w, dut.clk, dut.rst) + monitor_axi_b = AxiBMonitor(bus_axi_b, dut.clk, dut.rst) + + set_termination_event(monitor_addr_resp, terminate, test_count) + + memory = AxiRamWrite(bus_axi_write, dut.clk, dut.rst, size=mem_size) + + log = logging.getLogger("cocotb.tb") + log.setLevel(logging.WARNING) + memory.log.setLevel(logging.WARNING) + driver_axi_st.log.setLevel(logging.WARNING) + + scoreboard = Scoreboard(dut) + scoreboard.add_interface(monitor_addr_resp, addr_resp_expect) + + await reset(dut.clk, dut.rst, cycles=10) + await cocotb.start(driver_addr_req.send(addr_req_input)) + await cocotb.start(drive_axi_st(driver_axi_st, axi_st_input)) + await terminate.wait() + + for bundle in memory_verification: + memory_contents = bytearray(memory.read(bundle["base_address"], bundle["length"])) + expected_memory_contents = bytearray(expected_memory.read(bundle["base_address"], bundle["length"])) + assert memory_contents == expected_memory_contents, "{} bytes of memory contents at base address {}:\n{}\nvs\n{}\nHEXDUMP:\n{}\nvs\n{}".format(hex(bundle["length"]), hex(bundle["base_address"]), memory_contents, expected_memory_contents, memory.hexdump(bundle["base_address"], bundle["length"]), expected_memory.hexdump(bundle["base_address"], bundle["length"])) + +@cocotb.coroutine +async def drive_axi_st(driver, inputs): + for axi_st_input in inputs: + await driver.send(axi_st_input) + +def generate_test_data_random(test_count, mem_size): + AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x + + addr_req_input = [] + axi_st_input = [] + addr_resp_expect = [] + memory_verification = [] + memory = SparseMemory(mem_size) + + random.seed(1234) + + for i in range(test_count): + xfer_addr = random.randrange(0, mem_size) + # Don't allow unaligned writes + xfer_addr_aligned = (xfer_addr // 4) * 4 + # Make sure we don't write beyond available memory + memory_size_max_xfer_len = mem_size - xfer_addr_aligned + arbitrary_max_xfer_len = 0x5000 # 20kB + xfer_max_len = min(arbitrary_max_xfer_len, memory_size_max_xfer_len) + xfer_len = random.randrange(1, xfer_max_len) + transfer_req = WriteRequestStruct( + address = xfer_addr_aligned, + length = xfer_len, + ) + addr_req_input.append(transfer_req) + + data_to_write = random.randbytes(xfer_len) + axi_st_frame = AxiStreamFrame(tdata=data_to_write, tkeep=[15]*xfer_len, tid=(i % (1 << ID_WIDTH)), tdest=(i % (1 << ID_WIDTH))) + axi_st_input.append(axi_st_frame) + + write_expected_memory(transfer_req, axi_st_frame.tdata, memory) + + memory_bundle = { + "base_address": transfer_req.address, + "length": transfer_req.length, + } + memory_verification.append(memory_bundle) + + addr_resp_expect = [AxiWriterRespStruct(status=False)] * test_count + + return (addr_req_input, axi_st_input, addr_resp_expect, memory_verification, memory) + +def bytes_to_4k_boundary(addr): + AXI_4K_BOUNDARY = 0x1000 + return AXI_4K_BOUNDARY - (addr % AXI_4K_BOUNDARY) + +def write_expected_memory(transfer_req, data_to_write, memory): + """ + Write test data to reference memory keeping the AXI 4kb boundary + by spliting the write requests into smaller ones. + """ + prev_id = 0 + address = transfer_req.address + length = transfer_req.length + + BYTES_IN_TRANSFER = 4 + MAX_AXI_BURST_BYTES = 256 * BYTES_IN_TRANSFER + + while (length > 0): + bytes_to_4k = bytes_to_4k_boundary(address) + new_len = min(length, min(bytes_to_4k, MAX_AXI_BURST_BYTES)) + new_data = data_to_write[prev_id:prev_id+new_len] + memory.write(address, new_data) + address = address + new_len + length = length - new_len + prev_id = prev_id + new_len + +def generate_test_data_arbitrary(mem_size): + AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x + + addr_req_input = [] + axi_st_input = [] + addr_resp_expect = [] + memory_verification = [] + memory = SparseMemory(mem_size) + + xfer_addr_begin = [0, 8, 512, 1000, 0x1234, 256] + xfer_len = [1, 2, 4, 8, 0x48d, 4] + assert len(xfer_len) == len(xfer_addr_begin) + testcase_num = len(xfer_addr_begin) # test cases to execute + for i in range(testcase_num): + transfer_req = WriteRequestStruct( + address = xfer_addr_begin[i], + length = xfer_len[i] * 4, # xfer_len[i] transfers per 4 bytes + ) + addr_req_input.append(transfer_req) + + data_chunks = [] + data_bytes = [[(0xEF + j) & 0xFF, 0xBE, 0xAD, 0xDE] for j in range(xfer_len[i])] + data_words = [int.from_bytes(data_bytes[j]) for j in range(xfer_len[i])] + for j in range(xfer_len[i]): + data_chunks += data_bytes[j] + data_to_write = bytearray(data_chunks) + axi_st_frame = AxiStreamFrame(tdata=data_to_write, tkeep=[15]*xfer_len[i], tid=i, tdest=i) + axi_st_input.append(axi_st_frame) + + write_expected_memory(transfer_req, axi_st_frame.tdata, memory) + + memory_bundle = { + "base_address": transfer_req.address, + "length": transfer_req.length, # 4 byte words + } + memory_verification.append(memory_bundle) + + addr_resp_expect = [AxiWriterRespStruct(status=False)] * testcase_num + + return (addr_req_input, axi_st_input, addr_resp_expect, memory_verification, memory) + +if __name__ == "__main__": + toplevel = "axi_writer_wrapper" + verilog_sources = [ + "xls/modules/zstd/memory/axi_writer.v", + "xls/modules/zstd/memory/axi_writer_wrapper.v", + ] + test_module=[Path(__file__).stem] + run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/memory/axi_writer_wrapper.v b/xls/modules/zstd/memory/axi_writer_wrapper.v new file mode 100644 index 0000000000..556f839284 --- /dev/null +++ b/xls/modules/zstd/memory/axi_writer_wrapper.v @@ -0,0 +1,119 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +`default_nettype none + +module axi_writer_wrapper ( + input wire clk, + input wire rst, + + output wire write_resp_data, + output wire write_resp_vld, + input wire write_resp_rdy, + + input wire [31:0] write_req_data, + input wire write_req_vld, + output wire write_req_rdy, + + input wire [31:0] axi_st_read_tdata, + input wire [3:0] axi_st_read_tstr, + input wire [3:0] axi_st_read_tkeep, + input wire [0:0] axi_st_read_tlast, + input wire [3:0] axi_st_read_tid, + input wire [3:0] axi_st_read_tdest, + input wire axi_st_read_tvalid, + output wire axi_st_read_tready, + + output wire [3:0] axi_aw_awid, + output wire [15:0] axi_aw_awaddr, + output wire [2:0] axi_aw_awsize, + output wire [7:0] axi_aw_awlen, + output wire [1:0] axi_aw_awburst, + output wire axi_aw_awvalid, + input wire axi_aw_awready, + + output wire [31:0] axi_w_wdata, + output wire [3:0] axi_w_wstrb, + output wire [0:0] axi_w_wlast, + output wire axi_w_wvalid, + input wire axi_w_wready, + + input wire [2:0] axi_b_bresp, + input wire [3:0] axi_b_bid, + input wire axi_b_bvalid, + output wire axi_b_bready + +); + + wire [32:0] axi_writer__ch_axi_aw_data; + wire [36:0] axi_writer__ch_axi_w_data; + wire [ 6:0] axi_writer__ch_axi_b_data; + + wire [15:0] write_req_data_address; + wire [15:0] write_req_data_length; + + wire [48:0] axi_st_read_data; + + assign {write_req_data_address, write_req_data_length} = write_req_data; + + assign { axi_aw_awid, + axi_aw_awaddr, + axi_aw_awsize, + axi_aw_awlen, + axi_aw_awburst } = axi_writer__ch_axi_aw_data; + + assign {axi_w_wdata, axi_w_wstrb, axi_w_wlast} = axi_writer__ch_axi_w_data; + + assign axi_writer__ch_axi_b_data = {axi_b_bresp, axi_b_bid}; + + assign axi_st_read_data = { + axi_st_read_tdata, + axi_st_read_tstr, + axi_st_read_tkeep, + axi_st_read_tlast, + axi_st_read_tid, + axi_st_read_tdest + }; + + axi_writer axi_writer ( + .clk(clk), + .rst(rst), + + .axi_writer__ch_write_req_data(write_req_data), + .axi_writer__ch_write_req_rdy (write_req_rdy), + .axi_writer__ch_write_req_vld (write_req_vld), + + .axi_writer__ch_write_resp_rdy (write_resp_rdy), + .axi_writer__ch_write_resp_vld (write_resp_vld), + .axi_writer__ch_write_resp_data(write_resp_data), + + .axi_writer__ch_axi_aw_data(axi_writer__ch_axi_aw_data), + .axi_writer__ch_axi_aw_rdy (axi_aw_awready), + .axi_writer__ch_axi_aw_vld (axi_aw_awvalid), + + .axi_writer__ch_axi_w_data(axi_writer__ch_axi_w_data), + .axi_writer__ch_axi_w_rdy (axi_w_wready), + .axi_writer__ch_axi_w_vld (axi_w_wvalid), + + .axi_writer__ch_axi_b_data(axi_writer__ch_axi_b_data), + .axi_writer__ch_axi_b_rdy (axi_b_bready), + .axi_writer__ch_axi_b_vld (axi_b_bvalid), + + .axi_writer__ch_axi_st_read_data(axi_st_read_data), + .axi_writer__ch_axi_st_read_rdy (axi_st_read_tready), + .axi_writer__ch_axi_st_read_vld (axi_st_read_tvalid) + ); + + +endmodule : axi_writer_wrapper diff --git a/xls/modules/zstd/memory/mem_reader_cocotb_test.py b/xls/modules/zstd/memory/mem_reader_cocotb_test.py new file mode 100644 index 0000000000..845a321159 --- /dev/null +++ b/xls/modules/zstd/memory/mem_reader_cocotb_test.py @@ -0,0 +1,271 @@ +#!/usr/bin/env python +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import random +import sys +import warnings +from pathlib import Path + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, Event +from cocotb_bus.scoreboard import Scoreboard +from cocotbext.axi.axi_channels import AxiARBus, AxiRBus, AxiReadBus, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor +from cocotbext.axi.axi_ram import AxiRamRead +from cocotbext.axi.sparse_memory import SparseMemory + +from xls.modules.zstd.cocotb.channel import ( + XLSChannel, + XLSChannelDriver, + XLSChannelMonitor, +) +from xls.modules.zstd.cocotb.utils import reset, run_test +from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass + +# to disable warnings from hexdiff used by cocotb's Scoreboard +warnings.filterwarnings("ignore", category=DeprecationWarning) + +DSLX_DATA_W = 64 +DSLX_ADDR_W = 16 + +AXI_DATA_W = 128 +AXI_ADDR_W = 16 + +LAST_W = 1 +STATUS_W = 1 +ERROR_W = 1 +ID_W = 4 +DEST_W = 4 + +# AXI +AXI_AR_PREFIX = "axi_ar" +AXI_R_PREFIX = "axi_r" + +# MemReader +MEM_READER_REQ_CHANNEL = "req" +MEM_READER_RESP_CHANNEL = "resp" + +# Override default widths of AXI response signals +signal_widths = {"rresp": 3, "rlast": 1} +AxiRBus._signal_widths = signal_widths +AxiRTransaction._signal_widths = signal_widths +AxiRSource._signal_widths = signal_widths +AxiRSink._signal_widths = signal_widths +AxiRMonitor._signal_widths = signal_widths + +@xls_dataclass +class MemReaderReq(XLSStruct): + addr: DSLX_ADDR_W + length: DSLX_ADDR_W + + +@xls_dataclass +class MemReaderResp(XLSStruct): + status: STATUS_W + data: DSLX_DATA_W + length: DSLX_ADDR_W + last: LAST_W + + +@xls_dataclass +class AxiReaderReq(XLSStruct): + addr: AXI_ADDR_W + len: AXI_ADDR_W + + +@xls_dataclass +class AxiStream(XLSStruct): + data: AXI_DATA_W + str: AXI_DATA_W // 8 + keep: AXI_DATA_W // 8 = 0 + last: LAST_W = 0 + id: ID_W = 0 + dest: DEST_W = 0 + + +@xls_dataclass +class AxiReaderError(XLSStruct): + error: ERROR_W + + +@xls_dataclass +class AxiAr(XLSStruct): + id: ID_W + addr: AXI_ADDR_W + region: 4 + len: 8 + size: 3 + burst: 2 + cache: 4 + prot: 3 + qos: 4 + + +@xls_dataclass +class AxiR(XLSStruct): + id: ID_W + data: AXI_DATA_W + resp: 3 + last: 1 + + +def print_callback(name: str = "monitor"): + def _print_callback(transaction): + print(f" [{name}]: {transaction}") + + return _print_callback + + +def set_termination_event(monitor, event, transactions): + def terminate_cb(_): + if monitor.stats.received_transactions == transactions: + print("all transactions received") + event.set() + + monitor.add_callback(terminate_cb) + + +def generate_test_data(test_cases, xfer_base=0x0, seed=1234): + random.seed(seed) + mem_size = 2**AXI_ADDR_W + data_w_div8 = DSLX_DATA_W // 8 + + assert xfer_base < mem_size, "Base address outside the memory span" + + req = [] + resp = [] + mem_writes = {} + + for xfer_offset, xfer_length in test_cases: + xfer_addr = xfer_base + xfer_offset + xfer_max_addr = xfer_addr + xfer_length + + if xfer_length == 0: + req += [MemReaderReq(addr=xfer_addr, length=0)] + resp += [MemReaderResp(status=0, data=0, length=0, last=1)] + + assert xfer_max_addr < mem_size, "Max address outside the memory span" + req += [MemReaderReq(addr=xfer_addr, length=xfer_length)] + + rem = xfer_length % data_w_div8 + for addr in range(xfer_addr, xfer_max_addr - (data_w_div8 - 1), data_w_div8): + last = ((addr + data_w_div8) >= xfer_max_addr) & (rem == 0) + data = random.randint(0, 1 << (data_w_div8 * 8)) + mem_writes.update({addr: data}) + resp += [MemReaderResp(status=0, data=data, length=data_w_div8, last=last)] + + if rem > 0: + addr = xfer_max_addr - rem + mask = (1 << (rem * 8)) - 1 + data = random.randint(0, 1 << (data_w_div8 * 8)) + mem_writes.update({addr: data}) + resp += [MemReaderResp(status=0, data=data & mask, length=rem, last=1)] + + return (req, resp, mem_writes) + + +async def test_mem_reader(dut, req_input, resp_output, mem_contents={}): + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + mem_reader_resp_bus = XLSChannel( + dut, MEM_READER_RESP_CHANNEL, dut.clk, start_now=True + ) + mem_reader_req_driver = XLSChannelDriver(dut, MEM_READER_REQ_CHANNEL, dut.clk) + mem_reader_resp_monitor = XLSChannelMonitor( + dut, MEM_READER_RESP_CHANNEL, dut.clk, MemReaderResp, callback=print_callback() + ) + + terminate = Event() + set_termination_event(mem_reader_resp_monitor, terminate, len(resp_output)) + + scoreboard = Scoreboard(dut) + scoreboard.add_interface(mem_reader_resp_monitor, resp_output) + + ar_bus = AxiARBus.from_prefix(dut, AXI_AR_PREFIX) + r_bus = AxiRBus.from_prefix(dut, AXI_R_PREFIX) + axi_read_bus = AxiReadBus(ar=ar_bus, r=r_bus) + + mem_size = 2**AXI_ADDR_W + sparse_mem = SparseMemory(mem_size) + for addr, data in mem_contents.items(): + sparse_mem.write(addr, (data).to_bytes(8, "little")) + + memory = AxiRamRead(axi_read_bus, dut.clk, dut.rst, size=mem_size, mem=sparse_mem) + + await reset(dut.clk, dut.rst, cycles=10) + await mem_reader_req_driver.send(req_input) + await terminate.wait() + + +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def mem_reader_zero_length_req(dut): + req, resp, _ = generate_test_data( + xfer_base=0xFFF, test_cases=[(0x101, 0)] + ) + await test_mem_reader(dut, req, resp) + + +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def mem_reader_aligned_transfer_shorter_than_bus(dut): + req, resp, mem_contents = generate_test_data( + xfer_base=0xFFF, test_cases=[(0x101, 1)] + ) + await test_mem_reader(dut, req, resp, mem_contents) + + +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def mem_reader_aligned_transfer_shorter_than_bus1(dut): + req, resp, mem_contents = generate_test_data( + xfer_base=0xFFF, test_cases=[(0x2, 1)] + ) + await test_mem_reader(dut, req, resp, mem_contents) + + +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def mem_reader_aligned_transfer_shorter_than_bus2(dut): + req, resp, mem_contents = generate_test_data( + xfer_base=0xFFF, test_cases=[(0x2, 17)] + ) + await test_mem_reader(dut, req, resp, mem_contents) + + +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def mem_reader_aligned_transfer_shorter_than_bus3(dut): + req, resp, mem_contents = generate_test_data( + xfer_base=0xFFF, test_cases=[(0x0, 0x1000)] + ) + await test_mem_reader(dut, req, resp, mem_contents) + + +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def mem_reader_aligned_transfer_shorter_than_bus4(dut): + req, resp, mem_contents = generate_test_data( + xfer_base=0x1, test_cases=[(0x0, 0xFFF), (0x1000, 0x1)] + ) + await test_mem_reader(dut, req, resp, mem_contents) + + +if __name__ == "__main__": + sys.path.append(str(Path(__file__).parent)) + + toplevel = "mem_reader_wrapper" + verilog_sources = [ + "xls/modules/zstd/memory/mem_reader_adv.v", + "xls/modules/zstd/memory/mem_reader_wrapper.v", + ] + test_module = [Path(__file__).stem] + run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/memory/mem_reader_wrapper.v b/xls/modules/zstd/memory/mem_reader_wrapper.v new file mode 100644 index 0000000000..3601bcbb0e --- /dev/null +++ b/xls/modules/zstd/memory/mem_reader_wrapper.v @@ -0,0 +1,111 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +`default_nettype none + +module mem_reader_wrapper #( + parameter DSLX_DATA_W = 64, + parameter DSLX_ADDR_W = 16, + parameter AXI_DATA_W = 128, + parameter AXI_ADDR_W = 16, + parameter AXI_DEST_W = 8, + parameter AXI_ID_W = 8, + + parameter CTRL_W = (DSLX_ADDR_W), + parameter REQ_W = (2 * DSLX_ADDR_W), + parameter RESP_W = (1 + DSLX_DATA_W + DSLX_ADDR_W + 1), + parameter AXI_AR_W = (AXI_ID_W + AXI_ADDR_W + 28), + parameter AXI_R_W = (AXI_ID_W + AXI_DATA_W + 4) +) ( + input wire clk, + input wire rst, + + output wire req_rdy, + input wire req_vld, + input wire [REQ_W-1:0] req_data, + + output wire resp_vld, + input wire resp_rdy, + output wire [RESP_W-1:0] resp_data, + + output wire axi_ar_arvalid, + input wire axi_ar_arready, + output wire [ AXI_ID_W-1:0] axi_ar_arid, + output wire [AXI_ADDR_W-1:0] axi_ar_araddr, + output wire [ 3:0] axi_ar_arregion, + output wire [ 7:0] axi_ar_arlen, + output wire [ 2:0] axi_ar_arsize, + output wire [ 1:0] axi_ar_arburst, + output wire [ 3:0] axi_ar_arcache, + output wire [ 2:0] axi_ar_arprot, + output wire [ 3:0] axi_ar_arqos, + + input wire axi_r_rvalid, + output wire axi_r_rready, + input wire [ AXI_ID_W-1:0] axi_r_rid, + input wire [AXI_DATA_W-1:0] axi_r_rdata, + input wire [ 2:0] axi_r_rresp, + input wire axi_r_rlast +); + + wire [AXI_AR_W-1:0] axi_ar_data; + wire axi_ar_rdy; + wire axi_ar_vld; + + assign axi_ar_rdy = axi_ar_arready; + + assign axi_ar_arvalid = axi_ar_vld; + assign { + axi_ar_arid, + axi_ar_araddr, + axi_ar_arregion, + axi_ar_arlen, + axi_ar_arsize, + axi_ar_arburst, + axi_ar_arcache, + axi_ar_arprot, + axi_ar_arqos +} = axi_ar_data; + + wire [AXI_R_W-1:0] axi_r_data; + wire axi_r_vld; + wire axi_r_rdy; + + assign axi_r_data = {axi_r_rid, axi_r_rdata, axi_r_rresp, axi_r_rlast}; + assign axi_r_vld = axi_r_rvalid; + + assign axi_r_rready = axi_r_rdy; + + mem_reader_adv mem_reader_adv ( + .clk(clk), + .rst(rst), + + .mem_reader__req_r_data(req_data), + .mem_reader__req_r_rdy (req_rdy), + .mem_reader__req_r_vld (req_vld), + + .mem_reader__resp_s_data(resp_data), + .mem_reader__resp_s_rdy (resp_rdy), + .mem_reader__resp_s_vld (resp_vld), + + .mem_reader__axi_ar_s_data(axi_ar_data), + .mem_reader__axi_ar_s_rdy (axi_ar_rdy), + .mem_reader__axi_ar_s_vld (axi_ar_vld), + + .mem_reader__axi_r_r_data(axi_r_data), + .mem_reader__axi_r_r_vld (axi_r_vld), + .mem_reader__axi_r_r_rdy (axi_r_rdy) + ); + +endmodule diff --git a/xls/modules/zstd/memory/mem_writer_cocotb_test.py b/xls/modules/zstd/memory/mem_writer_cocotb_test.py new file mode 100644 index 0000000000..63d6e29cbf --- /dev/null +++ b/xls/modules/zstd/memory/mem_writer_cocotb_test.py @@ -0,0 +1,667 @@ +#!/usr/bin/env python +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import random +import logging +from enum import Enum +from pathlib import Path + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, Event +from cocotb.binary import BinaryValue +from cocotb_bus.scoreboard import Scoreboard + +from cocotbext.axi.axis import AxiStreamSource, AxiStreamBus, AxiStreamFrame +from cocotbext.axi.axi_channels import AxiAWBus, AxiWBus, AxiBBus, AxiWriteBus, AxiAWMonitor, AxiWMonitor, AxiBMonitor, AxiBTransaction, AxiBSource, AxiBSink +from cocotbext.axi.axi_ram import AxiRamWrite +from cocotbext.axi.sparse_memory import SparseMemory + +from xls.modules.zstd.cocotb.channel import ( + XLSChannel, + XLSChannelDriver, + XLSChannelMonitor, +) +from xls.modules.zstd.cocotb.utils import reset, run_test +from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass + +DATA_WIDTH = 32 +ADDR_WIDTH = 16 + +# Override default widths of AXI response signals +signal_widths = {"bresp": 3} +AxiBBus._signal_widths = signal_widths +AxiBTransaction._signal_widths = signal_widths +AxiBSource._signal_widths = signal_widths +AxiBSink._signal_widths = signal_widths +AxiBMonitor._signal_widths = signal_widths + +@xls_dataclass +class DataInStruct(XLSStruct): + data: DATA_WIDTH + length: ADDR_WIDTH + last: 1 + +@xls_dataclass +class WriteReqStruct(XLSStruct): + offset: ADDR_WIDTH + length: ADDR_WIDTH + +@xls_dataclass +class MemWriterRespStruct(XLSStruct): + status: 1 + +class MemWriterRespStatus(Enum): + OKAY = 0 + ERROR = 1 + +@xls_dataclass +class WriteRequestStruct(XLSStruct): + address: ADDR_WIDTH + length: ADDR_WIDTH + +def set_termination_event(monitor, event, transactions): + def terminate_cb(_): + if monitor.stats.received_transactions == transactions: + event.set() + monitor.add_callback(terminate_cb) + +async def test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt): + GENERIC_WRITE_REQ_CHANNEL = "req" + GENERIC_WRITE_RESP_CHANNEL = "resp" + GENERIC_DATA_IN_CHANNEL = "data_in" + AXI_AW_CHANNEL = "axi_aw" + AXI_W_CHANNEL = "axi_w" + AXI_B_CHANNEL = "axi_b" + + terminate = Event() + + dut.rst.setimmediatevalue(0) + + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + resp_bus = XLSChannel(dut, GENERIC_WRITE_RESP_CHANNEL, dut.clk, start_now=True) + + driver_write_req = XLSChannelDriver(dut, GENERIC_WRITE_REQ_CHANNEL, dut.clk) + driver_data_in = XLSChannelDriver(dut, GENERIC_DATA_IN_CHANNEL, dut.clk) + + bus_axi_aw = AxiAWBus.from_prefix(dut, AXI_AW_CHANNEL) + bus_axi_w = AxiWBus.from_prefix(dut, AXI_W_CHANNEL) + bus_axi_b = AxiBBus.from_prefix(dut, AXI_B_CHANNEL) + bus_axi_write = AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) + + monitor_write_req = XLSChannelMonitor(dut, GENERIC_WRITE_REQ_CHANNEL, dut.clk, WriteRequestStruct) + monitor_data_in = XLSChannelMonitor(dut, GENERIC_DATA_IN_CHANNEL, dut.clk, WriteRequestStruct) + monitor_write_resp = XLSChannelMonitor(dut, GENERIC_WRITE_RESP_CHANNEL, dut.clk, MemWriterRespStruct) + monitor_axi_aw = AxiAWMonitor(bus_axi_aw, dut.clk, dut.rst) + monitor_axi_w = AxiWMonitor(bus_axi_w, dut.clk, dut.rst) + monitor_axi_b = AxiBMonitor(bus_axi_b, dut.clk, dut.rst) + + set_termination_event(monitor_write_resp, terminate, resp_cnt) + + memory = AxiRamWrite(bus_axi_write, dut.clk, dut.rst, size=mem_size) + + log = logging.getLogger("cocotb.tb") + log.setLevel(logging.WARNING) + memory.log.setLevel(logging.WARNING) + + scoreboard = Scoreboard(dut) + scoreboard.add_interface(monitor_write_resp, write_resp_expect) + + await reset(dut.clk, dut.rst, cycles=10) + await cocotb.start(driver_write_req.send(write_req_input)) + await cocotb.start(driver_data_in.send(data_in_input)) + + await terminate.wait() + + for bundle in memory_verification: + memory_contents = bytearray(memory.read(bundle["base_address"], bundle["length"])) + expected_memory_contents = bytearray(expected_memory.read(bundle["base_address"], bundle["length"])) + assert memory_contents == expected_memory_contents, "{} bytes of memory contents at base address {}:\n{}\nvs\n{}\nHEXDUMP:\n{}\nvs\n{}".format(hex(bundle["length"]), hex(bundle["base_address"]), memory_contents, expected_memory_contents, memory.hexdump(bundle["base_address"], bundle["length"]), expected_memory.hexdump(bundle["base_address"], bundle["length"])) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_single_burst_1_transfer(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_1_transfer) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_single_burst_2_transfers(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_2_transfers) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_single_burst_almost_max_burst_transfer(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_almost_max_burst_transfer) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_single_burst_max_burst_transfer(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_max_burst_transfer) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_multiburst_2_full_bursts(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_2_full_bursts) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_multiburst_1_full_burst_and_single_transfer(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_1_full_burst_and_single_transfer) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_multiburst_crossing_4kb_boundary(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_crossing_4kb_boundary) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=2000, timeout_unit="ms") +async def ram_test_multiburst_crossing_4kb_boundary_with_2_full_bursts_and_1_transfer(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_crossing_4kb_boundary_with_2_full_bursts_and_1_transfer) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=5000, timeout_unit="ms") +async def ram_test_not_full_packets(dut): + mem_size = 2**ADDR_WIDTH + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_padded_test_data_arbitrary(mem_size, test_cases_not_full_packets) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +@cocotb.test(timeout_time=5000, timeout_unit="ms") +async def ram_test_random(dut): + mem_size = 2**ADDR_WIDTH + test_count = 200 + + (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_random(test_count, mem_size) + await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + +def generate_test_data_random(test_count, mem_size): + AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x + + write_req_input = [] + data_in_input = [] + write_resp_expect = [] + memory_verification = [] + memory = SparseMemory(mem_size) + + random.seed(1234) + + xfer_baseaddr = 0 + + for i in range(test_count): + # Generate offset from the absolute address + max_xfer_offset = mem_size - xfer_baseaddr + xfer_offset = random.randrange(0, max_xfer_offset) + xfer_addr = xfer_baseaddr + xfer_offset + # Make sure we don't write beyond available memory + memory_size_max_xfer_len = mem_size - xfer_addr + arbitrary_max_xfer_len = 0x5000 # 20kB + xfer_max_len = min(arbitrary_max_xfer_len, memory_size_max_xfer_len) + xfer_len = random.randrange(1, xfer_max_len) + + write_req = WriteReqStruct( + offset = xfer_offset, + length = xfer_len, + ) + write_req_input.append(write_req) + + data_to_write = random.randbytes(xfer_len) + rem = xfer_len % 4 + for j in list(range(0, xfer_len-3, 4)): + last = ((j + 4) >= xfer_len) & (rem == 0) + data_in = DataInStruct( + data = int.from_bytes(data_to_write[j:j+4], byteorder='little'), + length = 4, + last = last + ) + data_in_input.append(data_in) + if (rem > 0): + data_in = DataInStruct( + data = int.from_bytes(data_to_write[-rem:], byteorder='little'), + length = rem, + last = True + ) + data_in_input.append(data_in) + + + transfer_req = WriteRequestStruct( + address = xfer_addr, + length = xfer_len, + ) + write_expected_memory(transfer_req, data_to_write, memory) + + memory_bundle = { + "base_address": transfer_req.address, + "length": transfer_req.length, + } + memory_verification.append(memory_bundle) + + write_resp_expect = [MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value)] * test_count + + return (write_req_input, data_in_input, write_resp_expect, memory_verification, memory, test_count) + +def bytes_to_4k_boundary(addr): + AXI_4K_BOUNDARY = 0x1000 + return AXI_4K_BOUNDARY - (addr % AXI_4K_BOUNDARY) + +def write_expected_memory(transfer_req, data_to_write, memory): + """ + Write test data to reference memory keeping the AXI 4kb boundary + by spliting the write requests into smaller ones. + """ + prev_id = 0 + address = transfer_req.address + length = transfer_req.length + + BYTES_IN_TRANSFER = 4 + MAX_AXI_BURST_BYTES = 256 * BYTES_IN_TRANSFER + + while (length > 0): + bytes_to_4k = bytes_to_4k_boundary(address) + new_len = min(length, min(bytes_to_4k, MAX_AXI_BURST_BYTES)) + new_data = data_to_write[prev_id:prev_id+new_len] + memory.write(address, new_data) + address = address + new_len + length = length - new_len + prev_id = prev_id + new_len + +def generate_test_data_arbitrary(mem_size, test_cases): + AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x + test_count = len(test_cases) + + random.seed(1234) + + write_req_input = [] + data_in_input = [] + write_resp_expect = [] + memory_verification = [] + memory = SparseMemory(mem_size) + + xfer_baseaddr = 0x0 + assert xfer_baseaddr < mem_size + + max_xfer_offset = mem_size - xfer_baseaddr + + for xfer_offset, xfer_len in test_cases: + assert xfer_offset <= max_xfer_offset + xfer_addr = xfer_baseaddr + xfer_offset + # Make sure we don't write beyond available memory + memory_size_max_xfer_len = mem_size - xfer_addr + arbitrary_max_xfer_len = 0x5000 # 20kB + xfer_max_len = min(arbitrary_max_xfer_len, memory_size_max_xfer_len) + assert xfer_len <= xfer_max_len + + write_req = WriteReqStruct( + offset = xfer_offset, + length = xfer_len, + ) + write_req_input.append(write_req) + + data_to_write = random.randbytes(xfer_len) + rem = xfer_len % 4 + for j in list(range(0, xfer_len-3, 4)): + last = ((j + 4) >= xfer_len) & (rem == 0) + data_in = DataInStruct( + data = int.from_bytes(data_to_write[j:j+4], byteorder='little'), + length = 4, + last = last + ) + data_in_input.append(data_in) + if (rem > 0): + data_in = DataInStruct( + data = int.from_bytes(data_to_write[-rem:], byteorder='little'), + length = rem, + last = True + ) + data_in_input.append(data_in) + + + transfer_req = WriteRequestStruct( + address = xfer_addr, + length = xfer_len, + ) + write_expected_memory(transfer_req, data_to_write, memory) + + memory_bundle = { + "base_address": transfer_req.address, + "length": transfer_req.length, + } + memory_verification.append(memory_bundle) + + write_resp_expect = [MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value)] * test_count + + return (write_req_input, data_in_input, write_resp_expect, memory_verification, memory, test_count) + +def generate_padded_test_data_arbitrary(mem_size, test_cases): + AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x + test_count = len(test_cases) + + random.seed(1234) + + write_req_input = [] + data_in_input = [] + write_resp_expect = [] + memory_verification = [] + memory = SparseMemory(mem_size) + + xfer_baseaddr = 0x0 + assert xfer_baseaddr < mem_size + + max_xfer_offset = mem_size - xfer_baseaddr + + for xfer_offset, xfer_len in test_cases: + assert xfer_offset <= max_xfer_offset + xfer_addr = xfer_baseaddr + xfer_offset + # Make sure we don't write beyond available memory + memory_size_max_xfer_len = mem_size - xfer_addr + arbitrary_max_xfer_len = 0x5000 # 20kB + xfer_max_len = min(arbitrary_max_xfer_len, memory_size_max_xfer_len) + assert xfer_len <= xfer_max_len + + write_req = WriteReqStruct( + offset = xfer_offset, + length = xfer_len, + ) + write_req_input.append(write_req) + + data_to_write = random.randbytes(xfer_len) + bytes_to_packetize = xfer_len + packetized_bytes = 0 + while(bytes_to_packetize): + packet_len = random.randint(1, 4) + + if (bytes_to_packetize < packet_len): + packet_len = bytes_to_packetize + + last = packet_len == bytes_to_packetize + + data_in = DataInStruct( + data = int.from_bytes(data_to_write[packetized_bytes:packetized_bytes+packet_len], byteorder='little'), + length = packet_len, + last = last + ) + data_in_input.append(data_in) + + bytes_to_packetize -= packet_len + packetized_bytes += packet_len + assert xfer_len == packetized_bytes + + + transfer_req = WriteRequestStruct( + address = xfer_addr, + length = xfer_len, + ) + write_expected_memory(transfer_req, data_to_write, memory) + + memory_bundle = { + "base_address": transfer_req.address, + "length": transfer_req.length, + } + memory_verification.append(memory_bundle) + + write_resp_expect = [MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value)] * test_count + + return (write_req_input, data_in_input, write_resp_expect, memory_verification, memory, test_count) + +if __name__ == "__main__": + toplevel = "mem_writer_wrapper" + verilog_sources = [ + "xls/modules/zstd/memory/mem_writer.v", + "xls/modules/zstd/memory/mem_writer_wrapper.v", + ] + test_module=[Path(__file__).stem] + run_test(toplevel, test_module, verilog_sources) + +test_cases_single_burst_1_transfer = [ + # Aligned Address; Aligned Length + (0x0, 0x4), + # Aligned Address; Unaligned Length + (0x10, 0x1), + (0x24, 0x2), + (0x38, 0x3), + # Unaligned Address; Aligned Length + (0x41, 0x4), + (0x52, 0x4), + (0x63, 0x4), + # Unaligned Address; Unaligned Length + (0x71, 0x1), + (0x81, 0x2), + (0x91, 0x3), + (0xa2, 0x1), + (0xb2, 0x2), + (0xc2, 0x3), + (0xd3, 0x1), + (0xe3, 0x2), + (0xf3, 0x3) +] + +test_cases_single_burst_2_transfers = [ + # Aligned Address; Aligned Length + (0x100, 0x8), + # Aligned Address; Unaligned Length + (0x110, 0x5), + (0x120, 0x6), + (0x130, 0x7), + # Unaligned Address; Aligned Length + (0x141, 0x8), + (0x152, 0x8), + (0x163, 0x8), + # Unaligned Address; Unaligned Length + (0x171, 0x5), + (0x182, 0x5), + (0x193, 0x5), + (0x1A1, 0x6), + (0x1B2, 0x6), + (0x1C3, 0x6), + (0x1D1, 0x7), + (0x1E2, 0x7), + (0x1F3, 0x7) +] + +test_cases_single_burst_almost_max_burst_transfer = [ + # Aligned Address; Aligned Length + (0x200, 0x3FC), + # Aligned Address; Unaligned Length + (0x600, 0x3F9), + (0xA00, 0x3FA), + (0x1000, 0x3FB), + # Unaligned Address; Aligned Length + (0x1401, 0x3FC), + (0x1802, 0x3FC), + (0x2003, 0x3FC), + # Unaligned Address; Unaligned Length + (0x2401, 0x3F9), + (0x2802, 0x3F9), + (0x2C03, 0x3F9), + (0x3001, 0x3FA), + (0x3402, 0x3FA), + (0x3803, 0x3FA), + (0x3C01, 0x3FB), + (0x4002, 0x3FB), + (0x4403, 0x3FB) +] + +test_cases_single_burst_max_burst_transfer = [ + # Aligned Address; Aligned Length + (0x4800, 0x400), + # Aligned Address; Unaligned Length + (0x4C00, 0x3FD), + (0x5000, 0x3FE), + (0x5400, 0x3FF), + # Unaligned Address; Aligned Length + (0x5801, 0x400), + (0x6002, 0x400), + (0x6803, 0x400), + # Unaligned Address; Unaligned Length + (0x7001, 0x3FD), + (0x7802, 0x3FD), + (0x8003, 0x3FD), + (0x8801, 0x3FE), + (0x9002, 0x3FE), + (0x9803, 0x3FE), + (0xA001, 0x3FF), + (0xA802, 0x3FF), + (0xB003, 0x3FF) +] + +test_cases_multiburst_2_full_bursts = [ + # Aligned Address; Aligned Length + (0x0400, 0x800), + # Aligned Address; Unaligned Length + (0x1000, 0x7FD), + (0x1800, 0x7FE), + (0x2000, 0x7FF), + # Unaligned Address; Aligned Length + (0x2801, 0x800), + (0x3002, 0x800), + (0x3803, 0x800), + # Unaligned Address; Unaligned Length + (0x4001, 0x7FD), + (0x5002, 0x7FD), + (0x6003, 0x7FD), + (0x7001, 0x7FE), + (0x8002, 0x7FE), + (0x9003, 0x7FE), + (0xA001, 0x7FF), + (0xB002, 0x7FF), + (0xF003, 0x7FF) +] + +test_cases_multiburst_1_full_burst_and_single_transfer = [ + # Aligned Address; Aligned Length; Multi-Burst + (0x0000, 0x404), + # Aligned Address; Unaligned Length; Multi-Burst + (0x0800, 0x401), + (0x1000, 0x402), + (0x1800, 0x403), + # Unaligned Address; Aligned Length; Multi-Burst + (0x2000, 0x404), + (0x2800, 0x404), + (0x3000, 0x404), + # Unaligned Address; Unaligned Length; Multi-Burst + (0x3801, 0x401), + (0x5002, 0x401), + (0x5803, 0x401), + (0x6001, 0x402), + (0x6802, 0x402), + (0x7003, 0x402), + (0x7801, 0x403), + (0x8002, 0x403), + (0x8803, 0x403) +] + +test_cases_multiburst_crossing_4kb_boundary = [ + # Aligned Address; Aligned Length + (0x0FFC, 0x8), + # Aligned Address; Unaligned Length + (0x1FFC, 0x5), + (0x2FFC, 0x6), + (0x3FFC, 0x7), + # Unaligned Address; Aligned Length + (0x4FFD, 0x8), + (0x5FFE, 0x8), + (0x6FFF, 0x8), + # Unaligned Address; Unaligned Length + (0x7FFD, 0x5), + (0x8FFD, 0x6), + (0x9FFD, 0x7), + (0xAFFE, 0x5), + (0xBFFE, 0x6), + (0xCFFE, 0x7), + (0xDFFF, 0x5), + (0xEFFF, 0x6), + # End of address space - wrap around + (0x0FFF, 0x7), +] + +test_cases_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts = [ + # Aligned Address; Aligned Length; Multi-Burst; crossing 4kB boundary with perfectly aligned full bursts + (0x0C00, 0x800), + # Unaligned Address; Unaligned Length; Multi-Burst; crossing 4kB boundary with perfectly aligned full bursts + (0x1C01, 0x7FF), + (0x2C02, 0x7FE), + (0x3C03, 0x7FD), +] + +test_cases_multiburst_crossing_4kb_boundary_with_2_full_bursts_and_1_transfer = [ + # Aligned Address; Aligned Length + (0x0C04, 0x800), + # Aligned Address; Unaligned Length + (0x1C04, 0x801), + (0x2C04, 0x802), + (0x3C04, 0x803), + # Unaligned Address; Aligned Length + (0x4C01, 0x800), + (0x5C02, 0x800), + (0x6C03, 0x800), + # Unaligned Address; Unaligned Length + (0x7C01, 0x801), + (0x8C02, 0x802), + (0x9C03, 0x803), + (0xAC01, 0x802), + (0xBC02, 0x802), + (0xCC03, 0x802), + (0xDC01, 0x803), + (0xEC02, 0x803), + # End of address space - wrap around + (0x0C03, 0x803), +] + +test_cases_not_full_packets = [ + # Aligned Address; Aligned Length + (0x0000, 0x20), + # Aligned Address; Unaligned Length + (0x100, 0x21), + (0x200, 0x22), + (0x300, 0x23), + # Unaligned Address; Aligned Length + (0x401, 0x20), + (0x502, 0x20), + (0x603, 0x20), + # Unaligned Address; Unaligned Length + (0x701, 0x21), + (0x802, 0x22), + (0x903, 0x23), + (0xA01, 0x22), + (0xB02, 0x22), + (0xC03, 0x22), + (0xD01, 0x23), + (0xE02, 0x23), + (0xF03, 0x23), +] diff --git a/xls/modules/zstd/memory/mem_writer_wrapper.v b/xls/modules/zstd/memory/mem_writer_wrapper.v new file mode 100644 index 0000000000..c7513af58a --- /dev/null +++ b/xls/modules/zstd/memory/mem_writer_wrapper.v @@ -0,0 +1,193 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +`default_nettype none + +module mem_writer_wrapper ( + input wire clk, + input wire rst, + + input wire [31:0] req_data, + input wire req_vld, + output wire req_rdy, + + input wire [48:0] data_in_data, + input wire data_in_vld, + output wire data_in_rdy, + + output wire resp_data, + output wire resp_vld, + input wire resp_rdy, + + output wire [3:0] axi_aw_awid, + output wire [15:0] axi_aw_awaddr, + output wire [2:0] axi_aw_awsize, + output wire [7:0] axi_aw_awlen, + output wire [1:0] axi_aw_awburst, + output wire axi_aw_awvalid, + input wire axi_aw_awready, + + output wire [31:0] axi_w_wdata, + output wire [3:0] axi_w_wstrb, + output wire [0:0] axi_w_wlast, + output wire axi_w_wvalid, + input wire axi_w_wready, + + input wire [2:0] axi_b_bresp, + input wire [3:0] axi_b_bid, + input wire axi_b_bvalid, + output wire axi_b_bready +); + + wire [15:0] req_f_addr; + wire [15:0] req_f_length; + + wire [31:0] data_in_f_data; + wire [15:0] data_in_f_length; + wire [0:0] data_in_f_last; + + wire [36:0] axi_w_data; + wire axi_w_vld; + wire axi_w_rdy; + + wire [32:0] axi_aw_data; + wire axi_aw_vld; + wire axi_aw_rdy; + + wire [6:0] axi_b_data; + wire axi_b_rdy; + wire axi_b_vld; + + assign {req_f_addr, req_f_length} = req_data; + + assign {data_in_f_data, data_in_f_length, data_in_f_last} = data_in_data; + + assign {axi_aw_awid, axi_aw_awaddr, axi_aw_awsize, axi_aw_awlen, axi_aw_awburst} = axi_aw_data; + assign axi_aw_awvalid = axi_aw_vld; + assign axi_aw_rdy = axi_aw_awready; + + assign {axi_w_wdata, axi_w_wstrb, axi_w_wlast} = axi_w_data; + assign axi_w_wvalid = axi_w_vld; + assign axi_w_rdy = axi_w_wready; + + assign axi_b_data = {axi_b_bresp, axi_b_bid}; + assign axi_b_vld = axi_b_bvalid; + assign axi_b_bready = axi_b_rdy; + + wire [15:0] axi_writer_write_req_address; + wire [15:0] axi_writer_write_req_length; + wire [ 0:0] axi_writer_write_req_valid; + wire [ 0:0] axi_writer_write_req_ready; + + wire [15:0] padding_write_req_address; + wire [15:0] padding_write_req_length; + wire [ 0:0] padding_write_req_valid; + wire [ 0:0] padding_write_req_ready; + + wire [31:0] axi_stream_raw_tdata; + wire [ 3:0] axi_stream_raw_tstr; + wire [ 3:0] axi_stream_raw_tkeep; + wire [ 0:0] axi_stream_raw_tlast; + wire [ 3:0] axi_stream_raw_tid; + wire [ 3:0] axi_stream_raw_tdest; + wire [ 0:0] axi_stream_raw_tvalid; + wire [ 0:0] axi_stream_raw_tready; + + wire [31:0] axi_stream_clean_tdata; + wire [ 3:0] axi_stream_clean_tstr; + wire [ 3:0] axi_stream_clean_tkeep; + wire [ 0:0] axi_stream_clean_tlast; + wire [ 3:0] axi_stream_clean_tid; + wire [ 3:0] axi_stream_clean_tdest; + wire [ 0:0] axi_stream_clean_tvalid; + wire [ 0:0] axi_stream_clean_tready; + + wire [31:0] axi_stream_padded_tdata; + wire [ 3:0] axi_stream_padded_tstr; + wire [ 3:0] axi_stream_padded_tkeep; + wire [ 0:0] axi_stream_padded_tlast; + wire [ 3:0] axi_stream_padded_tid; + wire [ 3:0] axi_stream_padded_tdest; + wire [ 0:0] axi_stream_padded_tvalid; + wire [ 0:0] axi_stream_padded_tready; + + assign {axi_writer_write_req_address, axi_writer_write_req_length} = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_writer_req_data; + assign axi_writer_write_req_valid = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_writer_req_vld; + assign axi_writer_write_req_ready = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_writer_req_rdy; + + assign {padding_write_req_address, padding_write_req_length} = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__padding_req_data; + assign padding_write_req_valid = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__padding_req_vld; + assign padding_write_req_ready = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__padding_req_rdy; + + assign { axi_stream_raw_tdata, + axi_stream_raw_tstr, + axi_stream_raw_tkeep, + axi_stream_raw_tid, + axi_stream_raw_tdest, + axi_stream_raw_tlast} = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_st_raw_data; + assign axi_stream_raw_tvalid = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_st_raw_vld; + assign axi_stream_raw_tready = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_st_raw_rdy; + + assign { axi_stream_clean_tdata, + axi_stream_clean_tstr, + axi_stream_clean_tkeep, + axi_stream_clean_tid, + axi_stream_clean_tdest, + axi_stream_clean_tlast} = mem_writer.__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1.mem_writer__axi_st_clean_data; + assign axi_stream_clean_tvalid = mem_writer.__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1.mem_writer__axi_st_clean_vld; + assign axi_stream_clean_tready = mem_writer.__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1.mem_writer__axi_st_clean_rdy; + + assign { axi_stream_padded_tdata, + axi_stream_padded_tstr, + axi_stream_padded_tkeep, + axi_stream_padded_tid, + axi_stream_padded_tdest, + axi_stream_padded_tlast} = mem_writer.__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4.mem_writer__axi_st_padded_data; + assign axi_stream_padded_tvalid = mem_writer.__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4.mem_writer__axi_st_padded_vld; + assign axi_stream_padded_tready = mem_writer.__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4.mem_writer__axi_st_padded_rdy; + + mem_writer mem_writer ( + .clk(clk), + .rst(rst), + + // MemWriter Write Request + .mem_writer__req_in_r_data(req_data), + .mem_writer__req_in_r_vld (req_vld), + .mem_writer__req_in_r_rdy (req_rdy), + + // Data to write + .mem_writer__data_in_r_data(data_in_data), + .mem_writer__data_in_r_vld (data_in_vld), + .mem_writer__data_in_r_rdy (data_in_rdy), + + // Response channel + .mem_writer__resp_s_data(resp_data), + .mem_writer__resp_s_rdy (resp_rdy), + .mem_writer__resp_s_vld (resp_vld), + + // Memory AXI + .mem_writer__axi_w_s_data(axi_w_data), + .mem_writer__axi_w_s_vld (axi_w_vld), + .mem_writer__axi_w_s_rdy (axi_w_rdy), + + .mem_writer__axi_aw_s_data(axi_aw_data), + .mem_writer__axi_aw_s_vld (axi_aw_vld), + .mem_writer__axi_aw_s_rdy (axi_aw_rdy), + + .mem_writer__axi_b_r_data(axi_b_data), + .mem_writer__axi_b_r_vld (axi_b_vld), + .mem_writer__axi_b_r_rdy (axi_b_rdy) + ); + +endmodule : mem_writer_wrapper From 5b3f1d0f77fe33f431ff85366b2e22232fe709f1 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 31 Dec 2024 15:55:34 +0100 Subject: [PATCH 004/159] xls/modules/zstd: expose fifo verilog module Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 2 ++ xls/modules/zstd/memory/BUILD | 2 ++ xls/modules/zstd/memory/mem_reader_cocotb_test.py | 1 + xls/modules/zstd/memory/mem_writer_cocotb_test.py | 1 + 4 files changed, 6 insertions(+) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 413092f58e..5df7b6624a 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -59,6 +59,8 @@ xls_dslx_test( library = ":math_dslx", ) +exports_files(["xls_fifo_wrapper.sv"]) + xls_dslx_library( name = "buffer_dslx", srcs = [ diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 3fd85d42d7..086e7186c6 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -554,6 +554,7 @@ py_test( data = [ ":mem_reader_adv.v", ":mem_reader_wrapper.v", + "//xls/modules/zstd:xls_fifo_wrapper.sv", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], @@ -816,6 +817,7 @@ py_test( data = [ ":mem_writer.v", ":mem_writer_wrapper.v", + "//xls/modules/zstd:xls_fifo_wrapper.sv", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], diff --git a/xls/modules/zstd/memory/mem_reader_cocotb_test.py b/xls/modules/zstd/memory/mem_reader_cocotb_test.py index 845a321159..36c90cea1d 100644 --- a/xls/modules/zstd/memory/mem_reader_cocotb_test.py +++ b/xls/modules/zstd/memory/mem_reader_cocotb_test.py @@ -264,6 +264,7 @@ async def mem_reader_aligned_transfer_shorter_than_bus4(dut): toplevel = "mem_reader_wrapper" verilog_sources = [ + "xls/modules/zstd/xls_fifo_wrapper.sv", "xls/modules/zstd/memory/mem_reader_adv.v", "xls/modules/zstd/memory/mem_reader_wrapper.v", ] diff --git a/xls/modules/zstd/memory/mem_writer_cocotb_test.py b/xls/modules/zstd/memory/mem_writer_cocotb_test.py index 63d6e29cbf..b617c41fa2 100644 --- a/xls/modules/zstd/memory/mem_writer_cocotb_test.py +++ b/xls/modules/zstd/memory/mem_writer_cocotb_test.py @@ -442,6 +442,7 @@ def generate_padded_test_data_arbitrary(mem_size, test_cases): if __name__ == "__main__": toplevel = "mem_writer_wrapper" verilog_sources = [ + "xls/modules/zstd/xls_fifo_wrapper.sv", "xls/modules/zstd/memory/mem_writer.v", "xls/modules/zstd/memory/mem_writer_wrapper.v", ] From 82b442f1c117704c95ebb3229d9cfecdf5bd296c Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 9 Oct 2024 15:23:22 +0200 Subject: [PATCH 005/159] modules/zstd/cocotb: Add ZSTD frame generator library This reverts commit 04ad379225b706ddf492d440c673e77348d7a409. --- xls/modules/zstd/cocotb/BUILD | 9 +++++ xls/modules/zstd/cocotb/data_generator.py | 46 +++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 xls/modules/zstd/cocotb/data_generator.py diff --git a/xls/modules/zstd/cocotb/BUILD b/xls/modules/zstd/cocotb/BUILD index 23b1a843c3..cdb788d732 100644 --- a/xls/modules/zstd/cocotb/BUILD +++ b/xls/modules/zstd/cocotb/BUILD @@ -64,3 +64,12 @@ py_library( requirement("cocotb"), ], ) + +py_library( + name = "data_generator", + srcs = ["data_generator.py"], + deps = [ + "//xls/common:runfiles", + "@zstd//:decodecorpus", + ], +) diff --git a/xls/modules/zstd/cocotb/data_generator.py b/xls/modules/zstd/cocotb/data_generator.py new file mode 100644 index 0000000000..105a9a7ec7 --- /dev/null +++ b/xls/modules/zstd/cocotb/data_generator.py @@ -0,0 +1,46 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from enum import Enum + +from xls.common import runfiles +import subprocess + +class BlockType(Enum): + RAW = 0 + RLE = 1 + COMPRESSED = 2 + RANDOM = 3 + +def CallDecodecorpus(args): + decodecorpus = Path(runfiles.get_path("decodecorpus", repository = "zstd")) + cmd = args + cmd.insert(0, str(decodecorpus)) + cmd_concat = " ".join(cmd) + subprocess.run(cmd_concat, shell=True, check=True) + +def GenerateFrame(seed, btype, output_path): + args = [] + args.append("-s" + str(seed)) + if (btype != BlockType.RANDOM): + args.append("--block-type=" + str(btype.value)) + args.append("--content-size") + # Test payloads up to 16KB + args.append("--max-content-size-log=14") + args.append("-p" + output_path); + args.append("-vvvvvvv"); + + CallDecodecorpus(args) + From 3eec9bec67c5b1912b792b92eff0a26eb2bfbbc7 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Thu, 3 Oct 2024 14:15:33 +0200 Subject: [PATCH 006/159] dependency_support: Add zstandard python library Signed-off-by: Pawel Czarnecki --- dependency_support/pip_requirements.in | 1 + dependency_support/pip_requirements_lock.txt | 99 ++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/dependency_support/pip_requirements.in b/dependency_support/pip_requirements.in index 6046107abe..989620844a 100644 --- a/dependency_support/pip_requirements.in +++ b/dependency_support/pip_requirements.in @@ -19,6 +19,7 @@ pytest==8.2.2 cocotb==1.9.0 cocotbext-axi==0.1.24 cocotb_bus==0.2.1 +zstandard==0.23.0 # Note: numpy and scipy version availability seems to differ between Ubuntu # versions that we want to support (e.g. 18.04 vs 20.04), so we accept a diff --git a/dependency_support/pip_requirements_lock.txt b/dependency_support/pip_requirements_lock.txt index 703976edf1..3a84ca1a7c 100644 --- a/dependency_support/pip_requirements_lock.txt +++ b/dependency_support/pip_requirements_lock.txt @@ -695,3 +695,102 @@ z3-solver==4.14.0.0 \ --hash=sha256:c10f899c6a876e3a50e9b2c4927604c2c3da3cca672b8ed3b7db1bc97259e47f \ --hash=sha256:e6ae32bc1cf4d25b96f790755d790a23118e8a09b9b6060e32238fe6ff43606d # via -r dependency_support/pip_requirements.in +zstandard==0.23.0 \ + --hash=sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473 \ + --hash=sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916 \ + --hash=sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15 \ + --hash=sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072 \ + --hash=sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4 \ + --hash=sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e \ + --hash=sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26 \ + --hash=sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8 \ + --hash=sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5 \ + --hash=sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd \ + --hash=sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c \ + --hash=sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db \ + --hash=sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5 \ + --hash=sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc \ + --hash=sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152 \ + --hash=sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269 \ + --hash=sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045 \ + --hash=sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e \ + --hash=sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d \ + --hash=sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a \ + --hash=sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb \ + --hash=sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740 \ + --hash=sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105 \ + --hash=sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274 \ + --hash=sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2 \ + --hash=sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58 \ + --hash=sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b \ + --hash=sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4 \ + --hash=sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db \ + --hash=sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e \ + --hash=sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9 \ + --hash=sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0 \ + --hash=sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813 \ + --hash=sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e \ + --hash=sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512 \ + --hash=sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0 \ + --hash=sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b \ + --hash=sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48 \ + --hash=sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a \ + --hash=sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772 \ + --hash=sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed \ + --hash=sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373 \ + --hash=sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea \ + --hash=sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd \ + --hash=sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f \ + --hash=sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc \ + --hash=sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23 \ + --hash=sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2 \ + --hash=sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db \ + --hash=sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70 \ + --hash=sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259 \ + --hash=sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9 \ + --hash=sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700 \ + --hash=sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003 \ + --hash=sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba \ + --hash=sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a \ + --hash=sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c \ + --hash=sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90 \ + --hash=sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690 \ + --hash=sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f \ + --hash=sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840 \ + --hash=sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d \ + --hash=sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9 \ + --hash=sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35 \ + --hash=sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd \ + --hash=sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a \ + --hash=sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea \ + --hash=sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1 \ + --hash=sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573 \ + --hash=sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09 \ + --hash=sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094 \ + --hash=sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78 \ + --hash=sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9 \ + --hash=sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5 \ + --hash=sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9 \ + --hash=sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391 \ + --hash=sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847 \ + --hash=sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2 \ + --hash=sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c \ + --hash=sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2 \ + --hash=sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057 \ + --hash=sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20 \ + --hash=sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d \ + --hash=sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4 \ + --hash=sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54 \ + --hash=sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171 \ + --hash=sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e \ + --hash=sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160 \ + --hash=sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b \ + --hash=sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58 \ + --hash=sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8 \ + --hash=sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33 \ + --hash=sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a \ + --hash=sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880 \ + --hash=sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca \ + --hash=sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b \ + --hash=sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69 + # via -r dependency_support/pip_requirements.in From 595df0f395848c4567c7497063bb69a7da9f4c4f Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 8 Oct 2024 17:31:43 +0200 Subject: [PATCH 007/159] modules/zstd: Add verilog simulation of the ZstdDecoder Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 209 ++++- xls/modules/zstd/arbiter.v | 159 ++++ xls/modules/zstd/axi_interconnect.v | 988 ++++++++++++++++++++ xls/modules/zstd/axi_interconnect_wrapper.v | 424 +++++++++ xls/modules/zstd/priority_encoder.v | 92 ++ xls/modules/zstd/zstd_dec_cocotb_test.py | 418 +++++++++ 6 files changed, 2288 insertions(+), 2 deletions(-) create mode 100644 xls/modules/zstd/arbiter.v create mode 100644 xls/modules/zstd/axi_interconnect.v create mode 100644 xls/modules/zstd/axi_interconnect_wrapper.v create mode 100644 xls/modules/zstd/priority_encoder.v create mode 100644 xls/modules/zstd/zstd_dec_cocotb_test.py diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 5df7b6624a..f44dc05365 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -18,6 +18,7 @@ load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") load("@rules_hdl//verilog:providers.bzl", "verilog_library") +load("@xls_pip_deps//:requirements.bzl", "requirement") load( "//xls/build_rules:xls_build_defs.bzl", "xls_benchmark_ir", @@ -59,8 +60,6 @@ xls_dslx_test( library = ":math_dslx", ) -exports_files(["xls_fifo_wrapper.sv"]) - xls_dslx_library( name = "buffer_dslx", srcs = [ @@ -1463,6 +1462,101 @@ xls_dslx_test( deps = zstd_dec_deps, ) +ZSTD_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { + "module_name": "ZstdDecoder", + "clock_period_ps": "0", + "pipeline_stages": "16", + "flop_inputs_kind": "skid", + "flop_outputs_kind": "skid", + "worst_case_throughput": "6", + "materialize_internal_fifos": "false", # TODO: remove once this option works with loopback channels + "ram_configurations": ",".join([ + ",".join([ + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "history_buffer_ram{}".format(num), + rd_req = "zstd_dec__ram_rd_req_{}_s".format(num), + rd_resp = "zstd_dec__ram_rd_resp_{}_r".format(num), + wr_req = "zstd_dec__ram_wr_req_{}_s".format(num), + wr_resp = "zstd_dec__ram_wr_resp_{}_r".format(num), + ) + for num in range(8) + ]), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "dpd_ram", + rd_req = "zstd_dec__dpd_rd_req_s", + rd_resp = "zstd_dec__dpd_rd_resp_r", + wr_req = "zstd_dec__dpd_wr_req_s", + wr_resp = "zstd_dec__dpd_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "fse_tmp_ram", + rd_req = "zstd_dec__tmp_rd_req_s", + rd_resp = "zstd_dec__tmp_rd_resp_r", + wr_req = "zstd_dec__tmp_wr_req_s", + wr_resp = "zstd_dec__tmp_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "fse_tmp2_ram", + rd_req = "zstd_dec__tmp2_rd_req_s", + rd_resp = "zstd_dec__tmp2_rd_resp_r", + wr_req = "zstd_dec__tmp2_wr_req_s", + wr_resp = "zstd_dec__tmp2_wr_resp_r", + ), + ",".join([ + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "fse_lookup_ram{}".format(num), + rd_req = "zstd_dec__fse_rd_req_s__{}".format(num), + rd_resp = "zstd_dec__fse_rd_resp_r__{}".format(num), + wr_req = "zstd_dec__fse_wr_req_s__{}".format(num), + wr_resp = "zstd_dec__fse_wr_resp_r__{}".format(num), + ) + for num in range(6) + ]), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "huffman_literals_prescan_ram", + rd_req = "zstd_dec__huffman_lit_prescan_mem_rd_req_s", + rd_resp = "zstd_dec__huffman_lit_prescan_mem_rd_resp_r", + wr_req = "zstd_dec__huffman_lit_prescan_mem_wr_req_s", + wr_resp = "zstd_dec__huffman_lit_prescan_mem_wr_resp_r", + ), + # FIXME: Enable once HuffmanLiteralsDecoder has states independent of data read from RAM + #"{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + # latency = 5, + # ram_name = "huffman_literals_weights_ram", + # rd_req = "zstd_dec__huffman_lit_weights_mem_rd_req_s", + # rd_resp = "zstd_dec__huffman_lit_weights_mem_rd_resp_r", + # wr_req = "zstd_dec__huffman_lit_weights_mem_wr_req_s", + # wr_resp = "zstd_dec__huffman_lit_weights_mem_wr_resp_r", + #), + ",".join([ + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "literals_buffer_ram{}".format(num), + rd_req = "zstd_dec__litbuf_rd_req_s__{}".format(num), + rd_resp = "zstd_dec__litbuf_rd_resp_r__{}".format(num), + wr_req = "zstd_dec__litbuf_wr_req_s__{}".format(num), + wr_resp = "zstd_dec__litbuf_wr_resp_r__{}".format(num), + ) + for num in range(8) + ]), + ]), +} + +xls_dslx_verilog( + name = "zstd_dec_verilog", + codegen_args = ZSTD_DEC_CODEGEN_ARGS, + dslx_top = "ZstdDecoderInst", + library = ":zstd_dec_dslx", + tags = ["manual"], + verilog_file = "zstd_dec.v", +) + ZSTD_DEC_INTERNAL_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { "module_name": "ZstdDecoderInternal", "pipeline_stages": "2", @@ -1521,6 +1615,82 @@ place_and_route( target_die_utilization_percentage = "10", ) +verilog_library( + name = "zstd_dec_verilog_lib", + srcs = [ + ":zstd_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "zstd_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "ZstdDecoder", + deps = [ + ":zstd_dec_verilog_lib", + "//xls/modules/zstd/rtl:xls_fifo_wrapper_verilog_lib", + ], +) + +benchmark_synth( + name = "zstd_dec_benchmark_synth", + synth_target = ":zstd_dec_synth_asap7", + tags = ["manual"], +) + +#place_and_route( +# name = "zstd_dec_place_and_route", +# clock_period = CLOCK_PERIOD_PS, +# core_padding_microns = 2, +# min_pin_distance = "0.4", +# placement_density = "0.1", +# stop_after_step = "global_routing", +# synthesized_rtl = ":zstd_dec_synth_asap7", +# tags = ["manual"], +# target_die_utilization_percentage = "1", +#) + +py_test( + name = "zstd_dec_cocotb_test", + srcs = ["zstd_dec_cocotb_test.py"], + data = [ + ":zstd_dec.v", + "//xls/modules/zstd/external:arbiter.v", + "//xls/modules/zstd/external:axi_crossbar.v", + "//xls/modules/zstd/external:axi_crossbar_addr.v", + "//xls/modules/zstd/external:axi_crossbar_rd.v", + "//xls/modules/zstd/external:axi_crossbar_wr.v", + "//xls/modules/zstd/external:axi_crossbar_wrapper.v", + "//xls/modules/zstd/external:axi_register_rd.v", + "//xls/modules/zstd/external:axi_register_wr.v", + "//xls/modules/zstd/external:priority_encoder.v", + "//xls/modules/zstd/rtl:zstd_dec_wrapper.sv", + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, + imports = ["."], + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("cocotb"), + requirement("cocotbext-axi"), + requirement("pytest"), + requirement("zstandard"), + "//xls/common:runfiles", + "//xls/modules/zstd/cocotb:channel", + "//xls/modules/zstd/cocotb:data_generator", + "//xls/modules/zstd/cocotb:memory", + "//xls/modules/zstd/cocotb:utils", + "//xls/modules/zstd/cocotb:xlsstruct", + "@abseil-py//absl:app", + "@abseil-py//absl/flags", + "@com_google_protobuf//:protobuf_python", + ], +) + xls_dslx_library( name = "comp_lookup_dec_dslx", srcs = [ @@ -3462,3 +3632,38 @@ xls_dslx_test( name = "refilling_shift_buffer_mux_dslx_test", library = ":refilling_shift_buffer_mux_dslx", ) + +py_test( + name = "zstd_dec_cocotb_test", + srcs = ["zstd_dec_cocotb_test.py"], + data = [ + ":arbiter.v", + ":axi_interconnect.v", + ":axi_interconnect_wrapper.v", + ":priority_encoder.v", + ":xls_fifo_wrapper.sv", + ":zstd_dec.v", + ":zstd_dec_wrapper.sv", + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, + imports = ["."], + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("cocotb"), + requirement("cocotbext-axi"), + requirement("pytest"), + requirement("zstandard"), + "//xls/common:runfiles", + "//xls/modules/zstd/cocotb:channel", + "//xls/modules/zstd/cocotb:data_generator", + "//xls/modules/zstd/cocotb:memory", + "//xls/modules/zstd/cocotb:utils", + "//xls/modules/zstd/cocotb:xlsstruct", + "@com_google_absl_py//absl:app", + "@com_google_absl_py//absl/flags", + "@com_google_protobuf//:protobuf_python", + ], +) diff --git a/xls/modules/zstd/arbiter.v b/xls/modules/zstd/arbiter.v new file mode 100644 index 0000000000..cfac70d1c6 --- /dev/null +++ b/xls/modules/zstd/arbiter.v @@ -0,0 +1,159 @@ +/* + +Copyright (c) 2014-2021 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * Arbiter module + */ +module arbiter # +( + parameter PORTS = 4, + // select round robin arbitration + parameter ARB_TYPE_ROUND_ROBIN = 0, + // blocking arbiter enable + parameter ARB_BLOCK = 0, + // block on acknowledge assert when nonzero, request deassert when 0 + parameter ARB_BLOCK_ACK = 1, + // LSB priority selection + parameter ARB_LSB_HIGH_PRIORITY = 0 +) +( + input wire clk, + input wire rst, + + input wire [PORTS-1:0] request, + input wire [PORTS-1:0] acknowledge, + + output wire [PORTS-1:0] grant, + output wire grant_valid, + output wire [$clog2(PORTS)-1:0] grant_encoded +); + +reg [PORTS-1:0] grant_reg = 0, grant_next; +reg grant_valid_reg = 0, grant_valid_next; +reg [$clog2(PORTS)-1:0] grant_encoded_reg = 0, grant_encoded_next; + +assign grant_valid = grant_valid_reg; +assign grant = grant_reg; +assign grant_encoded = grant_encoded_reg; + +wire request_valid; +wire [$clog2(PORTS)-1:0] request_index; +wire [PORTS-1:0] request_mask; + +priority_encoder #( + .WIDTH(PORTS), + .LSB_HIGH_PRIORITY(ARB_LSB_HIGH_PRIORITY) +) +priority_encoder_inst ( + .input_unencoded(request), + .output_valid(request_valid), + .output_encoded(request_index), + .output_unencoded(request_mask) +); + +reg [PORTS-1:0] mask_reg = 0, mask_next; + +wire masked_request_valid; +wire [$clog2(PORTS)-1:0] masked_request_index; +wire [PORTS-1:0] masked_request_mask; + +priority_encoder #( + .WIDTH(PORTS), + .LSB_HIGH_PRIORITY(ARB_LSB_HIGH_PRIORITY) +) +priority_encoder_masked ( + .input_unencoded(request & mask_reg), + .output_valid(masked_request_valid), + .output_encoded(masked_request_index), + .output_unencoded(masked_request_mask) +); + +always @* begin + grant_next = 0; + grant_valid_next = 0; + grant_encoded_next = 0; + mask_next = mask_reg; + + if (ARB_BLOCK && !ARB_BLOCK_ACK && grant_reg & request) begin + // granted request still asserted; hold it + grant_valid_next = grant_valid_reg; + grant_next = grant_reg; + grant_encoded_next = grant_encoded_reg; + end else if (ARB_BLOCK && ARB_BLOCK_ACK && grant_valid && !(grant_reg & acknowledge)) begin + // granted request not yet acknowledged; hold it + grant_valid_next = grant_valid_reg; + grant_next = grant_reg; + grant_encoded_next = grant_encoded_reg; + end else if (request_valid) begin + if (ARB_TYPE_ROUND_ROBIN) begin + if (masked_request_valid) begin + grant_valid_next = 1; + grant_next = masked_request_mask; + grant_encoded_next = masked_request_index; + if (ARB_LSB_HIGH_PRIORITY) begin + mask_next = {PORTS{1'b1}} << (masked_request_index + 1); + end else begin + mask_next = {PORTS{1'b1}} >> (PORTS - masked_request_index); + end + end else begin + grant_valid_next = 1; + grant_next = request_mask; + grant_encoded_next = request_index; + if (ARB_LSB_HIGH_PRIORITY) begin + mask_next = {PORTS{1'b1}} << (request_index + 1); + end else begin + mask_next = {PORTS{1'b1}} >> (PORTS - request_index); + end + end + end else begin + grant_valid_next = 1; + grant_next = request_mask; + grant_encoded_next = request_index; + end + end +end + +always @(posedge clk) begin + if (rst) begin + grant_reg <= 0; + grant_valid_reg <= 0; + grant_encoded_reg <= 0; + mask_reg <= 0; + end else begin + grant_reg <= grant_next; + grant_valid_reg <= grant_valid_next; + grant_encoded_reg <= grant_encoded_next; + mask_reg <= mask_next; + end +end + +endmodule + +`resetall diff --git a/xls/modules/zstd/axi_interconnect.v b/xls/modules/zstd/axi_interconnect.v new file mode 100644 index 0000000000..14256ad7de --- /dev/null +++ b/xls/modules/zstd/axi_interconnect.v @@ -0,0 +1,988 @@ +/* + +Copyright (c) 2018 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 interconnect + */ +module axi_interconnect # +( + // Number of AXI inputs (slave interfaces) + parameter S_COUNT = 4, + // Number of AXI outputs (master interfaces) + parameter M_COUNT = 4, + // Width of data bus in bits + parameter DATA_WIDTH = 32, + // Width of address bus in bits + parameter ADDR_WIDTH = 32, + // Width of wstrb (width of data bus in words) + parameter STRB_WIDTH = (DATA_WIDTH/8), + // Width of ID signal + parameter ID_WIDTH = 8, + // Propagate awuser signal + parameter AWUSER_ENABLE = 0, + // Width of awuser signal + parameter AWUSER_WIDTH = 1, + // Propagate wuser signal + parameter WUSER_ENABLE = 0, + // Width of wuser signal + parameter WUSER_WIDTH = 1, + // Propagate buser signal + parameter BUSER_ENABLE = 0, + // Width of buser signal + parameter BUSER_WIDTH = 1, + // Propagate aruser signal + parameter ARUSER_ENABLE = 0, + // Width of aruser signal + parameter ARUSER_WIDTH = 1, + // Propagate ruser signal + parameter RUSER_ENABLE = 0, + // Width of ruser signal + parameter RUSER_WIDTH = 1, + // Propagate ID field + parameter FORWARD_ID = 0, + // Number of regions per master interface + parameter M_REGIONS = 1, + // Master interface base addresses + // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits + // set to zero for default addressing based on M_ADDR_WIDTH + parameter M_BASE_ADDR = 0, + // Master interface address widths + // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, + // Read connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT_READ = {M_COUNT{{S_COUNT{1'b1}}}}, + // Write connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT_WRITE = {M_COUNT{{S_COUNT{1'b1}}}}, + // Secure master (fail operations based on awprot/arprot) + // M_COUNT bits + parameter M_SECURE = {M_COUNT{1'b0}} +) +( + input wire clk, + input wire rst, + + /* + * AXI slave interfaces + */ + input wire [S_COUNT*ID_WIDTH-1:0] s_axi_awid, + input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_awaddr, + input wire [S_COUNT*8-1:0] s_axi_awlen, + input wire [S_COUNT*3-1:0] s_axi_awsize, + input wire [S_COUNT*2-1:0] s_axi_awburst, + input wire [S_COUNT-1:0] s_axi_awlock, + input wire [S_COUNT*4-1:0] s_axi_awcache, + input wire [S_COUNT*3-1:0] s_axi_awprot, + input wire [S_COUNT*4-1:0] s_axi_awqos, + input wire [S_COUNT*AWUSER_WIDTH-1:0] s_axi_awuser, + input wire [S_COUNT-1:0] s_axi_awvalid, + output wire [S_COUNT-1:0] s_axi_awready, + input wire [S_COUNT*DATA_WIDTH-1:0] s_axi_wdata, + input wire [S_COUNT*STRB_WIDTH-1:0] s_axi_wstrb, + input wire [S_COUNT-1:0] s_axi_wlast, + input wire [S_COUNT*WUSER_WIDTH-1:0] s_axi_wuser, + input wire [S_COUNT-1:0] s_axi_wvalid, + output wire [S_COUNT-1:0] s_axi_wready, + output wire [S_COUNT*ID_WIDTH-1:0] s_axi_bid, + output wire [S_COUNT*2-1:0] s_axi_bresp, + output wire [S_COUNT*BUSER_WIDTH-1:0] s_axi_buser, + output wire [S_COUNT-1:0] s_axi_bvalid, + input wire [S_COUNT-1:0] s_axi_bready, + input wire [S_COUNT*ID_WIDTH-1:0] s_axi_arid, + input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_araddr, + input wire [S_COUNT*8-1:0] s_axi_arlen, + input wire [S_COUNT*3-1:0] s_axi_arsize, + input wire [S_COUNT*2-1:0] s_axi_arburst, + input wire [S_COUNT-1:0] s_axi_arlock, + input wire [S_COUNT*4-1:0] s_axi_arcache, + input wire [S_COUNT*3-1:0] s_axi_arprot, + input wire [S_COUNT*4-1:0] s_axi_arqos, + input wire [S_COUNT*ARUSER_WIDTH-1:0] s_axi_aruser, + input wire [S_COUNT-1:0] s_axi_arvalid, + output wire [S_COUNT-1:0] s_axi_arready, + output wire [S_COUNT*ID_WIDTH-1:0] s_axi_rid, + output wire [S_COUNT*DATA_WIDTH-1:0] s_axi_rdata, + output wire [S_COUNT*2-1:0] s_axi_rresp, + output wire [S_COUNT-1:0] s_axi_rlast, + output wire [S_COUNT*RUSER_WIDTH-1:0] s_axi_ruser, + output wire [S_COUNT-1:0] s_axi_rvalid, + input wire [S_COUNT-1:0] s_axi_rready, + + /* + * AXI master interfaces + */ + output wire [M_COUNT*ID_WIDTH-1:0] m_axi_awid, + output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_awaddr, + output wire [M_COUNT*8-1:0] m_axi_awlen, + output wire [M_COUNT*3-1:0] m_axi_awsize, + output wire [M_COUNT*2-1:0] m_axi_awburst, + output wire [M_COUNT-1:0] m_axi_awlock, + output wire [M_COUNT*4-1:0] m_axi_awcache, + output wire [M_COUNT*3-1:0] m_axi_awprot, + output wire [M_COUNT*4-1:0] m_axi_awqos, + output wire [M_COUNT*4-1:0] m_axi_awregion, + output wire [M_COUNT*AWUSER_WIDTH-1:0] m_axi_awuser, + output wire [M_COUNT-1:0] m_axi_awvalid, + input wire [M_COUNT-1:0] m_axi_awready, + output wire [M_COUNT*DATA_WIDTH-1:0] m_axi_wdata, + output wire [M_COUNT*STRB_WIDTH-1:0] m_axi_wstrb, + output wire [M_COUNT-1:0] m_axi_wlast, + output wire [M_COUNT*WUSER_WIDTH-1:0] m_axi_wuser, + output wire [M_COUNT-1:0] m_axi_wvalid, + input wire [M_COUNT-1:0] m_axi_wready, + input wire [M_COUNT*ID_WIDTH-1:0] m_axi_bid, + input wire [M_COUNT*2-1:0] m_axi_bresp, + input wire [M_COUNT*BUSER_WIDTH-1:0] m_axi_buser, + input wire [M_COUNT-1:0] m_axi_bvalid, + output wire [M_COUNT-1:0] m_axi_bready, + output wire [M_COUNT*ID_WIDTH-1:0] m_axi_arid, + output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_araddr, + output wire [M_COUNT*8-1:0] m_axi_arlen, + output wire [M_COUNT*3-1:0] m_axi_arsize, + output wire [M_COUNT*2-1:0] m_axi_arburst, + output wire [M_COUNT-1:0] m_axi_arlock, + output wire [M_COUNT*4-1:0] m_axi_arcache, + output wire [M_COUNT*3-1:0] m_axi_arprot, + output wire [M_COUNT*4-1:0] m_axi_arqos, + output wire [M_COUNT*4-1:0] m_axi_arregion, + output wire [M_COUNT*ARUSER_WIDTH-1:0] m_axi_aruser, + output wire [M_COUNT-1:0] m_axi_arvalid, + input wire [M_COUNT-1:0] m_axi_arready, + input wire [M_COUNT*ID_WIDTH-1:0] m_axi_rid, + input wire [M_COUNT*DATA_WIDTH-1:0] m_axi_rdata, + input wire [M_COUNT*2-1:0] m_axi_rresp, + input wire [M_COUNT-1:0] m_axi_rlast, + input wire [M_COUNT*RUSER_WIDTH-1:0] m_axi_ruser, + input wire [M_COUNT-1:0] m_axi_rvalid, + output wire [M_COUNT-1:0] m_axi_rready +); + +parameter CL_S_COUNT = $clog2(S_COUNT); +parameter CL_M_COUNT = $clog2(M_COUNT); + +parameter AUSER_WIDTH = AWUSER_WIDTH > ARUSER_WIDTH ? AWUSER_WIDTH : ARUSER_WIDTH; + +// default address computation +function [M_COUNT*M_REGIONS*ADDR_WIDTH-1:0] calcBaseAddrs(input [31:0] dummy); + integer i; + reg [ADDR_WIDTH-1:0] base; + reg [ADDR_WIDTH-1:0] width; + reg [ADDR_WIDTH-1:0] size; + reg [ADDR_WIDTH-1:0] mask; + begin + calcBaseAddrs = {M_COUNT*M_REGIONS*ADDR_WIDTH{1'b0}}; + base = 0; + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + width = M_ADDR_WIDTH[i*32 +: 32]; + mask = {ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - width); + size = mask + 1; + if (width > 0) begin + if ((base & mask) != 0) begin + base = base + size - (base & mask); // align + end + calcBaseAddrs[i * ADDR_WIDTH +: ADDR_WIDTH] = base; + base = base + size; // increment + end + end + end +endfunction + +parameter M_BASE_ADDR_INT = M_BASE_ADDR ? M_BASE_ADDR : calcBaseAddrs(0); + +integer i, j; + +// check configuration +initial begin + if (M_REGIONS < 1 || M_REGIONS > 16) begin + $error("Error: M_REGIONS must be between 1 and 16 (instance %m)"); + $finish; + end + + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + if (M_ADDR_WIDTH[i*32 +: 32] && (M_ADDR_WIDTH[i*32 +: 32] < 12 || M_ADDR_WIDTH[i*32 +: 32] > ADDR_WIDTH)) begin + $error("Error: address width out of range (instance %m)"); + $finish; + end + end + + $display("Addressing configuration for axi_interconnect instance %m"); + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + if (M_ADDR_WIDTH[i*32 +: 32]) begin + $display("%2d (%2d): %x / %02d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], + M_ADDR_WIDTH[i*32 +: 32], + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) + ); + end + end + + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + if ((M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & (2**M_ADDR_WIDTH[i*32 +: 32]-1)) != 0) begin + $display("Region not aligned:"); + $display("%2d (%2d): %x / %2d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], + M_ADDR_WIDTH[i*32 +: 32], + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) + ); + $error("Error: address range not aligned (instance %m)"); + $finish; + end + end + + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + for (j = i+1; j < M_COUNT*M_REGIONS; j = j + 1) begin + if (M_ADDR_WIDTH[i*32 +: 32] && M_ADDR_WIDTH[j*32 +: 32]) begin + if (((M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32])) <= (M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[j*32 +: 32])))) + && ((M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[j*32 +: 32])) <= (M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32]))))) begin + $display("Overlapping regions:"); + $display("%2d (%2d): %x / %2d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], + M_ADDR_WIDTH[i*32 +: 32], + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) + ); + $display("%2d (%2d): %x / %2d -- %x-%x", + j/M_REGIONS, j%M_REGIONS, + M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH], + M_ADDR_WIDTH[j*32 +: 32], + M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[j*32 +: 32]), + M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[j*32 +: 32])) + ); + $error("Error: address ranges overlap (instance %m)"); + $finish; + end + end + end + end +end + +localparam [2:0] + STATE_IDLE = 3'd0, + STATE_DECODE = 3'd1, + STATE_WRITE = 3'd2, + STATE_WRITE_RESP = 3'd3, + STATE_WRITE_DROP = 3'd4, + STATE_READ = 3'd5, + STATE_READ_DROP = 3'd6, + STATE_WAIT_IDLE = 3'd7; + +reg [2:0] state_reg = STATE_IDLE, state_next; + +reg match; + +reg [CL_M_COUNT-1:0] m_select_reg = 2'd0, m_select_next; +reg [ID_WIDTH-1:0] axi_id_reg = {ID_WIDTH{1'b0}}, axi_id_next; +reg [ADDR_WIDTH-1:0] axi_addr_reg = {ADDR_WIDTH{1'b0}}, axi_addr_next; +reg axi_addr_valid_reg = 1'b0, axi_addr_valid_next; +reg [7:0] axi_len_reg = 8'd0, axi_len_next; +reg [2:0] axi_size_reg = 3'd0, axi_size_next; +reg [1:0] axi_burst_reg = 2'd0, axi_burst_next; +reg axi_lock_reg = 1'b0, axi_lock_next; +reg [3:0] axi_cache_reg = 4'd0, axi_cache_next; +reg [2:0] axi_prot_reg = 3'b000, axi_prot_next; +reg [3:0] axi_qos_reg = 4'd0, axi_qos_next; +reg [3:0] axi_region_reg = 4'd0, axi_region_next; +reg [AUSER_WIDTH-1:0] axi_auser_reg = {AUSER_WIDTH{1'b0}}, axi_auser_next; +reg [1:0] axi_bresp_reg = 2'b00, axi_bresp_next; +reg [BUSER_WIDTH-1:0] axi_buser_reg = {BUSER_WIDTH{1'b0}}, axi_buser_next; + +reg [S_COUNT-1:0] s_axi_awready_reg = 0, s_axi_awready_next; +reg [S_COUNT-1:0] s_axi_wready_reg = 0, s_axi_wready_next; +reg [S_COUNT-1:0] s_axi_bvalid_reg = 0, s_axi_bvalid_next; +reg [S_COUNT-1:0] s_axi_arready_reg = 0, s_axi_arready_next; + +reg [M_COUNT-1:0] m_axi_awvalid_reg = 0, m_axi_awvalid_next; +reg [M_COUNT-1:0] m_axi_bready_reg = 0, m_axi_bready_next; +reg [M_COUNT-1:0] m_axi_arvalid_reg = 0, m_axi_arvalid_next; +reg [M_COUNT-1:0] m_axi_rready_reg = 0, m_axi_rready_next; + +// internal datapath +reg [ID_WIDTH-1:0] s_axi_rid_int; +reg [DATA_WIDTH-1:0] s_axi_rdata_int; +reg [1:0] s_axi_rresp_int; +reg s_axi_rlast_int; +reg [RUSER_WIDTH-1:0] s_axi_ruser_int; +reg s_axi_rvalid_int; +reg s_axi_rready_int_reg = 1'b0; +wire s_axi_rready_int_early; + +reg [DATA_WIDTH-1:0] m_axi_wdata_int; +reg [STRB_WIDTH-1:0] m_axi_wstrb_int; +reg m_axi_wlast_int; +reg [WUSER_WIDTH-1:0] m_axi_wuser_int; +reg m_axi_wvalid_int; +reg m_axi_wready_int_reg = 1'b0; +wire m_axi_wready_int_early; + +assign s_axi_awready = s_axi_awready_reg; +assign s_axi_wready = s_axi_wready_reg; +assign s_axi_bid = {S_COUNT{axi_id_reg}}; +assign s_axi_bresp = {S_COUNT{axi_bresp_reg}}; +assign s_axi_buser = {S_COUNT{BUSER_ENABLE ? axi_buser_reg : {BUSER_WIDTH{1'b0}}}}; +assign s_axi_bvalid = s_axi_bvalid_reg; +assign s_axi_arready = s_axi_arready_reg; + +assign m_axi_awid = {M_COUNT{FORWARD_ID ? axi_id_reg : {ID_WIDTH{1'b0}}}}; +assign m_axi_awaddr = {M_COUNT{axi_addr_reg}}; +assign m_axi_awlen = {M_COUNT{axi_len_reg}}; +assign m_axi_awsize = {M_COUNT{axi_size_reg}}; +assign m_axi_awburst = {M_COUNT{axi_burst_reg}}; +assign m_axi_awlock = {M_COUNT{axi_lock_reg}}; +assign m_axi_awcache = {M_COUNT{axi_cache_reg}}; +assign m_axi_awprot = {M_COUNT{axi_prot_reg}}; +assign m_axi_awqos = {M_COUNT{axi_qos_reg}}; +assign m_axi_awregion = {M_COUNT{axi_region_reg}}; +assign m_axi_awuser = {M_COUNT{AWUSER_ENABLE ? axi_auser_reg[AWUSER_WIDTH-1:0] : {AWUSER_WIDTH{1'b0}}}}; +assign m_axi_awvalid = m_axi_awvalid_reg; +assign m_axi_bready = m_axi_bready_reg; +assign m_axi_arid = {M_COUNT{FORWARD_ID ? axi_id_reg : {ID_WIDTH{1'b0}}}}; +assign m_axi_araddr = {M_COUNT{axi_addr_reg}}; +assign m_axi_arlen = {M_COUNT{axi_len_reg}}; +assign m_axi_arsize = {M_COUNT{axi_size_reg}}; +assign m_axi_arburst = {M_COUNT{axi_burst_reg}}; +assign m_axi_arlock = {M_COUNT{axi_lock_reg}}; +assign m_axi_arcache = {M_COUNT{axi_cache_reg}}; +assign m_axi_arprot = {M_COUNT{axi_prot_reg}}; +assign m_axi_arqos = {M_COUNT{axi_qos_reg}}; +assign m_axi_arregion = {M_COUNT{axi_region_reg}}; +assign m_axi_aruser = {M_COUNT{ARUSER_ENABLE ? axi_auser_reg[ARUSER_WIDTH-1:0] : {ARUSER_WIDTH{1'b0}}}}; +assign m_axi_arvalid = m_axi_arvalid_reg; +assign m_axi_rready = m_axi_rready_reg; + +// slave side mux +wire [(CL_S_COUNT > 0 ? CL_S_COUNT-1 : 0):0] s_select; + +wire [ID_WIDTH-1:0] current_s_axi_awid = s_axi_awid[s_select*ID_WIDTH +: ID_WIDTH]; +wire [ADDR_WIDTH-1:0] current_s_axi_awaddr = s_axi_awaddr[s_select*ADDR_WIDTH +: ADDR_WIDTH]; +wire [7:0] current_s_axi_awlen = s_axi_awlen[s_select*8 +: 8]; +wire [2:0] current_s_axi_awsize = s_axi_awsize[s_select*3 +: 3]; +wire [1:0] current_s_axi_awburst = s_axi_awburst[s_select*2 +: 2]; +wire current_s_axi_awlock = s_axi_awlock[s_select]; +wire [3:0] current_s_axi_awcache = s_axi_awcache[s_select*4 +: 4]; +wire [2:0] current_s_axi_awprot = s_axi_awprot[s_select*3 +: 3]; +wire [3:0] current_s_axi_awqos = s_axi_awqos[s_select*4 +: 4]; +wire [AWUSER_WIDTH-1:0] current_s_axi_awuser = s_axi_awuser[s_select*AWUSER_WIDTH +: AWUSER_WIDTH]; +wire current_s_axi_awvalid = s_axi_awvalid[s_select]; +wire current_s_axi_awready = s_axi_awready[s_select]; +wire [DATA_WIDTH-1:0] current_s_axi_wdata = s_axi_wdata[s_select*DATA_WIDTH +: DATA_WIDTH]; +wire [STRB_WIDTH-1:0] current_s_axi_wstrb = s_axi_wstrb[s_select*STRB_WIDTH +: STRB_WIDTH]; +wire current_s_axi_wlast = s_axi_wlast[s_select]; +wire [WUSER_WIDTH-1:0] current_s_axi_wuser = s_axi_wuser[s_select*WUSER_WIDTH +: WUSER_WIDTH]; +wire current_s_axi_wvalid = s_axi_wvalid[s_select]; +wire current_s_axi_wready = s_axi_wready[s_select]; +wire [ID_WIDTH-1:0] current_s_axi_bid = s_axi_bid[s_select*ID_WIDTH +: ID_WIDTH]; +wire [1:0] current_s_axi_bresp = s_axi_bresp[s_select*2 +: 2]; +wire [BUSER_WIDTH-1:0] current_s_axi_buser = s_axi_buser[s_select*BUSER_WIDTH +: BUSER_WIDTH]; +wire current_s_axi_bvalid = s_axi_bvalid[s_select]; +wire current_s_axi_bready = s_axi_bready[s_select]; +wire [ID_WIDTH-1:0] current_s_axi_arid = s_axi_arid[s_select*ID_WIDTH +: ID_WIDTH]; +wire [ADDR_WIDTH-1:0] current_s_axi_araddr = s_axi_araddr[s_select*ADDR_WIDTH +: ADDR_WIDTH]; +wire [7:0] current_s_axi_arlen = s_axi_arlen[s_select*8 +: 8]; +wire [2:0] current_s_axi_arsize = s_axi_arsize[s_select*3 +: 3]; +wire [1:0] current_s_axi_arburst = s_axi_arburst[s_select*2 +: 2]; +wire current_s_axi_arlock = s_axi_arlock[s_select]; +wire [3:0] current_s_axi_arcache = s_axi_arcache[s_select*4 +: 4]; +wire [2:0] current_s_axi_arprot = s_axi_arprot[s_select*3 +: 3]; +wire [3:0] current_s_axi_arqos = s_axi_arqos[s_select*4 +: 4]; +wire [ARUSER_WIDTH-1:0] current_s_axi_aruser = s_axi_aruser[s_select*ARUSER_WIDTH +: ARUSER_WIDTH]; +wire current_s_axi_arvalid = s_axi_arvalid[s_select]; +wire current_s_axi_arready = s_axi_arready[s_select]; +wire [ID_WIDTH-1:0] current_s_axi_rid = s_axi_rid[s_select*ID_WIDTH +: ID_WIDTH]; +wire [DATA_WIDTH-1:0] current_s_axi_rdata = s_axi_rdata[s_select*DATA_WIDTH +: DATA_WIDTH]; +wire [1:0] current_s_axi_rresp = s_axi_rresp[s_select*2 +: 2]; +wire current_s_axi_rlast = s_axi_rlast[s_select]; +wire [RUSER_WIDTH-1:0] current_s_axi_ruser = s_axi_ruser[s_select*RUSER_WIDTH +: RUSER_WIDTH]; +wire current_s_axi_rvalid = s_axi_rvalid[s_select]; +wire current_s_axi_rready = s_axi_rready[s_select]; + +// master side mux +wire [ID_WIDTH-1:0] current_m_axi_awid = m_axi_awid[m_select_reg*ID_WIDTH +: ID_WIDTH]; +wire [ADDR_WIDTH-1:0] current_m_axi_awaddr = m_axi_awaddr[m_select_reg*ADDR_WIDTH +: ADDR_WIDTH]; +wire [7:0] current_m_axi_awlen = m_axi_awlen[m_select_reg*8 +: 8]; +wire [2:0] current_m_axi_awsize = m_axi_awsize[m_select_reg*3 +: 3]; +wire [1:0] current_m_axi_awburst = m_axi_awburst[m_select_reg*2 +: 2]; +wire current_m_axi_awlock = m_axi_awlock[m_select_reg]; +wire [3:0] current_m_axi_awcache = m_axi_awcache[m_select_reg*4 +: 4]; +wire [2:0] current_m_axi_awprot = m_axi_awprot[m_select_reg*3 +: 3]; +wire [3:0] current_m_axi_awqos = m_axi_awqos[m_select_reg*4 +: 4]; +wire [3:0] current_m_axi_awregion = m_axi_awregion[m_select_reg*4 +: 4]; +wire [AWUSER_WIDTH-1:0] current_m_axi_awuser = m_axi_awuser[m_select_reg*AWUSER_WIDTH +: AWUSER_WIDTH]; +wire current_m_axi_awvalid = m_axi_awvalid[m_select_reg]; +wire current_m_axi_awready = m_axi_awready[m_select_reg]; +wire [DATA_WIDTH-1:0] current_m_axi_wdata = m_axi_wdata[m_select_reg*DATA_WIDTH +: DATA_WIDTH]; +wire [STRB_WIDTH-1:0] current_m_axi_wstrb = m_axi_wstrb[m_select_reg*STRB_WIDTH +: STRB_WIDTH]; +wire current_m_axi_wlast = m_axi_wlast[m_select_reg]; +wire [WUSER_WIDTH-1:0] current_m_axi_wuser = m_axi_wuser[m_select_reg*WUSER_WIDTH +: WUSER_WIDTH]; +wire current_m_axi_wvalid = m_axi_wvalid[m_select_reg]; +wire current_m_axi_wready = m_axi_wready[m_select_reg]; +wire [ID_WIDTH-1:0] current_m_axi_bid = m_axi_bid[m_select_reg*ID_WIDTH +: ID_WIDTH]; +wire [1:0] current_m_axi_bresp = m_axi_bresp[m_select_reg*2 +: 2]; +wire [BUSER_WIDTH-1:0] current_m_axi_buser = m_axi_buser[m_select_reg*BUSER_WIDTH +: BUSER_WIDTH]; +wire current_m_axi_bvalid = m_axi_bvalid[m_select_reg]; +wire current_m_axi_bready = m_axi_bready[m_select_reg]; +wire [ID_WIDTH-1:0] current_m_axi_arid = m_axi_arid[m_select_reg*ID_WIDTH +: ID_WIDTH]; +wire [ADDR_WIDTH-1:0] current_m_axi_araddr = m_axi_araddr[m_select_reg*ADDR_WIDTH +: ADDR_WIDTH]; +wire [7:0] current_m_axi_arlen = m_axi_arlen[m_select_reg*8 +: 8]; +wire [2:0] current_m_axi_arsize = m_axi_arsize[m_select_reg*3 +: 3]; +wire [1:0] current_m_axi_arburst = m_axi_arburst[m_select_reg*2 +: 2]; +wire current_m_axi_arlock = m_axi_arlock[m_select_reg]; +wire [3:0] current_m_axi_arcache = m_axi_arcache[m_select_reg*4 +: 4]; +wire [2:0] current_m_axi_arprot = m_axi_arprot[m_select_reg*3 +: 3]; +wire [3:0] current_m_axi_arqos = m_axi_arqos[m_select_reg*4 +: 4]; +wire [3:0] current_m_axi_arregion = m_axi_arregion[m_select_reg*4 +: 4]; +wire [ARUSER_WIDTH-1:0] current_m_axi_aruser = m_axi_aruser[m_select_reg*ARUSER_WIDTH +: ARUSER_WIDTH]; +wire current_m_axi_arvalid = m_axi_arvalid[m_select_reg]; +wire current_m_axi_arready = m_axi_arready[m_select_reg]; +wire [ID_WIDTH-1:0] current_m_axi_rid = m_axi_rid[m_select_reg*ID_WIDTH +: ID_WIDTH]; +wire [DATA_WIDTH-1:0] current_m_axi_rdata = m_axi_rdata[m_select_reg*DATA_WIDTH +: DATA_WIDTH]; +wire [1:0] current_m_axi_rresp = m_axi_rresp[m_select_reg*2 +: 2]; +wire current_m_axi_rlast = m_axi_rlast[m_select_reg]; +wire [RUSER_WIDTH-1:0] current_m_axi_ruser = m_axi_ruser[m_select_reg*RUSER_WIDTH +: RUSER_WIDTH]; +wire current_m_axi_rvalid = m_axi_rvalid[m_select_reg]; +wire current_m_axi_rready = m_axi_rready[m_select_reg]; + +// arbiter instance +wire [S_COUNT*2-1:0] request; +wire [S_COUNT*2-1:0] acknowledge; +wire [S_COUNT*2-1:0] grant; +wire grant_valid; +wire [CL_S_COUNT:0] grant_encoded; + +wire read = grant_encoded[0]; +assign s_select = grant_encoded >> 1; + +arbiter #( + .PORTS(S_COUNT*2), + .ARB_TYPE_ROUND_ROBIN(1), + .ARB_BLOCK(1), + .ARB_BLOCK_ACK(1), + .ARB_LSB_HIGH_PRIORITY(1) +) +arb_inst ( + .clk(clk), + .rst(rst), + .request(request), + .acknowledge(acknowledge), + .grant(grant), + .grant_valid(grant_valid), + .grant_encoded(grant_encoded) +); + +genvar n; + +// request generation +generate +for (n = 0; n < S_COUNT; n = n + 1) begin + assign request[2*n] = s_axi_awvalid[n]; + assign request[2*n+1] = s_axi_arvalid[n]; +end +endgenerate + +// acknowledge generation +generate +for (n = 0; n < S_COUNT; n = n + 1) begin + assign acknowledge[2*n] = grant[2*n] && s_axi_bvalid[n] && s_axi_bready[n]; + assign acknowledge[2*n+1] = grant[2*n+1] && s_axi_rvalid[n] && s_axi_rready[n] && s_axi_rlast[n]; +end +endgenerate + +always @* begin + state_next = STATE_IDLE; + + match = 1'b0; + + m_select_next = m_select_reg; + axi_id_next = axi_id_reg; + axi_addr_next = axi_addr_reg; + axi_addr_valid_next = axi_addr_valid_reg; + axi_len_next = axi_len_reg; + axi_size_next = axi_size_reg; + axi_burst_next = axi_burst_reg; + axi_lock_next = axi_lock_reg; + axi_cache_next = axi_cache_reg; + axi_prot_next = axi_prot_reg; + axi_qos_next = axi_qos_reg; + axi_region_next = axi_region_reg; + axi_auser_next = axi_auser_reg; + axi_bresp_next = axi_bresp_reg; + axi_buser_next = axi_buser_reg; + + s_axi_awready_next = 0; + s_axi_wready_next = 0; + s_axi_bvalid_next = s_axi_bvalid_reg & ~s_axi_bready; + s_axi_arready_next = 0; + + m_axi_awvalid_next = m_axi_awvalid_reg & ~m_axi_awready; + m_axi_bready_next = 0; + m_axi_arvalid_next = m_axi_arvalid_reg & ~m_axi_arready; + m_axi_rready_next = 0; + + s_axi_rid_int = axi_id_reg; + s_axi_rdata_int = current_m_axi_rdata; + s_axi_rresp_int = current_m_axi_rresp; + s_axi_rlast_int = current_m_axi_rlast; + s_axi_ruser_int = current_m_axi_ruser; + s_axi_rvalid_int = 1'b0; + + m_axi_wdata_int = current_s_axi_wdata; + m_axi_wstrb_int = current_s_axi_wstrb; + m_axi_wlast_int = current_s_axi_wlast; + m_axi_wuser_int = current_s_axi_wuser; + m_axi_wvalid_int = 1'b0; + + case (state_reg) + STATE_IDLE: begin + // idle state; wait for arbitration + + if (grant_valid) begin + + axi_addr_valid_next = 1'b1; + + if (read) begin + // reading + axi_addr_next = current_s_axi_araddr; + axi_prot_next = current_s_axi_arprot; + axi_id_next = current_s_axi_arid; + axi_addr_next = current_s_axi_araddr; + axi_len_next = current_s_axi_arlen; + axi_size_next = current_s_axi_arsize; + axi_burst_next = current_s_axi_arburst; + axi_lock_next = current_s_axi_arlock; + axi_cache_next = current_s_axi_arcache; + axi_prot_next = current_s_axi_arprot; + axi_qos_next = current_s_axi_arqos; + axi_auser_next = current_s_axi_aruser; + s_axi_arready_next[s_select] = 1'b1; + end else begin + // writing + axi_addr_next = current_s_axi_awaddr; + axi_prot_next = current_s_axi_awprot; + axi_id_next = current_s_axi_awid; + axi_addr_next = current_s_axi_awaddr; + axi_len_next = current_s_axi_awlen; + axi_size_next = current_s_axi_awsize; + axi_burst_next = current_s_axi_awburst; + axi_lock_next = current_s_axi_awlock; + axi_cache_next = current_s_axi_awcache; + axi_prot_next = current_s_axi_awprot; + axi_qos_next = current_s_axi_awqos; + axi_auser_next = current_s_axi_awuser; + s_axi_awready_next[s_select] = 1'b1; + end + + state_next = STATE_DECODE; + end else begin + state_next = STATE_IDLE; + end + end + STATE_DECODE: begin + // decode state; determine master interface + + match = 1'b0; + for (i = 0; i < M_COUNT; i = i + 1) begin + for (j = 0; j < M_REGIONS; j = j + 1) begin + if (M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32] && (!M_SECURE[i] || !axi_prot_reg[1]) && ((read ? M_CONNECT_READ : M_CONNECT_WRITE) & (1 << (s_select+i*S_COUNT))) && (axi_addr_reg >> M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32]) == (M_BASE_ADDR_INT[(i*M_REGIONS+j)*ADDR_WIDTH +: ADDR_WIDTH] >> M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32])) begin + m_select_next = i; + axi_region_next = j; + match = 1'b1; + end + end + end + + if (match) begin + if (read) begin + // reading + m_axi_rready_next[m_select_reg] = s_axi_rready_int_early; + state_next = STATE_READ; + end else begin + // writing + s_axi_wready_next[s_select] = m_axi_wready_int_early; + state_next = STATE_WRITE; + end + end else begin + // no match; return decode error + if (read) begin + // reading + state_next = STATE_READ_DROP; + end else begin + // writing + axi_bresp_next = 2'b11; + s_axi_wready_next[s_select] = 1'b1; + state_next = STATE_WRITE_DROP; + end + end + end + STATE_WRITE: begin + // write state; store and forward write data + s_axi_wready_next[s_select] = m_axi_wready_int_early; + + if (axi_addr_valid_reg) begin + m_axi_awvalid_next[m_select_reg] = 1'b1; + end + axi_addr_valid_next = 1'b0; + + if (current_s_axi_wready && current_s_axi_wvalid) begin + m_axi_wdata_int = current_s_axi_wdata; + m_axi_wstrb_int = current_s_axi_wstrb; + m_axi_wlast_int = current_s_axi_wlast; + m_axi_wuser_int = current_s_axi_wuser; + m_axi_wvalid_int = 1'b1; + + if (current_s_axi_wlast) begin + s_axi_wready_next[s_select] = 1'b0; + m_axi_bready_next[m_select_reg] = 1'b1; + state_next = STATE_WRITE_RESP; + end else begin + state_next = STATE_WRITE; + end + end else begin + state_next = STATE_WRITE; + end + end + STATE_WRITE_RESP: begin + // write response state; store and forward write response + m_axi_bready_next[m_select_reg] = 1'b1; + + if (current_m_axi_bready && current_m_axi_bvalid) begin + m_axi_bready_next[m_select_reg] = 1'b0; + axi_bresp_next = current_m_axi_bresp; + s_axi_bvalid_next[s_select] = 1'b1; + state_next = STATE_WAIT_IDLE; + end else begin + state_next = STATE_WRITE_RESP; + end + end + STATE_WRITE_DROP: begin + // write drop state; drop write data + s_axi_wready_next[s_select] = 1'b1; + + axi_addr_valid_next = 1'b0; + + if (current_s_axi_wready && current_s_axi_wvalid && current_s_axi_wlast) begin + s_axi_wready_next[s_select] = 1'b0; + s_axi_bvalid_next[s_select] = 1'b1; + state_next = STATE_WAIT_IDLE; + end else begin + state_next = STATE_WRITE_DROP; + end + end + STATE_READ: begin + // read state; store and forward read response + m_axi_rready_next[m_select_reg] = s_axi_rready_int_early; + + if (axi_addr_valid_reg) begin + m_axi_arvalid_next[m_select_reg] = 1'b1; + end + axi_addr_valid_next = 1'b0; + + if (current_m_axi_rready && current_m_axi_rvalid) begin + s_axi_rid_int = axi_id_reg; + s_axi_rdata_int = current_m_axi_rdata; + s_axi_rresp_int = current_m_axi_rresp; + s_axi_rlast_int = current_m_axi_rlast; + s_axi_ruser_int = current_m_axi_ruser; + s_axi_rvalid_int = 1'b1; + + if (current_m_axi_rlast) begin + m_axi_rready_next[m_select_reg] = 1'b0; + state_next = STATE_WAIT_IDLE; + end else begin + state_next = STATE_READ; + end + end else begin + state_next = STATE_READ; + end + end + STATE_READ_DROP: begin + // read drop state; generate decode error read response + + s_axi_rid_int = axi_id_reg; + s_axi_rdata_int = {DATA_WIDTH{1'b0}}; + s_axi_rresp_int = 2'b11; + s_axi_rlast_int = axi_len_reg == 0; + s_axi_ruser_int = {RUSER_WIDTH{1'b0}}; + s_axi_rvalid_int = 1'b1; + + if (s_axi_rready_int_reg) begin + axi_len_next = axi_len_reg - 1; + if (axi_len_reg == 0) begin + state_next = STATE_WAIT_IDLE; + end else begin + state_next = STATE_READ_DROP; + end + end else begin + state_next = STATE_READ_DROP; + end + end + STATE_WAIT_IDLE: begin + // wait for idle state; wait untl grant valid is deasserted + + if (!grant_valid || acknowledge) begin + state_next = STATE_IDLE; + end else begin + state_next = STATE_WAIT_IDLE; + end + end + endcase +end + +always @(posedge clk) begin + if (rst) begin + state_reg <= STATE_IDLE; + + s_axi_awready_reg <= 0; + s_axi_wready_reg <= 0; + s_axi_bvalid_reg <= 0; + s_axi_arready_reg <= 0; + + m_axi_awvalid_reg <= 0; + m_axi_bready_reg <= 0; + m_axi_arvalid_reg <= 0; + m_axi_rready_reg <= 0; + end else begin + state_reg <= state_next; + + s_axi_awready_reg <= s_axi_awready_next; + s_axi_wready_reg <= s_axi_wready_next; + s_axi_bvalid_reg <= s_axi_bvalid_next; + s_axi_arready_reg <= s_axi_arready_next; + + m_axi_awvalid_reg <= m_axi_awvalid_next; + m_axi_bready_reg <= m_axi_bready_next; + m_axi_arvalid_reg <= m_axi_arvalid_next; + m_axi_rready_reg <= m_axi_rready_next; + end + + m_select_reg <= m_select_next; + axi_id_reg <= axi_id_next; + axi_addr_reg <= axi_addr_next; + axi_addr_valid_reg <= axi_addr_valid_next; + axi_len_reg <= axi_len_next; + axi_size_reg <= axi_size_next; + axi_burst_reg <= axi_burst_next; + axi_lock_reg <= axi_lock_next; + axi_cache_reg <= axi_cache_next; + axi_prot_reg <= axi_prot_next; + axi_qos_reg <= axi_qos_next; + axi_region_reg <= axi_region_next; + axi_auser_reg <= axi_auser_next; + axi_bresp_reg <= axi_bresp_next; + axi_buser_reg <= axi_buser_next; +end + +// output datapath logic (R channel) +reg [ID_WIDTH-1:0] s_axi_rid_reg = {ID_WIDTH{1'b0}}; +reg [DATA_WIDTH-1:0] s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; +reg [1:0] s_axi_rresp_reg = 2'd0; +reg s_axi_rlast_reg = 1'b0; +reg [RUSER_WIDTH-1:0] s_axi_ruser_reg = 1'b0; +reg [S_COUNT-1:0] s_axi_rvalid_reg = 1'b0, s_axi_rvalid_next; + +reg [ID_WIDTH-1:0] temp_s_axi_rid_reg = {ID_WIDTH{1'b0}}; +reg [DATA_WIDTH-1:0] temp_s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; +reg [1:0] temp_s_axi_rresp_reg = 2'd0; +reg temp_s_axi_rlast_reg = 1'b0; +reg [RUSER_WIDTH-1:0] temp_s_axi_ruser_reg = 1'b0; +reg temp_s_axi_rvalid_reg = 1'b0, temp_s_axi_rvalid_next; + +// datapath control +reg store_axi_r_int_to_output; +reg store_axi_r_int_to_temp; +reg store_axi_r_temp_to_output; + +assign s_axi_rid = {S_COUNT{s_axi_rid_reg}}; +assign s_axi_rdata = {S_COUNT{s_axi_rdata_reg}}; +assign s_axi_rresp = {S_COUNT{s_axi_rresp_reg}}; +assign s_axi_rlast = {S_COUNT{s_axi_rlast_reg}}; +assign s_axi_ruser = {S_COUNT{RUSER_ENABLE ? s_axi_ruser_reg : {RUSER_WIDTH{1'b0}}}}; +assign s_axi_rvalid = s_axi_rvalid_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +assign s_axi_rready_int_early = current_s_axi_rready | (~temp_s_axi_rvalid_reg & (~current_s_axi_rvalid | ~s_axi_rvalid_int)); + +always @* begin + // transfer sink ready state to source + s_axi_rvalid_next = s_axi_rvalid_reg; + temp_s_axi_rvalid_next = temp_s_axi_rvalid_reg; + + store_axi_r_int_to_output = 1'b0; + store_axi_r_int_to_temp = 1'b0; + store_axi_r_temp_to_output = 1'b0; + + if (s_axi_rready_int_reg) begin + // input is ready + if (current_s_axi_rready | ~current_s_axi_rvalid) begin + // output is ready or currently not valid, transfer data to output + s_axi_rvalid_next[s_select] = s_axi_rvalid_int; + store_axi_r_int_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_s_axi_rvalid_next = s_axi_rvalid_int; + store_axi_r_int_to_temp = 1'b1; + end + end else if (current_s_axi_rready) begin + // input is not ready, but output is ready + s_axi_rvalid_next[s_select] = temp_s_axi_rvalid_reg; + temp_s_axi_rvalid_next = 1'b0; + store_axi_r_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + s_axi_rvalid_reg <= 1'b0; + s_axi_rready_int_reg <= 1'b0; + temp_s_axi_rvalid_reg <= 1'b0; + end else begin + s_axi_rvalid_reg <= s_axi_rvalid_next; + s_axi_rready_int_reg <= s_axi_rready_int_early; + temp_s_axi_rvalid_reg <= temp_s_axi_rvalid_next; + end + + // datapath + if (store_axi_r_int_to_output) begin + s_axi_rid_reg <= s_axi_rid_int; + s_axi_rdata_reg <= s_axi_rdata_int; + s_axi_rresp_reg <= s_axi_rresp_int; + s_axi_rlast_reg <= s_axi_rlast_int; + s_axi_ruser_reg <= s_axi_ruser_int; + end else if (store_axi_r_temp_to_output) begin + s_axi_rid_reg <= temp_s_axi_rid_reg; + s_axi_rdata_reg <= temp_s_axi_rdata_reg; + s_axi_rresp_reg <= temp_s_axi_rresp_reg; + s_axi_rlast_reg <= temp_s_axi_rlast_reg; + s_axi_ruser_reg <= temp_s_axi_ruser_reg; + end + + if (store_axi_r_int_to_temp) begin + temp_s_axi_rid_reg <= s_axi_rid_int; + temp_s_axi_rdata_reg <= s_axi_rdata_int; + temp_s_axi_rresp_reg <= s_axi_rresp_int; + temp_s_axi_rlast_reg <= s_axi_rlast_int; + temp_s_axi_ruser_reg <= s_axi_ruser_int; + end +end + +// output datapath logic (W channel) +reg [DATA_WIDTH-1:0] m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; +reg [STRB_WIDTH-1:0] m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; +reg m_axi_wlast_reg = 1'b0; +reg [WUSER_WIDTH-1:0] m_axi_wuser_reg = 1'b0; +reg [M_COUNT-1:0] m_axi_wvalid_reg = 1'b0, m_axi_wvalid_next; + +reg [DATA_WIDTH-1:0] temp_m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; +reg [STRB_WIDTH-1:0] temp_m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; +reg temp_m_axi_wlast_reg = 1'b0; +reg [WUSER_WIDTH-1:0] temp_m_axi_wuser_reg = 1'b0; +reg temp_m_axi_wvalid_reg = 1'b0, temp_m_axi_wvalid_next; + +// datapath control +reg store_axi_w_int_to_output; +reg store_axi_w_int_to_temp; +reg store_axi_w_temp_to_output; + +assign m_axi_wdata = {M_COUNT{m_axi_wdata_reg}}; +assign m_axi_wstrb = {M_COUNT{m_axi_wstrb_reg}}; +assign m_axi_wlast = {M_COUNT{m_axi_wlast_reg}}; +assign m_axi_wuser = {M_COUNT{WUSER_ENABLE ? m_axi_wuser_reg : {WUSER_WIDTH{1'b0}}}}; +assign m_axi_wvalid = m_axi_wvalid_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +assign m_axi_wready_int_early = current_m_axi_wready | (~temp_m_axi_wvalid_reg & (~current_m_axi_wvalid | ~m_axi_wvalid_int)); + +always @* begin + // transfer sink ready state to source + m_axi_wvalid_next = m_axi_wvalid_reg; + temp_m_axi_wvalid_next = temp_m_axi_wvalid_reg; + + store_axi_w_int_to_output = 1'b0; + store_axi_w_int_to_temp = 1'b0; + store_axi_w_temp_to_output = 1'b0; + + if (m_axi_wready_int_reg) begin + // input is ready + if (current_m_axi_wready | ~current_m_axi_wvalid) begin + // output is ready or currently not valid, transfer data to output + m_axi_wvalid_next[m_select_reg] = m_axi_wvalid_int; + store_axi_w_int_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_m_axi_wvalid_next = m_axi_wvalid_int; + store_axi_w_int_to_temp = 1'b1; + end + end else if (current_m_axi_wready) begin + // input is not ready, but output is ready + m_axi_wvalid_next[m_select_reg] = temp_m_axi_wvalid_reg; + temp_m_axi_wvalid_next = 1'b0; + store_axi_w_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + m_axi_wvalid_reg <= 1'b0; + m_axi_wready_int_reg <= 1'b0; + temp_m_axi_wvalid_reg <= 1'b0; + end else begin + m_axi_wvalid_reg <= m_axi_wvalid_next; + m_axi_wready_int_reg <= m_axi_wready_int_early; + temp_m_axi_wvalid_reg <= temp_m_axi_wvalid_next; + end + + // datapath + if (store_axi_w_int_to_output) begin + m_axi_wdata_reg <= m_axi_wdata_int; + m_axi_wstrb_reg <= m_axi_wstrb_int; + m_axi_wlast_reg <= m_axi_wlast_int; + m_axi_wuser_reg <= m_axi_wuser_int; + end else if (store_axi_w_temp_to_output) begin + m_axi_wdata_reg <= temp_m_axi_wdata_reg; + m_axi_wstrb_reg <= temp_m_axi_wstrb_reg; + m_axi_wlast_reg <= temp_m_axi_wlast_reg; + m_axi_wuser_reg <= temp_m_axi_wuser_reg; + end + + if (store_axi_w_int_to_temp) begin + temp_m_axi_wdata_reg <= m_axi_wdata_int; + temp_m_axi_wstrb_reg <= m_axi_wstrb_int; + temp_m_axi_wlast_reg <= m_axi_wlast_int; + temp_m_axi_wuser_reg <= m_axi_wuser_int; + end +end + +endmodule + +`resetall diff --git a/xls/modules/zstd/axi_interconnect_wrapper.v b/xls/modules/zstd/axi_interconnect_wrapper.v new file mode 100644 index 0000000000..50017314f9 --- /dev/null +++ b/xls/modules/zstd/axi_interconnect_wrapper.v @@ -0,0 +1,424 @@ +/* + +Copyright (c) 2020 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 4x1 interconnect (wrapper) + */ +module axi_interconnect_wrapper # +( + parameter DATA_WIDTH = 32, + parameter ADDR_WIDTH = 32, + parameter STRB_WIDTH = (DATA_WIDTH/8), + parameter ID_WIDTH = 8, + parameter AWUSER_ENABLE = 0, + parameter AWUSER_WIDTH = 1, + parameter WUSER_ENABLE = 0, + parameter WUSER_WIDTH = 1, + parameter BUSER_ENABLE = 0, + parameter BUSER_WIDTH = 1, + parameter ARUSER_ENABLE = 0, + parameter ARUSER_WIDTH = 1, + parameter RUSER_ENABLE = 0, + parameter RUSER_WIDTH = 1, + parameter FORWARD_ID = 0, + parameter M_REGIONS = 1, + parameter M00_BASE_ADDR = 0, + parameter M00_ADDR_WIDTH = {M_REGIONS{32'd24}}, + parameter M00_CONNECT_READ = 4'b1111, + parameter M00_CONNECT_WRITE = 4'b1111, + parameter M00_SECURE = 1'b0 +) +( + input wire clk, + input wire rst, + + /* + * AXI slave interface + */ + input wire [ID_WIDTH-1:0] s00_axi_awid, + input wire [ADDR_WIDTH-1:0] s00_axi_awaddr, + input wire [7:0] s00_axi_awlen, + input wire [2:0] s00_axi_awsize, + input wire [1:0] s00_axi_awburst, + input wire s00_axi_awlock, + input wire [3:0] s00_axi_awcache, + input wire [2:0] s00_axi_awprot, + input wire [3:0] s00_axi_awqos, + input wire [AWUSER_WIDTH-1:0] s00_axi_awuser, + input wire s00_axi_awvalid, + output wire s00_axi_awready, + input wire [DATA_WIDTH-1:0] s00_axi_wdata, + input wire [STRB_WIDTH-1:0] s00_axi_wstrb, + input wire s00_axi_wlast, + input wire [WUSER_WIDTH-1:0] s00_axi_wuser, + input wire s00_axi_wvalid, + output wire s00_axi_wready, + output wire [ID_WIDTH-1:0] s00_axi_bid, + output wire [1:0] s00_axi_bresp, + output wire [BUSER_WIDTH-1:0] s00_axi_buser, + output wire s00_axi_bvalid, + input wire s00_axi_bready, + input wire [ID_WIDTH-1:0] s00_axi_arid, + input wire [ADDR_WIDTH-1:0] s00_axi_araddr, + input wire [7:0] s00_axi_arlen, + input wire [2:0] s00_axi_arsize, + input wire [1:0] s00_axi_arburst, + input wire s00_axi_arlock, + input wire [3:0] s00_axi_arcache, + input wire [2:0] s00_axi_arprot, + input wire [3:0] s00_axi_arqos, + input wire [ARUSER_WIDTH-1:0] s00_axi_aruser, + input wire s00_axi_arvalid, + output wire s00_axi_arready, + output wire [ID_WIDTH-1:0] s00_axi_rid, + output wire [DATA_WIDTH-1:0] s00_axi_rdata, + output wire [1:0] s00_axi_rresp, + output wire s00_axi_rlast, + output wire [RUSER_WIDTH-1:0] s00_axi_ruser, + output wire s00_axi_rvalid, + input wire s00_axi_rready, + + input wire [ID_WIDTH-1:0] s01_axi_awid, + input wire [ADDR_WIDTH-1:0] s01_axi_awaddr, + input wire [7:0] s01_axi_awlen, + input wire [2:0] s01_axi_awsize, + input wire [1:0] s01_axi_awburst, + input wire s01_axi_awlock, + input wire [3:0] s01_axi_awcache, + input wire [2:0] s01_axi_awprot, + input wire [3:0] s01_axi_awqos, + input wire [AWUSER_WIDTH-1:0] s01_axi_awuser, + input wire s01_axi_awvalid, + output wire s01_axi_awready, + input wire [DATA_WIDTH-1:0] s01_axi_wdata, + input wire [STRB_WIDTH-1:0] s01_axi_wstrb, + input wire s01_axi_wlast, + input wire [WUSER_WIDTH-1:0] s01_axi_wuser, + input wire s01_axi_wvalid, + output wire s01_axi_wready, + output wire [ID_WIDTH-1:0] s01_axi_bid, + output wire [1:0] s01_axi_bresp, + output wire [BUSER_WIDTH-1:0] s01_axi_buser, + output wire s01_axi_bvalid, + input wire s01_axi_bready, + input wire [ID_WIDTH-1:0] s01_axi_arid, + input wire [ADDR_WIDTH-1:0] s01_axi_araddr, + input wire [7:0] s01_axi_arlen, + input wire [2:0] s01_axi_arsize, + input wire [1:0] s01_axi_arburst, + input wire s01_axi_arlock, + input wire [3:0] s01_axi_arcache, + input wire [2:0] s01_axi_arprot, + input wire [3:0] s01_axi_arqos, + input wire [ARUSER_WIDTH-1:0] s01_axi_aruser, + input wire s01_axi_arvalid, + output wire s01_axi_arready, + output wire [ID_WIDTH-1:0] s01_axi_rid, + output wire [DATA_WIDTH-1:0] s01_axi_rdata, + output wire [1:0] s01_axi_rresp, + output wire s01_axi_rlast, + output wire [RUSER_WIDTH-1:0] s01_axi_ruser, + output wire s01_axi_rvalid, + input wire s01_axi_rready, + + input wire [ID_WIDTH-1:0] s02_axi_awid, + input wire [ADDR_WIDTH-1:0] s02_axi_awaddr, + input wire [7:0] s02_axi_awlen, + input wire [2:0] s02_axi_awsize, + input wire [1:0] s02_axi_awburst, + input wire s02_axi_awlock, + input wire [3:0] s02_axi_awcache, + input wire [2:0] s02_axi_awprot, + input wire [3:0] s02_axi_awqos, + input wire [AWUSER_WIDTH-1:0] s02_axi_awuser, + input wire s02_axi_awvalid, + output wire s02_axi_awready, + input wire [DATA_WIDTH-1:0] s02_axi_wdata, + input wire [STRB_WIDTH-1:0] s02_axi_wstrb, + input wire s02_axi_wlast, + input wire [WUSER_WIDTH-1:0] s02_axi_wuser, + input wire s02_axi_wvalid, + output wire s02_axi_wready, + output wire [ID_WIDTH-1:0] s02_axi_bid, + output wire [1:0] s02_axi_bresp, + output wire [BUSER_WIDTH-1:0] s02_axi_buser, + output wire s02_axi_bvalid, + input wire s02_axi_bready, + input wire [ID_WIDTH-1:0] s02_axi_arid, + input wire [ADDR_WIDTH-1:0] s02_axi_araddr, + input wire [7:0] s02_axi_arlen, + input wire [2:0] s02_axi_arsize, + input wire [1:0] s02_axi_arburst, + input wire s02_axi_arlock, + input wire [3:0] s02_axi_arcache, + input wire [2:0] s02_axi_arprot, + input wire [3:0] s02_axi_arqos, + input wire [ARUSER_WIDTH-1:0] s02_axi_aruser, + input wire s02_axi_arvalid, + output wire s02_axi_arready, + output wire [ID_WIDTH-1:0] s02_axi_rid, + output wire [DATA_WIDTH-1:0] s02_axi_rdata, + output wire [1:0] s02_axi_rresp, + output wire s02_axi_rlast, + output wire [RUSER_WIDTH-1:0] s02_axi_ruser, + output wire s02_axi_rvalid, + input wire s02_axi_rready, + + input wire [ID_WIDTH-1:0] s03_axi_awid, + input wire [ADDR_WIDTH-1:0] s03_axi_awaddr, + input wire [7:0] s03_axi_awlen, + input wire [2:0] s03_axi_awsize, + input wire [1:0] s03_axi_awburst, + input wire s03_axi_awlock, + input wire [3:0] s03_axi_awcache, + input wire [2:0] s03_axi_awprot, + input wire [3:0] s03_axi_awqos, + input wire [AWUSER_WIDTH-1:0] s03_axi_awuser, + input wire s03_axi_awvalid, + output wire s03_axi_awready, + input wire [DATA_WIDTH-1:0] s03_axi_wdata, + input wire [STRB_WIDTH-1:0] s03_axi_wstrb, + input wire s03_axi_wlast, + input wire [WUSER_WIDTH-1:0] s03_axi_wuser, + input wire s03_axi_wvalid, + output wire s03_axi_wready, + output wire [ID_WIDTH-1:0] s03_axi_bid, + output wire [1:0] s03_axi_bresp, + output wire [BUSER_WIDTH-1:0] s03_axi_buser, + output wire s03_axi_bvalid, + input wire s03_axi_bready, + input wire [ID_WIDTH-1:0] s03_axi_arid, + input wire [ADDR_WIDTH-1:0] s03_axi_araddr, + input wire [7:0] s03_axi_arlen, + input wire [2:0] s03_axi_arsize, + input wire [1:0] s03_axi_arburst, + input wire s03_axi_arlock, + input wire [3:0] s03_axi_arcache, + input wire [2:0] s03_axi_arprot, + input wire [3:0] s03_axi_arqos, + input wire [ARUSER_WIDTH-1:0] s03_axi_aruser, + input wire s03_axi_arvalid, + output wire s03_axi_arready, + output wire [ID_WIDTH-1:0] s03_axi_rid, + output wire [DATA_WIDTH-1:0] s03_axi_rdata, + output wire [1:0] s03_axi_rresp, + output wire s03_axi_rlast, + output wire [RUSER_WIDTH-1:0] s03_axi_ruser, + output wire s03_axi_rvalid, + input wire s03_axi_rready, + + /* + * AXI master interface + */ + output wire [ID_WIDTH-1:0] m00_axi_awid, + output wire [ADDR_WIDTH-1:0] m00_axi_awaddr, + output wire [7:0] m00_axi_awlen, + output wire [2:0] m00_axi_awsize, + output wire [1:0] m00_axi_awburst, + output wire m00_axi_awlock, + output wire [3:0] m00_axi_awcache, + output wire [2:0] m00_axi_awprot, + output wire [3:0] m00_axi_awqos, + output wire [3:0] m00_axi_awregion, + output wire [AWUSER_WIDTH-1:0] m00_axi_awuser, + output wire m00_axi_awvalid, + input wire m00_axi_awready, + output wire [DATA_WIDTH-1:0] m00_axi_wdata, + output wire [STRB_WIDTH-1:0] m00_axi_wstrb, + output wire m00_axi_wlast, + output wire [WUSER_WIDTH-1:0] m00_axi_wuser, + output wire m00_axi_wvalid, + input wire m00_axi_wready, + input wire [ID_WIDTH-1:0] m00_axi_bid, + input wire [1:0] m00_axi_bresp, + input wire [BUSER_WIDTH-1:0] m00_axi_buser, + input wire m00_axi_bvalid, + output wire m00_axi_bready, + output wire [ID_WIDTH-1:0] m00_axi_arid, + output wire [ADDR_WIDTH-1:0] m00_axi_araddr, + output wire [7:0] m00_axi_arlen, + output wire [2:0] m00_axi_arsize, + output wire [1:0] m00_axi_arburst, + output wire m00_axi_arlock, + output wire [3:0] m00_axi_arcache, + output wire [2:0] m00_axi_arprot, + output wire [3:0] m00_axi_arqos, + output wire [3:0] m00_axi_arregion, + output wire [ARUSER_WIDTH-1:0] m00_axi_aruser, + output wire m00_axi_arvalid, + input wire m00_axi_arready, + input wire [ID_WIDTH-1:0] m00_axi_rid, + input wire [DATA_WIDTH-1:0] m00_axi_rdata, + input wire [1:0] m00_axi_rresp, + input wire m00_axi_rlast, + input wire [RUSER_WIDTH-1:0] m00_axi_ruser, + input wire m00_axi_rvalid, + output wire m00_axi_rready +); + +localparam S_COUNT = 4; +localparam M_COUNT = 1; + +// parameter sizing helpers +function [ADDR_WIDTH*M_REGIONS-1:0] w_a_r(input [ADDR_WIDTH*M_REGIONS-1:0] val); + w_a_r = val; +endfunction + +function [32*M_REGIONS-1:0] w_32_r(input [32*M_REGIONS-1:0] val); + w_32_r = val; +endfunction + +function [S_COUNT-1:0] w_s(input [S_COUNT-1:0] val); + w_s = val; +endfunction + +function w_1(input val); + w_1 = val; +endfunction + +axi_interconnect #( + .S_COUNT(S_COUNT), + .M_COUNT(M_COUNT), + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .STRB_WIDTH(STRB_WIDTH), + .ID_WIDTH(ID_WIDTH), + .AWUSER_ENABLE(AWUSER_ENABLE), + .AWUSER_WIDTH(AWUSER_WIDTH), + .WUSER_ENABLE(WUSER_ENABLE), + .WUSER_WIDTH(WUSER_WIDTH), + .BUSER_ENABLE(BUSER_ENABLE), + .BUSER_WIDTH(BUSER_WIDTH), + .ARUSER_ENABLE(ARUSER_ENABLE), + .ARUSER_WIDTH(ARUSER_WIDTH), + .RUSER_ENABLE(RUSER_ENABLE), + .RUSER_WIDTH(RUSER_WIDTH), + .FORWARD_ID(FORWARD_ID), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR({ w_a_r(M00_BASE_ADDR) }), + .M_ADDR_WIDTH({ w_32_r(M00_ADDR_WIDTH) }), + .M_CONNECT_READ({ w_s(M00_CONNECT_READ) }), + .M_CONNECT_WRITE({ w_s(M00_CONNECT_WRITE) }), + .M_SECURE({ w_1(M00_SECURE) }) +) +axi_interconnect_inst ( + .clk(clk), + .rst(rst), + .s_axi_awid({ s03_axi_awid, s02_axi_awid, s01_axi_awid, s00_axi_awid }), + .s_axi_awaddr({ s03_axi_awaddr, s02_axi_awaddr, s01_axi_awaddr, s00_axi_awaddr }), + .s_axi_awlen({ s03_axi_awlen, s02_axi_awlen, s01_axi_awlen, s00_axi_awlen }), + .s_axi_awsize({ s03_axi_awsize, s02_axi_awsize, s01_axi_awsize, s00_axi_awsize }), + .s_axi_awburst({ s03_axi_awburst, s02_axi_awburst, s01_axi_awburst, s00_axi_awburst }), + .s_axi_awlock({ s03_axi_awlock, s02_axi_awlock, s01_axi_awlock, s00_axi_awlock }), + .s_axi_awcache({ s03_axi_awcache, s02_axi_awcache, s01_axi_awcache, s00_axi_awcache }), + .s_axi_awprot({ s03_axi_awprot, s02_axi_awprot, s01_axi_awprot, s00_axi_awprot }), + .s_axi_awqos({ s03_axi_awqos, s02_axi_awqos, s01_axi_awqos, s00_axi_awqos }), + .s_axi_awuser({ s03_axi_awuser, s02_axi_awuser, s01_axi_awuser, s00_axi_awuser }), + .s_axi_awvalid({ s03_axi_awvalid, s02_axi_awvalid, s01_axi_awvalid, s00_axi_awvalid }), + .s_axi_awready({ s03_axi_awready, s02_axi_awready, s01_axi_awready, s00_axi_awready }), + .s_axi_wdata({ s03_axi_wdata, s02_axi_wdata, s01_axi_wdata, s00_axi_wdata }), + .s_axi_wstrb({ s03_axi_wstrb, s02_axi_wstrb, s01_axi_wstrb, s00_axi_wstrb }), + .s_axi_wlast({ s03_axi_wlast, s02_axi_wlast, s01_axi_wlast, s00_axi_wlast }), + .s_axi_wuser({ s03_axi_wuser, s02_axi_wuser, s01_axi_wuser, s00_axi_wuser }), + .s_axi_wvalid({ s03_axi_wvalid, s02_axi_wvalid, s01_axi_wvalid, s00_axi_wvalid }), + .s_axi_wready({ s03_axi_wready, s02_axi_wready, s01_axi_wready, s00_axi_wready }), + .s_axi_bid({ s03_axi_bid, s02_axi_bid, s01_axi_bid, s00_axi_bid }), + .s_axi_bresp({ s03_axi_bresp, s02_axi_bresp, s01_axi_bresp, s00_axi_bresp }), + .s_axi_buser({ s03_axi_buser, s02_axi_buser, s01_axi_buser, s00_axi_buser }), + .s_axi_bvalid({ s03_axi_bvalid, s02_axi_bvalid, s01_axi_bvalid, s00_axi_bvalid }), + .s_axi_bready({ s03_axi_bready, s02_axi_bready, s01_axi_bready, s00_axi_bready }), + .s_axi_arid({ s03_axi_arid, s02_axi_arid, s01_axi_arid, s00_axi_arid }), + .s_axi_araddr({ s03_axi_araddr, s02_axi_araddr, s01_axi_araddr, s00_axi_araddr }), + .s_axi_arlen({ s03_axi_arlen, s02_axi_arlen, s01_axi_arlen, s00_axi_arlen }), + .s_axi_arsize({ s03_axi_arsize, s02_axi_arsize, s01_axi_arsize, s00_axi_arsize }), + .s_axi_arburst({ s03_axi_arburst, s02_axi_arburst, s01_axi_arburst, s00_axi_arburst }), + .s_axi_arlock({ s03_axi_arlock, s02_axi_arlock, s01_axi_arlock, s00_axi_arlock }), + .s_axi_arcache({ s03_axi_arcache, s02_axi_arcache, s01_axi_arcache, s00_axi_arcache }), + .s_axi_arprot({ s03_axi_arprot, s02_axi_arprot, s01_axi_arprot, s00_axi_arprot }), + .s_axi_arqos({ s03_axi_arqos, s02_axi_arqos, s01_axi_arqos, s00_axi_arqos }), + .s_axi_aruser({ s03_axi_aruser, s02_axi_aruser, s01_axi_aruser, s00_axi_aruser }), + .s_axi_arvalid({ s03_axi_arvalid, s02_axi_arvalid, s01_axi_arvalid, s00_axi_arvalid }), + .s_axi_arready({ s03_axi_arready, s02_axi_arready, s01_axi_arready, s00_axi_arready }), + .s_axi_rid({ s03_axi_rid, s02_axi_rid, s01_axi_rid, s00_axi_rid }), + .s_axi_rdata({ s03_axi_rdata, s02_axi_rdata, s01_axi_rdata, s00_axi_rdata }), + .s_axi_rresp({ s03_axi_rresp, s02_axi_rresp, s01_axi_rresp, s00_axi_rresp }), + .s_axi_rlast({ s03_axi_rlast, s02_axi_rlast, s01_axi_rlast, s00_axi_rlast }), + .s_axi_ruser({ s03_axi_ruser, s02_axi_ruser, s01_axi_ruser, s00_axi_ruser }), + .s_axi_rvalid({ s03_axi_rvalid, s02_axi_rvalid, s01_axi_rvalid, s00_axi_rvalid }), + .s_axi_rready({ s03_axi_rready, s02_axi_rready, s01_axi_rready, s00_axi_rready }), + .m_axi_awid({ m00_axi_awid }), + .m_axi_awaddr({ m00_axi_awaddr }), + .m_axi_awlen({ m00_axi_awlen }), + .m_axi_awsize({ m00_axi_awsize }), + .m_axi_awburst({ m00_axi_awburst }), + .m_axi_awlock({ m00_axi_awlock }), + .m_axi_awcache({ m00_axi_awcache }), + .m_axi_awprot({ m00_axi_awprot }), + .m_axi_awqos({ m00_axi_awqos }), + .m_axi_awregion({ m00_axi_awregion }), + .m_axi_awuser({ m00_axi_awuser }), + .m_axi_awvalid({ m00_axi_awvalid }), + .m_axi_awready({ m00_axi_awready }), + .m_axi_wdata({ m00_axi_wdata }), + .m_axi_wstrb({ m00_axi_wstrb }), + .m_axi_wlast({ m00_axi_wlast }), + .m_axi_wuser({ m00_axi_wuser }), + .m_axi_wvalid({ m00_axi_wvalid }), + .m_axi_wready({ m00_axi_wready }), + .m_axi_bid({ m00_axi_bid }), + .m_axi_bresp({ m00_axi_bresp }), + .m_axi_buser({ m00_axi_buser }), + .m_axi_bvalid({ m00_axi_bvalid }), + .m_axi_bready({ m00_axi_bready }), + .m_axi_arid({ m00_axi_arid }), + .m_axi_araddr({ m00_axi_araddr }), + .m_axi_arlen({ m00_axi_arlen }), + .m_axi_arsize({ m00_axi_arsize }), + .m_axi_arburst({ m00_axi_arburst }), + .m_axi_arlock({ m00_axi_arlock }), + .m_axi_arcache({ m00_axi_arcache }), + .m_axi_arprot({ m00_axi_arprot }), + .m_axi_arqos({ m00_axi_arqos }), + .m_axi_arregion({ m00_axi_arregion }), + .m_axi_aruser({ m00_axi_aruser }), + .m_axi_arvalid({ m00_axi_arvalid }), + .m_axi_arready({ m00_axi_arready }), + .m_axi_rid({ m00_axi_rid }), + .m_axi_rdata({ m00_axi_rdata }), + .m_axi_rresp({ m00_axi_rresp }), + .m_axi_rlast({ m00_axi_rlast }), + .m_axi_ruser({ m00_axi_ruser }), + .m_axi_rvalid({ m00_axi_rvalid }), + .m_axi_rready({ m00_axi_rready }) +); + +endmodule + +`resetall diff --git a/xls/modules/zstd/priority_encoder.v b/xls/modules/zstd/priority_encoder.v new file mode 100644 index 0000000000..cf82512ba8 --- /dev/null +++ b/xls/modules/zstd/priority_encoder.v @@ -0,0 +1,92 @@ +/* + +Copyright (c) 2014-2021 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * Priority encoder module + */ +module priority_encoder # +( + parameter WIDTH = 4, + // LSB priority selection + parameter LSB_HIGH_PRIORITY = 0 +) +( + input wire [WIDTH-1:0] input_unencoded, + output wire output_valid, + output wire [$clog2(WIDTH)-1:0] output_encoded, + output wire [WIDTH-1:0] output_unencoded +); + +parameter LEVELS = WIDTH > 2 ? $clog2(WIDTH) : 1; +parameter W = 2**LEVELS; + +// pad input to even power of two +wire [W-1:0] input_padded = {{W-WIDTH{1'b0}}, input_unencoded}; + +wire [W/2-1:0] stage_valid[LEVELS-1:0]; +wire [W/2-1:0] stage_enc[LEVELS-1:0]; + +generate + genvar l, n; + + // process input bits; generate valid bit and encoded bit for each pair + for (n = 0; n < W/2; n = n + 1) begin : loop_in + assign stage_valid[0][n] = |input_padded[n*2+1:n*2]; + if (LSB_HIGH_PRIORITY) begin + // bit 0 is highest priority + assign stage_enc[0][n] = !input_padded[n*2+0]; + end else begin + // bit 0 is lowest priority + assign stage_enc[0][n] = input_padded[n*2+1]; + end + end + + // compress down to single valid bit and encoded bus + for (l = 1; l < LEVELS; l = l + 1) begin : loop_levels + for (n = 0; n < W/(2*2**l); n = n + 1) begin : loop_compress + assign stage_valid[l][n] = |stage_valid[l-1][n*2+1:n*2]; + if (LSB_HIGH_PRIORITY) begin + // bit 0 is highest priority + assign stage_enc[l][(n+1)*(l+1)-1:n*(l+1)] = stage_valid[l-1][n*2+0] ? {1'b0, stage_enc[l-1][(n*2+1)*l-1:(n*2+0)*l]} : {1'b1, stage_enc[l-1][(n*2+2)*l-1:(n*2+1)*l]}; + end else begin + // bit 0 is lowest priority + assign stage_enc[l][(n+1)*(l+1)-1:n*(l+1)] = stage_valid[l-1][n*2+1] ? {1'b1, stage_enc[l-1][(n*2+2)*l-1:(n*2+1)*l]} : {1'b0, stage_enc[l-1][(n*2+1)*l-1:(n*2+0)*l]}; + end + end + end +endgenerate + +assign output_valid = stage_valid[LEVELS-1]; +assign output_encoded = stage_enc[LEVELS-1]; +assign output_unencoded = 1 << output_encoded; + +endmodule + +`resetall diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py new file mode 100644 index 0000000000..ff9fd5afff --- /dev/null +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -0,0 +1,418 @@ +#!/usr/bin/env python +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from enum import Enum +from pathlib import Path +import tempfile + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, Event +from cocotb.binary import BinaryValue +from cocotb_bus.scoreboard import Scoreboard + +from cocotbext.axi.axi_master import AxiMaster +from cocotbext.axi.axi_channels import AxiAWBus, AxiWBus, AxiBBus, AxiWriteBus, AxiARBus, AxiRBus, AxiReadBus, AxiBus, AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor +from cocotbext.axi.axi_ram import AxiRam +from cocotbext.axi.sparse_memory import SparseMemory + +import zstandard + +from xls.common import runfiles +from xls.modules.zstd.cocotb.channel import ( + XLSChannel, + XLSChannelDriver, + XLSChannelMonitor, +) +from xls.modules.zstd.cocotb.data_generator import GenerateFrame, BlockType +from xls.modules.zstd.cocotb.memory import init_axi_mem, AxiRamFromFile +from xls.modules.zstd.cocotb.utils import reset, run_test +from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass + +AXI_DATA_W = 64 +AXI_DATA_W_BYTES = AXI_DATA_W // 8 +MAX_ENCODED_FRAME_SIZE_B = 16384 +NOTIFY_CHANNEL = "notify" +OUTPUT_CHANNEL = "output" +RESET_CHANNEL = "reset" + +# Override default widths of AXI response signals +signal_widths = {"bresp": 3} +AxiBBus._signal_widths = signal_widths +AxiBTransaction._signal_widths = signal_widths +AxiBSource._signal_widths = signal_widths +AxiBSink._signal_widths = signal_widths +AxiBMonitor._signal_widths = signal_widths +signal_widths = {"rresp": 3, "rlast": 1} +AxiRBus._signal_widths = signal_widths +AxiRTransaction._signal_widths = signal_widths +AxiRSource._signal_widths = signal_widths +AxiRSink._signal_widths = signal_widths +AxiRMonitor._signal_widths = signal_widths + +@xls_dataclass +class NotifyStruct(XLSStruct): + pass + +@xls_dataclass +class ResetStruct(XLSStruct): + pass + +@xls_dataclass +class OutputStruct(XLSStruct): + data: 64 + length: 32 + last: 1 + +class CSR(Enum): + """ + Maps the offsets to the ZSTD Decoder registers + """ + Status = 0 + Start = 1 + Reset = 2 + InputBuffer = 3 + OutputBuffer = 4 + +class Status(Enum): + """ + Codes for the Status register + """ + IDLE = 0x0 + RUNNING = 0x1 + +def set_termination_event(monitor, event, transactions): + def terminate_cb(_): + if monitor.stats.received_transactions == transactions: + event.set() + monitor.add_callback(terminate_cb) + +def connect_axi_read_bus(dut, name=""): + AXI_AR = "axi_ar" + AXI_R = "axi_r" + + if name != "": + name += "_" + + bus_axi_ar = AxiARBus.from_prefix(dut, name + AXI_AR) + bus_axi_r = AxiRBus.from_prefix(dut, name + AXI_R) + + return AxiReadBus(bus_axi_ar, bus_axi_r) + +def connect_axi_write_bus(dut, name=""): + AXI_AW = "axi_aw" + AXI_W = "axi_w" + AXI_B = "axi_b" + + if name != "": + name += "_" + + bus_axi_aw = AxiAWBus.from_prefix(dut, name + AXI_AW) + bus_axi_w = AxiWBus.from_prefix(dut, name + AXI_W) + bus_axi_b = AxiBBus.from_prefix(dut, name + AXI_B) + + return AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) + +def connect_axi_bus(dut, name=""): + bus_axi_read = connect_axi_read_bus(dut, name) + bus_axi_write = connect_axi_write_bus(dut, name) + + return AxiBus(bus_axi_write, bus_axi_read) + +def get_decoded_frame_bytes(ifh): + dctx = zstandard.ZstdDecompressor() + return dctx.decompress(ifh.read()) + +def get_decoded_frame_buffer(ifh, address, memory=SparseMemory(size=MAX_ENCODED_FRAME_SIZE_B)): + dctx = zstandard.ZstdDecompressor() + memory.write(address, dctx.decompress(ifh.read())) + return memory + +async def csr_write(cpu, csr, data): + if type(data) is int: + data = data.to_bytes(AXI_DATA_W_BYTES, byteorder='little') + assert len(data) <= AXI_DATA_W_BYTES + await cpu.write(csr.value * AXI_DATA_W_BYTES, data) + +async def csr_read(cpu, csr): + return await cpu.read(csr.value * AXI_DATA_W_BYTES, AXI_DATA_W_BYTES) + +async def test_csr(dut): + + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + await reset_dut(dut, 50) + + csr_bus = connect_axi_bus(dut, "csr") + + cpu = AxiMaster(csr_bus, dut.clk, dut.rst) + + await ClockCycles(dut.clk, 10) + i = 0 + for reg in CSR: + # Reset CSR tested in a separate test case + if (reg == CSR.Reset): + continue + expected_src = bytearray.fromhex("0DF0AD8BEFBEADDE") + assert len(expected_src) >= AXI_DATA_W_BYTES + expected = expected_src[-AXI_DATA_W_BYTES:] + expected[0] += i + await csr_write(cpu, reg, expected) + read = await csr_read(cpu, reg) + assert read.data == expected, "Expected data doesn't match contents of the {}".format(reg) + i += 1 + await ClockCycles(dut.clk, 10) + +async def test_reset(dut): + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + await reset_dut(dut, 50) + + (reset_channel, reset_monitor) = connect_xls_channel(dut, RESET_CHANNEL, ResetStruct) + + csr_bus = connect_axi_bus(dut, "csr") + cpu = AxiMaster(csr_bus, dut.clk, dut.rst) + + scoreboard = Scoreboard(dut) + + rst_struct = ResetStruct() + # Expect single reset signal on reset output channel + expected_reset = [rst_struct] + scoreboard.add_interface(reset_monitor, expected_reset) + + await ClockCycles(dut.clk, 10) + await start_decoder(cpu) + timeout = 10 + status = await csr_read(cpu, CSR.Status) + while ((int.from_bytes(status.data, byteorder='little') == Status.IDLE.value) & (timeout != 0)): + status = await csr_read(cpu, CSR.Status) + timeout -= 1 + assert (timeout != 0) + + await csr_write(cpu, CSR.Reset, 0x1) + await wait_for_idle(cpu, 10) + + await ClockCycles(dut.clk, 10) + +async def configure_decoder(cpu, ibuf_addr, obuf_addr): + status = await csr_read(cpu, CSR.Status) + if int.from_bytes(status.data, byteorder='little') != Status.IDLE.value: + await csr_write(cpu, CSR.Reset, 0x1) + await csr_write(cpu, CSR.InputBuffer, ibuf_addr) + await csr_write(cpu, CSR.OutputBuffer, obuf_addr) + +async def start_decoder(cpu): + await csr_write(cpu, CSR.Start, 0x1) + +async def wait_for_idle(cpu, timeout=100): + status = await csr_read(cpu, CSR.Status) + while ((int.from_bytes(status.data, byteorder='little') != Status.IDLE.value) & (timeout != 0)): + status = await csr_read(cpu, CSR.Status) + timeout -= 1 + assert (timeout != 0) + +def generate_expected_output(decoded_frame): + packets = [] + frame_len = len(decoded_frame) + last_len = frame_len % 8 + for i in range(frame_len // 8): + start_id = i * 8 + end_id = start_id + 8 + packet_data = int.from_bytes(decoded_frame[start_id:end_id], byteorder='little') + last_packet = (end_id==frame_len) + packet = OutputStruct(data=packet_data, length=64, last=last_packet) + packets.append(packet) + if (last_len): + packet_data = int.from_bytes(decoded_frame[-last_len:], byteorder='little') + packet = OutputStruct(data=packet_data, length=last_len*8, last=True) + packets.append(packet) + return packets + +async def reset_dut(dut, rst_len=10): + dut.rst.setimmediatevalue(0) + await ClockCycles(dut.clk, rst_len) + dut.rst.setimmediatevalue(1) + await ClockCycles(dut.clk, rst_len) + dut.rst.setimmediatevalue(0) + +def connect_xls_channel(dut, channel_name, xls_struct): + channel = XLSChannel(dut, channel_name, dut.clk, start_now=True) + monitor = XLSChannelMonitor(dut, channel_name, dut.clk, xls_struct) + + return (channel, monitor) + +def prepare_test_environment(dut): + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + scoreboard = Scoreboard(dut) + + memory_bus = connect_axi_bus(dut, "memory") + csr_bus = connect_axi_bus(dut, "csr") + axi_buses = { + "memory": memory_bus, + "csr": csr_bus + } + + notify = connect_xls_channel(dut, NOTIFY_CHANNEL, NotifyStruct) + output = connect_xls_channel(dut, OUTPUT_CHANNEL, OutputStruct) + xls_channels = { + "notify": notify, + "output": output + } + + cpu = AxiMaster(csr_bus, dut.clk, dut.rst) + + return (scoreboard, axi_buses, xls_channels, cpu) + +async def test_decoder(dut, seed, block_type, scoreboard, axi_buses, xls_channels, cpu): + memory_bus = axi_buses["memory"] + csr_bus = axi_buses["csr"] + (notify_channel, notify_monitor) = xls_channels[NOTIFY_CHANNEL] + (output_channel, output_monitor) = xls_channels[OUTPUT_CHANNEL] + + assert_notify = Event() + set_termination_event(notify_monitor, assert_notify, 1) + + mem_size = MAX_ENCODED_FRAME_SIZE_B + ibuf_addr = 0x0 + obuf_addr = mem_size // 2 + + #FIXME: use delete_on_close=False after moving to python 3.12 + with tempfile.NamedTemporaryFile(delete=False) as encoded: + await reset_dut(dut, 50) + + # Generate ZSTD frame to temporary file + GenerateFrame(seed, block_type, encoded.name) + + expected_decoded_frame = get_decoded_frame_bytes(encoded) + encoded.close() + expected_output_packets = generate_expected_output(expected_decoded_frame) + + assert_expected_output = Event() + set_termination_event(output_monitor, assert_expected_output, len(expected_output_packets)) + # Monitor stream output for packets with the decoded ZSTD frame + scoreboard.add_interface(output_monitor, expected_output_packets) + + # Initialise testbench memory with generated ZSTD frame + memory = AxiRamFromFile(bus=memory_bus, clock=dut.clk, reset=dut.rst, path=encoded.name, size=mem_size) + + await configure_decoder(cpu, ibuf_addr, obuf_addr) + await start_decoder(cpu) + await assert_expected_output.wait() + await wait_for_idle(cpu) + # TODO: Check decoded frame in memory under `obuf_addr` when ZstdDecoder + # will fully support memory output interface + + await assert_notify.wait() + await ClockCycles(dut.clk, 20) + +@cocotb.test(timeout_time=50, timeout_unit="ms") +async def zstd_csr_test(dut): + await test_csr(dut) + +@cocotb.test(timeout_time=50, timeout_unit="ms") +async def zstd_reset_test(dut): + await test_reset(dut) + +#FIXME: Rework testbench to decode multiple ZSTD frames in one test +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_raw_frames_test_1(dut): + block_type = BlockType.RAW + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 1, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_raw_frames_test_2(dut): + block_type = BlockType.RAW + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 2, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_raw_frames_test_3(dut): + block_type = BlockType.RAW + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 3, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_raw_frames_test_4(dut): + block_type = BlockType.RAW + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 4, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_raw_frames_test_5(dut): + block_type = BlockType.RAW + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 5, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_rle_frames_test_1(dut): + block_type = BlockType.RLE + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 1, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_rle_frames_test_2(dut): + block_type = BlockType.RLE + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 2, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_rle_frames_test_3(dut): + block_type = BlockType.RLE + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 3, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_rle_frames_test_4(dut): + block_type = BlockType.RLE + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 4, block_type, scoreboard, axi_buses, xls_channels, cpu) + +@cocotb.test(timeout_time=20000, timeout_unit="ms") +async def zstd_rle_frames_test_5(dut): + block_type = BlockType.RLE + (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 5, block_type, scoreboard, axi_buses, xls_channels, cpu) + +#@cocotb.test(timeout_time=20000, timeout_unit="ms") +#async def zstd_compressed_frames_test(dut): +# test_cases = 1 +# block_type = BlockType.COMPRESSED +# await test_decoder(dut, test_cases, block_type) +# +#@cocotb.test(timeout_time=20000, timeout_unit="ms") +#async def zstd_random_frames_test(dut): +# test_cases = 1 +# block_type = BlockType.RANDOM +# await test_decoder(dut, test_cases, block_type) + +if __name__ == "__main__": + toplevel = "zstd_dec_wrapper" + verilog_sources = [ + "xls/modules/zstd/zstd_dec.v", + "xls/modules/zstd/rtl/xls_fifo_wrapper.sv", + "xls/modules/zstd/rtl/zstd_dec_wrapper.sv", + "xls/modules/zstd/axi_interconnect_wrapper.v", + "xls/modules/zstd/axi_interconnect.v", + "xls/modules/zstd/arbiter.v", + "xls/modules/zstd/priority_encoder.v", + ] + test_module=[Path(__file__).stem] + run_test(toplevel, test_module, verilog_sources) From 6a7294fe98c95eb1da84c7c83e951b5b1b84bdaf Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Mon, 29 Dec 2025 09:42:38 +0100 Subject: [PATCH 008/159] Use frame generator in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Winkler Signed-off-by: Robert Winkler Signed-off-by: Krzysztof Obłonczek --- xls/modules/zstd/BUILD | 23 +++ xls/modules/zstd/cocotb/data_generator.py | 19 ++- xls/modules/zstd/zstd_dec_cocotb_test.py | 15 +- xls/modules/zstd/zstd_frame_dslx.py | 170 ++++++++++++++++++++++ 4 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 xls/modules/zstd/zstd_frame_dslx.py diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f44dc05365..974dc5d056 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1414,6 +1414,29 @@ xls_dslx_test( ], ) +py_binary( + name = "zstd_test_frames_generator", + srcs = ["zstd_frame_dslx.py"], + imports = ["."], + main = "zstd_frame_dslx.py", + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("zstandard"), + "//xls/common:runfiles", + "//xls/modules/zstd/cocotb:data_generator", + "@com_google_protobuf//:protobuf_python", + ], +) + +genrule( + name = "zstd_test_frames_generate", + srcs = [], + outs = ["zstd_frame_testcases.x"], + cmd = "$(location :zstd_test_frames_generator) --seed 36 -n 1 --btype COMPRESSED -o $@", + tools = [":zstd_test_frames_generator"], +) + zstd_dec_deps = [ ":axi_csr_accessor_dslx", ":block_header_dec_dslx", diff --git a/xls/modules/zstd/cocotb/data_generator.py b/xls/modules/zstd/cocotb/data_generator.py index 105a9a7ec7..72b60c5eee 100644 --- a/xls/modules/zstd/cocotb/data_generator.py +++ b/xls/modules/zstd/cocotb/data_generator.py @@ -17,6 +17,7 @@ from xls.common import runfiles import subprocess +import zstandard class BlockType(Enum): RAW = 0 @@ -24,6 +25,16 @@ class BlockType(Enum): COMPRESSED = 2 RANDOM = 3 + def __str__(self): + return self.name + + @staticmethod + def from_string(s): + try: + return BlockType[s] + except KeyError as e: + raise ValueError(str(e)) + def CallDecodecorpus(args): decodecorpus = Path(runfiles.get_path("decodecorpus", repository = "zstd")) cmd = args @@ -31,6 +42,10 @@ def CallDecodecorpus(args): cmd_concat = " ".join(cmd) subprocess.run(cmd_concat, shell=True, check=True) +def DecompressFrame(data): + dctx = zstandard.ZstdDecompressor() + return dctx.decompress(data) + def GenerateFrame(seed, btype, output_path): args = [] args.append("-s" + str(seed)) @@ -39,8 +54,8 @@ def GenerateFrame(seed, btype, output_path): args.append("--content-size") # Test payloads up to 16KB args.append("--max-content-size-log=14") - args.append("-p" + output_path); - args.append("-vvvvvvv"); + args.append("-p" + output_path) + args.append("-vvvvvvv") CallDecodecorpus(args) diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index ff9fd5afff..b696272811 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -29,15 +29,13 @@ from cocotbext.axi.axi_ram import AxiRam from cocotbext.axi.sparse_memory import SparseMemory -import zstandard - from xls.common import runfiles from xls.modules.zstd.cocotb.channel import ( XLSChannel, XLSChannelDriver, XLSChannelMonitor, ) -from xls.modules.zstd.cocotb.data_generator import GenerateFrame, BlockType +from xls.modules.zstd.cocotb.data_generator import GenerateFrame, DecompressFrame, BlockType from xls.modules.zstd.cocotb.memory import init_axi_mem, AxiRamFromFile from xls.modules.zstd.cocotb.utils import reset, run_test from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass @@ -132,15 +130,6 @@ def connect_axi_bus(dut, name=""): return AxiBus(bus_axi_write, bus_axi_read) -def get_decoded_frame_bytes(ifh): - dctx = zstandard.ZstdDecompressor() - return dctx.decompress(ifh.read()) - -def get_decoded_frame_buffer(ifh, address, memory=SparseMemory(size=MAX_ENCODED_FRAME_SIZE_B)): - dctx = zstandard.ZstdDecompressor() - memory.write(address, dctx.decompress(ifh.read())) - return memory - async def csr_write(cpu, csr, data): if type(data) is int: data = data.to_bytes(AXI_DATA_W_BYTES, byteorder='little') @@ -300,7 +289,7 @@ async def test_decoder(dut, seed, block_type, scoreboard, axi_buses, xls_channel # Generate ZSTD frame to temporary file GenerateFrame(seed, block_type, encoded.name) - expected_decoded_frame = get_decoded_frame_bytes(encoded) + expected_decoded_frame = DecompressFrame(encoded.read()) encoded.close() expected_output_packets = generate_expected_output(expected_decoded_frame) diff --git a/xls/modules/zstd/zstd_frame_dslx.py b/xls/modules/zstd/zstd_frame_dslx.py new file mode 100644 index 0000000000..65c5573677 --- /dev/null +++ b/xls/modules/zstd/zstd_frame_dslx.py @@ -0,0 +1,170 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import argparse +import math +import random +import tempfile +from pathlib import Path + +from xls.modules.zstd.cocotb.data_generator import ( + BlockType, + DecompressFrame, + GenerateFrame, +) + + +def GenerateTestData(seed, btype): + with tempfile.NamedTemporaryFile() as tmp: + GenerateFrame(seed, btype, tmp.name) + tmp.seek(0) + return tmp.read() + + +def Bytes2DSLX(frames, bytes_per_word, array_name): + frames_hex = [] + maxlen = max(len(frame) for frame in frames) + maxlen_size = math.ceil(maxlen / bytes_per_word) + bits_per_word = bytes_per_word * 8 + for i, frame in enumerate(frames): + frame_hex = [] + for i in range(0, len(frame), bytes_per_word): + # reverse byte order to make them little endian + word = bytes(reversed(frame[i : i + bytes_per_word])).hex() + frame_hex.append(f"uN[{bits_per_word}]:0x{word}") + + array_length = len(frame_hex) + if len(frame) < maxlen: + frame_hex += [f"uN[{bits_per_word}]:0x0", "..."] + + frame_array = ( + f"DataArray<{bits_per_word}, {maxlen_size}>{{\n" + f" length: u32:{len(frame)},\n" + f" array_length: u32:{array_length},\n" + f" data: uN[{bits_per_word}][{maxlen_size}]:[{', '.join(frame_hex)}]\n" + f"}}" + ) + frames_hex.append(frame_array) + + frames_str = ",\n".join(frames_hex) + frames_array = ( + f"pub const {array_name}:DataArray<\n" + f" u32:{bits_per_word},\n" + f" u32:{maxlen_size}\n" + f">[{len(frames_hex)}] = [{frames_str}];\n" + ) + return frames_array + + +def GenerateDataStruct(): + return ( + f"pub struct DataArray{{\n" + f" data: uN[BITS_PER_WORD][LENGTH],\n" + f" length: u32,\n" + f" array_length: u32\n" + f"}}\n" + ) + +def main2(): + parser = argparse.ArgumentParser() + parser.add_argument( + "input", + help="Filename of the decodecorpus input", + type=Path, + ) + parser.add_argument( + "output", + help="Filename of the DSLX output file", + type=Path, + ) + parser.add_argument( + "--bytes-per-word", + help="Width of a word in memory, in bytes", + type=int, + default=8, + ) + + args = parser.parse_args() + + with open(args.input, "rb") as fd: + byte_frames = [fd.read()] + + with open(args.output, "w") as dslx_output: + dslx_output.write(GenerateDataStruct()) + + dslx_frames = Bytes2DSLX(byte_frames, args.bytes_per_word, "FRAMES") + dslx_output.write(dslx_frames) + + byte_frames_decompressed = list(map(DecompressFrame, byte_frames)) + dslx_frames_decompressed = Bytes2DSLX( + byte_frames_decompressed, args.bytes_per_word, "DECOMPRESSED_FRAMES" + ) + dslx_output.write(dslx_frames_decompressed) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-n", help="Number of testcases to generate", type=int, default=1 + ) + parser.add_argument( + "--seed", help="Seed for the testcases generator", type=int, default=0 + ) + parser.add_argument( + "--btype", + help=( + "Block types allowed in the generated testcases. If multiple block types " + "are supplied, generated testcases will cycle through them" + ), + type=BlockType.from_string, + choices=list(BlockType), + default=BlockType.RANDOM, + nargs="+", + ) + parser.add_argument( + "-o", + "--output", + help="Filename of the DSLX output file", + type=Path, + default=Path("frames_test_data.x"), + ) + parser.add_argument( + "--bytes-per-word", + help="Width of a word in memory, in bytes", + type=int, + default=8, + ) + args = parser.parse_args() + + seed = random.seed(args.seed) + byte_frames = [ + GenerateTestData(random.randrange(2**32), args.btype[i % len(args.btype)]) + for i in range(args.n) + ] + with open(args.output, "w") as dslx_output: + dslx_output.write(GenerateDataStruct()) + + dslx_frames = Bytes2DSLX(byte_frames, args.bytes_per_word, "FRAMES") + dslx_output.write(dslx_frames) + + byte_frames_decompressed = list(map(DecompressFrame, byte_frames)) + dslx_frames_decompressed = Bytes2DSLX( + byte_frames_decompressed, args.bytes_per_word, "DECOMPRESSED_FRAMES" + ) + dslx_output.write(dslx_frames_decompressed) + + +if __name__ == "__main__": + main() From 8db2cc70561660ec848a3cdffc41c6f130781d43 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 13 May 2025 14:47:31 +0200 Subject: [PATCH 009/159] modules/zstd/zstd_dec: Write decoded data to the memory ZstdDecoder: * Cocotb tests: * Move third-party verilog modules (AXI Interconnect) to external directory * Replace AXI Interconnect with AXI Crossbar that handles simultaneous AXI Read and Write transactions * Add reference memory and fill it with expected data for comparison against testbench memory at the end of the decoding Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 35 - xls/modules/zstd/axi_interconnect.v | 988 ------------------ xls/modules/zstd/external/BUILD | 33 + xls/modules/zstd/{ => external}/arbiter.v | 0 xls/modules/zstd/external/axi_crossbar.v | 391 +++++++ xls/modules/zstd/external/axi_crossbar_addr.v | 418 ++++++++ xls/modules/zstd/external/axi_crossbar_rd.v | 569 ++++++++++ xls/modules/zstd/external/axi_crossbar_wr.v | 678 ++++++++++++ .../axi_crossbar_wrapper.v} | 200 +++- xls/modules/zstd/external/axi_register_rd.v | 530 ++++++++++ xls/modules/zstd/external/axi_register_wr.v | 691 ++++++++++++ .../zstd/{ => external}/priority_encoder.v | 0 xls/modules/zstd/zstd_dec_cocotb_test.py | 51 +- 13 files changed, 3511 insertions(+), 1073 deletions(-) delete mode 100644 xls/modules/zstd/axi_interconnect.v create mode 100644 xls/modules/zstd/external/BUILD rename xls/modules/zstd/{ => external}/arbiter.v (100%) create mode 100644 xls/modules/zstd/external/axi_crossbar.v create mode 100644 xls/modules/zstd/external/axi_crossbar_addr.v create mode 100644 xls/modules/zstd/external/axi_crossbar_rd.v create mode 100644 xls/modules/zstd/external/axi_crossbar_wr.v rename xls/modules/zstd/{axi_interconnect_wrapper.v => external/axi_crossbar_wrapper.v} (70%) create mode 100644 xls/modules/zstd/external/axi_register_rd.v create mode 100644 xls/modules/zstd/external/axi_register_wr.v rename xls/modules/zstd/{ => external}/priority_encoder.v (100%) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 974dc5d056..d39b49dd1e 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -3655,38 +3655,3 @@ xls_dslx_test( name = "refilling_shift_buffer_mux_dslx_test", library = ":refilling_shift_buffer_mux_dslx", ) - -py_test( - name = "zstd_dec_cocotb_test", - srcs = ["zstd_dec_cocotb_test.py"], - data = [ - ":arbiter.v", - ":axi_interconnect.v", - ":axi_interconnect_wrapper.v", - ":priority_encoder.v", - ":xls_fifo_wrapper.sv", - ":zstd_dec.v", - ":zstd_dec_wrapper.sv", - "@com_icarus_iverilog//:iverilog", - "@com_icarus_iverilog//:vvp", - ], - env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, - imports = ["."], - tags = ["manual"], - visibility = ["//xls:xls_users"], - deps = [ - requirement("cocotb"), - requirement("cocotbext-axi"), - requirement("pytest"), - requirement("zstandard"), - "//xls/common:runfiles", - "//xls/modules/zstd/cocotb:channel", - "//xls/modules/zstd/cocotb:data_generator", - "//xls/modules/zstd/cocotb:memory", - "//xls/modules/zstd/cocotb:utils", - "//xls/modules/zstd/cocotb:xlsstruct", - "@com_google_absl_py//absl:app", - "@com_google_absl_py//absl/flags", - "@com_google_protobuf//:protobuf_python", - ], -) diff --git a/xls/modules/zstd/axi_interconnect.v b/xls/modules/zstd/axi_interconnect.v deleted file mode 100644 index 14256ad7de..0000000000 --- a/xls/modules/zstd/axi_interconnect.v +++ /dev/null @@ -1,988 +0,0 @@ -/* - -Copyright (c) 2018 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * AXI4 interconnect - */ -module axi_interconnect # -( - // Number of AXI inputs (slave interfaces) - parameter S_COUNT = 4, - // Number of AXI outputs (master interfaces) - parameter M_COUNT = 4, - // Width of data bus in bits - parameter DATA_WIDTH = 32, - // Width of address bus in bits - parameter ADDR_WIDTH = 32, - // Width of wstrb (width of data bus in words) - parameter STRB_WIDTH = (DATA_WIDTH/8), - // Width of ID signal - parameter ID_WIDTH = 8, - // Propagate awuser signal - parameter AWUSER_ENABLE = 0, - // Width of awuser signal - parameter AWUSER_WIDTH = 1, - // Propagate wuser signal - parameter WUSER_ENABLE = 0, - // Width of wuser signal - parameter WUSER_WIDTH = 1, - // Propagate buser signal - parameter BUSER_ENABLE = 0, - // Width of buser signal - parameter BUSER_WIDTH = 1, - // Propagate aruser signal - parameter ARUSER_ENABLE = 0, - // Width of aruser signal - parameter ARUSER_WIDTH = 1, - // Propagate ruser signal - parameter RUSER_ENABLE = 0, - // Width of ruser signal - parameter RUSER_WIDTH = 1, - // Propagate ID field - parameter FORWARD_ID = 0, - // Number of regions per master interface - parameter M_REGIONS = 1, - // Master interface base addresses - // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits - // set to zero for default addressing based on M_ADDR_WIDTH - parameter M_BASE_ADDR = 0, - // Master interface address widths - // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits - parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, - // Read connections between interfaces - // M_COUNT concatenated fields of S_COUNT bits - parameter M_CONNECT_READ = {M_COUNT{{S_COUNT{1'b1}}}}, - // Write connections between interfaces - // M_COUNT concatenated fields of S_COUNT bits - parameter M_CONNECT_WRITE = {M_COUNT{{S_COUNT{1'b1}}}}, - // Secure master (fail operations based on awprot/arprot) - // M_COUNT bits - parameter M_SECURE = {M_COUNT{1'b0}} -) -( - input wire clk, - input wire rst, - - /* - * AXI slave interfaces - */ - input wire [S_COUNT*ID_WIDTH-1:0] s_axi_awid, - input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_awaddr, - input wire [S_COUNT*8-1:0] s_axi_awlen, - input wire [S_COUNT*3-1:0] s_axi_awsize, - input wire [S_COUNT*2-1:0] s_axi_awburst, - input wire [S_COUNT-1:0] s_axi_awlock, - input wire [S_COUNT*4-1:0] s_axi_awcache, - input wire [S_COUNT*3-1:0] s_axi_awprot, - input wire [S_COUNT*4-1:0] s_axi_awqos, - input wire [S_COUNT*AWUSER_WIDTH-1:0] s_axi_awuser, - input wire [S_COUNT-1:0] s_axi_awvalid, - output wire [S_COUNT-1:0] s_axi_awready, - input wire [S_COUNT*DATA_WIDTH-1:0] s_axi_wdata, - input wire [S_COUNT*STRB_WIDTH-1:0] s_axi_wstrb, - input wire [S_COUNT-1:0] s_axi_wlast, - input wire [S_COUNT*WUSER_WIDTH-1:0] s_axi_wuser, - input wire [S_COUNT-1:0] s_axi_wvalid, - output wire [S_COUNT-1:0] s_axi_wready, - output wire [S_COUNT*ID_WIDTH-1:0] s_axi_bid, - output wire [S_COUNT*2-1:0] s_axi_bresp, - output wire [S_COUNT*BUSER_WIDTH-1:0] s_axi_buser, - output wire [S_COUNT-1:0] s_axi_bvalid, - input wire [S_COUNT-1:0] s_axi_bready, - input wire [S_COUNT*ID_WIDTH-1:0] s_axi_arid, - input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_araddr, - input wire [S_COUNT*8-1:0] s_axi_arlen, - input wire [S_COUNT*3-1:0] s_axi_arsize, - input wire [S_COUNT*2-1:0] s_axi_arburst, - input wire [S_COUNT-1:0] s_axi_arlock, - input wire [S_COUNT*4-1:0] s_axi_arcache, - input wire [S_COUNT*3-1:0] s_axi_arprot, - input wire [S_COUNT*4-1:0] s_axi_arqos, - input wire [S_COUNT*ARUSER_WIDTH-1:0] s_axi_aruser, - input wire [S_COUNT-1:0] s_axi_arvalid, - output wire [S_COUNT-1:0] s_axi_arready, - output wire [S_COUNT*ID_WIDTH-1:0] s_axi_rid, - output wire [S_COUNT*DATA_WIDTH-1:0] s_axi_rdata, - output wire [S_COUNT*2-1:0] s_axi_rresp, - output wire [S_COUNT-1:0] s_axi_rlast, - output wire [S_COUNT*RUSER_WIDTH-1:0] s_axi_ruser, - output wire [S_COUNT-1:0] s_axi_rvalid, - input wire [S_COUNT-1:0] s_axi_rready, - - /* - * AXI master interfaces - */ - output wire [M_COUNT*ID_WIDTH-1:0] m_axi_awid, - output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_awaddr, - output wire [M_COUNT*8-1:0] m_axi_awlen, - output wire [M_COUNT*3-1:0] m_axi_awsize, - output wire [M_COUNT*2-1:0] m_axi_awburst, - output wire [M_COUNT-1:0] m_axi_awlock, - output wire [M_COUNT*4-1:0] m_axi_awcache, - output wire [M_COUNT*3-1:0] m_axi_awprot, - output wire [M_COUNT*4-1:0] m_axi_awqos, - output wire [M_COUNT*4-1:0] m_axi_awregion, - output wire [M_COUNT*AWUSER_WIDTH-1:0] m_axi_awuser, - output wire [M_COUNT-1:0] m_axi_awvalid, - input wire [M_COUNT-1:0] m_axi_awready, - output wire [M_COUNT*DATA_WIDTH-1:0] m_axi_wdata, - output wire [M_COUNT*STRB_WIDTH-1:0] m_axi_wstrb, - output wire [M_COUNT-1:0] m_axi_wlast, - output wire [M_COUNT*WUSER_WIDTH-1:0] m_axi_wuser, - output wire [M_COUNT-1:0] m_axi_wvalid, - input wire [M_COUNT-1:0] m_axi_wready, - input wire [M_COUNT*ID_WIDTH-1:0] m_axi_bid, - input wire [M_COUNT*2-1:0] m_axi_bresp, - input wire [M_COUNT*BUSER_WIDTH-1:0] m_axi_buser, - input wire [M_COUNT-1:0] m_axi_bvalid, - output wire [M_COUNT-1:0] m_axi_bready, - output wire [M_COUNT*ID_WIDTH-1:0] m_axi_arid, - output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_araddr, - output wire [M_COUNT*8-1:0] m_axi_arlen, - output wire [M_COUNT*3-1:0] m_axi_arsize, - output wire [M_COUNT*2-1:0] m_axi_arburst, - output wire [M_COUNT-1:0] m_axi_arlock, - output wire [M_COUNT*4-1:0] m_axi_arcache, - output wire [M_COUNT*3-1:0] m_axi_arprot, - output wire [M_COUNT*4-1:0] m_axi_arqos, - output wire [M_COUNT*4-1:0] m_axi_arregion, - output wire [M_COUNT*ARUSER_WIDTH-1:0] m_axi_aruser, - output wire [M_COUNT-1:0] m_axi_arvalid, - input wire [M_COUNT-1:0] m_axi_arready, - input wire [M_COUNT*ID_WIDTH-1:0] m_axi_rid, - input wire [M_COUNT*DATA_WIDTH-1:0] m_axi_rdata, - input wire [M_COUNT*2-1:0] m_axi_rresp, - input wire [M_COUNT-1:0] m_axi_rlast, - input wire [M_COUNT*RUSER_WIDTH-1:0] m_axi_ruser, - input wire [M_COUNT-1:0] m_axi_rvalid, - output wire [M_COUNT-1:0] m_axi_rready -); - -parameter CL_S_COUNT = $clog2(S_COUNT); -parameter CL_M_COUNT = $clog2(M_COUNT); - -parameter AUSER_WIDTH = AWUSER_WIDTH > ARUSER_WIDTH ? AWUSER_WIDTH : ARUSER_WIDTH; - -// default address computation -function [M_COUNT*M_REGIONS*ADDR_WIDTH-1:0] calcBaseAddrs(input [31:0] dummy); - integer i; - reg [ADDR_WIDTH-1:0] base; - reg [ADDR_WIDTH-1:0] width; - reg [ADDR_WIDTH-1:0] size; - reg [ADDR_WIDTH-1:0] mask; - begin - calcBaseAddrs = {M_COUNT*M_REGIONS*ADDR_WIDTH{1'b0}}; - base = 0; - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - width = M_ADDR_WIDTH[i*32 +: 32]; - mask = {ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - width); - size = mask + 1; - if (width > 0) begin - if ((base & mask) != 0) begin - base = base + size - (base & mask); // align - end - calcBaseAddrs[i * ADDR_WIDTH +: ADDR_WIDTH] = base; - base = base + size; // increment - end - end - end -endfunction - -parameter M_BASE_ADDR_INT = M_BASE_ADDR ? M_BASE_ADDR : calcBaseAddrs(0); - -integer i, j; - -// check configuration -initial begin - if (M_REGIONS < 1 || M_REGIONS > 16) begin - $error("Error: M_REGIONS must be between 1 and 16 (instance %m)"); - $finish; - end - - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - if (M_ADDR_WIDTH[i*32 +: 32] && (M_ADDR_WIDTH[i*32 +: 32] < 12 || M_ADDR_WIDTH[i*32 +: 32] > ADDR_WIDTH)) begin - $error("Error: address width out of range (instance %m)"); - $finish; - end - end - - $display("Addressing configuration for axi_interconnect instance %m"); - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - if (M_ADDR_WIDTH[i*32 +: 32]) begin - $display("%2d (%2d): %x / %02d -- %x-%x", - i/M_REGIONS, i%M_REGIONS, - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], - M_ADDR_WIDTH[i*32 +: 32], - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) - ); - end - end - - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - if ((M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & (2**M_ADDR_WIDTH[i*32 +: 32]-1)) != 0) begin - $display("Region not aligned:"); - $display("%2d (%2d): %x / %2d -- %x-%x", - i/M_REGIONS, i%M_REGIONS, - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], - M_ADDR_WIDTH[i*32 +: 32], - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) - ); - $error("Error: address range not aligned (instance %m)"); - $finish; - end - end - - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - for (j = i+1; j < M_COUNT*M_REGIONS; j = j + 1) begin - if (M_ADDR_WIDTH[i*32 +: 32] && M_ADDR_WIDTH[j*32 +: 32]) begin - if (((M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32])) <= (M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[j*32 +: 32])))) - && ((M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[j*32 +: 32])) <= (M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32]))))) begin - $display("Overlapping regions:"); - $display("%2d (%2d): %x / %2d -- %x-%x", - i/M_REGIONS, i%M_REGIONS, - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], - M_ADDR_WIDTH[i*32 +: 32], - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) - ); - $display("%2d (%2d): %x / %2d -- %x-%x", - j/M_REGIONS, j%M_REGIONS, - M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH], - M_ADDR_WIDTH[j*32 +: 32], - M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[j*32 +: 32]), - M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[j*32 +: 32])) - ); - $error("Error: address ranges overlap (instance %m)"); - $finish; - end - end - end - end -end - -localparam [2:0] - STATE_IDLE = 3'd0, - STATE_DECODE = 3'd1, - STATE_WRITE = 3'd2, - STATE_WRITE_RESP = 3'd3, - STATE_WRITE_DROP = 3'd4, - STATE_READ = 3'd5, - STATE_READ_DROP = 3'd6, - STATE_WAIT_IDLE = 3'd7; - -reg [2:0] state_reg = STATE_IDLE, state_next; - -reg match; - -reg [CL_M_COUNT-1:0] m_select_reg = 2'd0, m_select_next; -reg [ID_WIDTH-1:0] axi_id_reg = {ID_WIDTH{1'b0}}, axi_id_next; -reg [ADDR_WIDTH-1:0] axi_addr_reg = {ADDR_WIDTH{1'b0}}, axi_addr_next; -reg axi_addr_valid_reg = 1'b0, axi_addr_valid_next; -reg [7:0] axi_len_reg = 8'd0, axi_len_next; -reg [2:0] axi_size_reg = 3'd0, axi_size_next; -reg [1:0] axi_burst_reg = 2'd0, axi_burst_next; -reg axi_lock_reg = 1'b0, axi_lock_next; -reg [3:0] axi_cache_reg = 4'd0, axi_cache_next; -reg [2:0] axi_prot_reg = 3'b000, axi_prot_next; -reg [3:0] axi_qos_reg = 4'd0, axi_qos_next; -reg [3:0] axi_region_reg = 4'd0, axi_region_next; -reg [AUSER_WIDTH-1:0] axi_auser_reg = {AUSER_WIDTH{1'b0}}, axi_auser_next; -reg [1:0] axi_bresp_reg = 2'b00, axi_bresp_next; -reg [BUSER_WIDTH-1:0] axi_buser_reg = {BUSER_WIDTH{1'b0}}, axi_buser_next; - -reg [S_COUNT-1:0] s_axi_awready_reg = 0, s_axi_awready_next; -reg [S_COUNT-1:0] s_axi_wready_reg = 0, s_axi_wready_next; -reg [S_COUNT-1:0] s_axi_bvalid_reg = 0, s_axi_bvalid_next; -reg [S_COUNT-1:0] s_axi_arready_reg = 0, s_axi_arready_next; - -reg [M_COUNT-1:0] m_axi_awvalid_reg = 0, m_axi_awvalid_next; -reg [M_COUNT-1:0] m_axi_bready_reg = 0, m_axi_bready_next; -reg [M_COUNT-1:0] m_axi_arvalid_reg = 0, m_axi_arvalid_next; -reg [M_COUNT-1:0] m_axi_rready_reg = 0, m_axi_rready_next; - -// internal datapath -reg [ID_WIDTH-1:0] s_axi_rid_int; -reg [DATA_WIDTH-1:0] s_axi_rdata_int; -reg [1:0] s_axi_rresp_int; -reg s_axi_rlast_int; -reg [RUSER_WIDTH-1:0] s_axi_ruser_int; -reg s_axi_rvalid_int; -reg s_axi_rready_int_reg = 1'b0; -wire s_axi_rready_int_early; - -reg [DATA_WIDTH-1:0] m_axi_wdata_int; -reg [STRB_WIDTH-1:0] m_axi_wstrb_int; -reg m_axi_wlast_int; -reg [WUSER_WIDTH-1:0] m_axi_wuser_int; -reg m_axi_wvalid_int; -reg m_axi_wready_int_reg = 1'b0; -wire m_axi_wready_int_early; - -assign s_axi_awready = s_axi_awready_reg; -assign s_axi_wready = s_axi_wready_reg; -assign s_axi_bid = {S_COUNT{axi_id_reg}}; -assign s_axi_bresp = {S_COUNT{axi_bresp_reg}}; -assign s_axi_buser = {S_COUNT{BUSER_ENABLE ? axi_buser_reg : {BUSER_WIDTH{1'b0}}}}; -assign s_axi_bvalid = s_axi_bvalid_reg; -assign s_axi_arready = s_axi_arready_reg; - -assign m_axi_awid = {M_COUNT{FORWARD_ID ? axi_id_reg : {ID_WIDTH{1'b0}}}}; -assign m_axi_awaddr = {M_COUNT{axi_addr_reg}}; -assign m_axi_awlen = {M_COUNT{axi_len_reg}}; -assign m_axi_awsize = {M_COUNT{axi_size_reg}}; -assign m_axi_awburst = {M_COUNT{axi_burst_reg}}; -assign m_axi_awlock = {M_COUNT{axi_lock_reg}}; -assign m_axi_awcache = {M_COUNT{axi_cache_reg}}; -assign m_axi_awprot = {M_COUNT{axi_prot_reg}}; -assign m_axi_awqos = {M_COUNT{axi_qos_reg}}; -assign m_axi_awregion = {M_COUNT{axi_region_reg}}; -assign m_axi_awuser = {M_COUNT{AWUSER_ENABLE ? axi_auser_reg[AWUSER_WIDTH-1:0] : {AWUSER_WIDTH{1'b0}}}}; -assign m_axi_awvalid = m_axi_awvalid_reg; -assign m_axi_bready = m_axi_bready_reg; -assign m_axi_arid = {M_COUNT{FORWARD_ID ? axi_id_reg : {ID_WIDTH{1'b0}}}}; -assign m_axi_araddr = {M_COUNT{axi_addr_reg}}; -assign m_axi_arlen = {M_COUNT{axi_len_reg}}; -assign m_axi_arsize = {M_COUNT{axi_size_reg}}; -assign m_axi_arburst = {M_COUNT{axi_burst_reg}}; -assign m_axi_arlock = {M_COUNT{axi_lock_reg}}; -assign m_axi_arcache = {M_COUNT{axi_cache_reg}}; -assign m_axi_arprot = {M_COUNT{axi_prot_reg}}; -assign m_axi_arqos = {M_COUNT{axi_qos_reg}}; -assign m_axi_arregion = {M_COUNT{axi_region_reg}}; -assign m_axi_aruser = {M_COUNT{ARUSER_ENABLE ? axi_auser_reg[ARUSER_WIDTH-1:0] : {ARUSER_WIDTH{1'b0}}}}; -assign m_axi_arvalid = m_axi_arvalid_reg; -assign m_axi_rready = m_axi_rready_reg; - -// slave side mux -wire [(CL_S_COUNT > 0 ? CL_S_COUNT-1 : 0):0] s_select; - -wire [ID_WIDTH-1:0] current_s_axi_awid = s_axi_awid[s_select*ID_WIDTH +: ID_WIDTH]; -wire [ADDR_WIDTH-1:0] current_s_axi_awaddr = s_axi_awaddr[s_select*ADDR_WIDTH +: ADDR_WIDTH]; -wire [7:0] current_s_axi_awlen = s_axi_awlen[s_select*8 +: 8]; -wire [2:0] current_s_axi_awsize = s_axi_awsize[s_select*3 +: 3]; -wire [1:0] current_s_axi_awburst = s_axi_awburst[s_select*2 +: 2]; -wire current_s_axi_awlock = s_axi_awlock[s_select]; -wire [3:0] current_s_axi_awcache = s_axi_awcache[s_select*4 +: 4]; -wire [2:0] current_s_axi_awprot = s_axi_awprot[s_select*3 +: 3]; -wire [3:0] current_s_axi_awqos = s_axi_awqos[s_select*4 +: 4]; -wire [AWUSER_WIDTH-1:0] current_s_axi_awuser = s_axi_awuser[s_select*AWUSER_WIDTH +: AWUSER_WIDTH]; -wire current_s_axi_awvalid = s_axi_awvalid[s_select]; -wire current_s_axi_awready = s_axi_awready[s_select]; -wire [DATA_WIDTH-1:0] current_s_axi_wdata = s_axi_wdata[s_select*DATA_WIDTH +: DATA_WIDTH]; -wire [STRB_WIDTH-1:0] current_s_axi_wstrb = s_axi_wstrb[s_select*STRB_WIDTH +: STRB_WIDTH]; -wire current_s_axi_wlast = s_axi_wlast[s_select]; -wire [WUSER_WIDTH-1:0] current_s_axi_wuser = s_axi_wuser[s_select*WUSER_WIDTH +: WUSER_WIDTH]; -wire current_s_axi_wvalid = s_axi_wvalid[s_select]; -wire current_s_axi_wready = s_axi_wready[s_select]; -wire [ID_WIDTH-1:0] current_s_axi_bid = s_axi_bid[s_select*ID_WIDTH +: ID_WIDTH]; -wire [1:0] current_s_axi_bresp = s_axi_bresp[s_select*2 +: 2]; -wire [BUSER_WIDTH-1:0] current_s_axi_buser = s_axi_buser[s_select*BUSER_WIDTH +: BUSER_WIDTH]; -wire current_s_axi_bvalid = s_axi_bvalid[s_select]; -wire current_s_axi_bready = s_axi_bready[s_select]; -wire [ID_WIDTH-1:0] current_s_axi_arid = s_axi_arid[s_select*ID_WIDTH +: ID_WIDTH]; -wire [ADDR_WIDTH-1:0] current_s_axi_araddr = s_axi_araddr[s_select*ADDR_WIDTH +: ADDR_WIDTH]; -wire [7:0] current_s_axi_arlen = s_axi_arlen[s_select*8 +: 8]; -wire [2:0] current_s_axi_arsize = s_axi_arsize[s_select*3 +: 3]; -wire [1:0] current_s_axi_arburst = s_axi_arburst[s_select*2 +: 2]; -wire current_s_axi_arlock = s_axi_arlock[s_select]; -wire [3:0] current_s_axi_arcache = s_axi_arcache[s_select*4 +: 4]; -wire [2:0] current_s_axi_arprot = s_axi_arprot[s_select*3 +: 3]; -wire [3:0] current_s_axi_arqos = s_axi_arqos[s_select*4 +: 4]; -wire [ARUSER_WIDTH-1:0] current_s_axi_aruser = s_axi_aruser[s_select*ARUSER_WIDTH +: ARUSER_WIDTH]; -wire current_s_axi_arvalid = s_axi_arvalid[s_select]; -wire current_s_axi_arready = s_axi_arready[s_select]; -wire [ID_WIDTH-1:0] current_s_axi_rid = s_axi_rid[s_select*ID_WIDTH +: ID_WIDTH]; -wire [DATA_WIDTH-1:0] current_s_axi_rdata = s_axi_rdata[s_select*DATA_WIDTH +: DATA_WIDTH]; -wire [1:0] current_s_axi_rresp = s_axi_rresp[s_select*2 +: 2]; -wire current_s_axi_rlast = s_axi_rlast[s_select]; -wire [RUSER_WIDTH-1:0] current_s_axi_ruser = s_axi_ruser[s_select*RUSER_WIDTH +: RUSER_WIDTH]; -wire current_s_axi_rvalid = s_axi_rvalid[s_select]; -wire current_s_axi_rready = s_axi_rready[s_select]; - -// master side mux -wire [ID_WIDTH-1:0] current_m_axi_awid = m_axi_awid[m_select_reg*ID_WIDTH +: ID_WIDTH]; -wire [ADDR_WIDTH-1:0] current_m_axi_awaddr = m_axi_awaddr[m_select_reg*ADDR_WIDTH +: ADDR_WIDTH]; -wire [7:0] current_m_axi_awlen = m_axi_awlen[m_select_reg*8 +: 8]; -wire [2:0] current_m_axi_awsize = m_axi_awsize[m_select_reg*3 +: 3]; -wire [1:0] current_m_axi_awburst = m_axi_awburst[m_select_reg*2 +: 2]; -wire current_m_axi_awlock = m_axi_awlock[m_select_reg]; -wire [3:0] current_m_axi_awcache = m_axi_awcache[m_select_reg*4 +: 4]; -wire [2:0] current_m_axi_awprot = m_axi_awprot[m_select_reg*3 +: 3]; -wire [3:0] current_m_axi_awqos = m_axi_awqos[m_select_reg*4 +: 4]; -wire [3:0] current_m_axi_awregion = m_axi_awregion[m_select_reg*4 +: 4]; -wire [AWUSER_WIDTH-1:0] current_m_axi_awuser = m_axi_awuser[m_select_reg*AWUSER_WIDTH +: AWUSER_WIDTH]; -wire current_m_axi_awvalid = m_axi_awvalid[m_select_reg]; -wire current_m_axi_awready = m_axi_awready[m_select_reg]; -wire [DATA_WIDTH-1:0] current_m_axi_wdata = m_axi_wdata[m_select_reg*DATA_WIDTH +: DATA_WIDTH]; -wire [STRB_WIDTH-1:0] current_m_axi_wstrb = m_axi_wstrb[m_select_reg*STRB_WIDTH +: STRB_WIDTH]; -wire current_m_axi_wlast = m_axi_wlast[m_select_reg]; -wire [WUSER_WIDTH-1:0] current_m_axi_wuser = m_axi_wuser[m_select_reg*WUSER_WIDTH +: WUSER_WIDTH]; -wire current_m_axi_wvalid = m_axi_wvalid[m_select_reg]; -wire current_m_axi_wready = m_axi_wready[m_select_reg]; -wire [ID_WIDTH-1:0] current_m_axi_bid = m_axi_bid[m_select_reg*ID_WIDTH +: ID_WIDTH]; -wire [1:0] current_m_axi_bresp = m_axi_bresp[m_select_reg*2 +: 2]; -wire [BUSER_WIDTH-1:0] current_m_axi_buser = m_axi_buser[m_select_reg*BUSER_WIDTH +: BUSER_WIDTH]; -wire current_m_axi_bvalid = m_axi_bvalid[m_select_reg]; -wire current_m_axi_bready = m_axi_bready[m_select_reg]; -wire [ID_WIDTH-1:0] current_m_axi_arid = m_axi_arid[m_select_reg*ID_WIDTH +: ID_WIDTH]; -wire [ADDR_WIDTH-1:0] current_m_axi_araddr = m_axi_araddr[m_select_reg*ADDR_WIDTH +: ADDR_WIDTH]; -wire [7:0] current_m_axi_arlen = m_axi_arlen[m_select_reg*8 +: 8]; -wire [2:0] current_m_axi_arsize = m_axi_arsize[m_select_reg*3 +: 3]; -wire [1:0] current_m_axi_arburst = m_axi_arburst[m_select_reg*2 +: 2]; -wire current_m_axi_arlock = m_axi_arlock[m_select_reg]; -wire [3:0] current_m_axi_arcache = m_axi_arcache[m_select_reg*4 +: 4]; -wire [2:0] current_m_axi_arprot = m_axi_arprot[m_select_reg*3 +: 3]; -wire [3:0] current_m_axi_arqos = m_axi_arqos[m_select_reg*4 +: 4]; -wire [3:0] current_m_axi_arregion = m_axi_arregion[m_select_reg*4 +: 4]; -wire [ARUSER_WIDTH-1:0] current_m_axi_aruser = m_axi_aruser[m_select_reg*ARUSER_WIDTH +: ARUSER_WIDTH]; -wire current_m_axi_arvalid = m_axi_arvalid[m_select_reg]; -wire current_m_axi_arready = m_axi_arready[m_select_reg]; -wire [ID_WIDTH-1:0] current_m_axi_rid = m_axi_rid[m_select_reg*ID_WIDTH +: ID_WIDTH]; -wire [DATA_WIDTH-1:0] current_m_axi_rdata = m_axi_rdata[m_select_reg*DATA_WIDTH +: DATA_WIDTH]; -wire [1:0] current_m_axi_rresp = m_axi_rresp[m_select_reg*2 +: 2]; -wire current_m_axi_rlast = m_axi_rlast[m_select_reg]; -wire [RUSER_WIDTH-1:0] current_m_axi_ruser = m_axi_ruser[m_select_reg*RUSER_WIDTH +: RUSER_WIDTH]; -wire current_m_axi_rvalid = m_axi_rvalid[m_select_reg]; -wire current_m_axi_rready = m_axi_rready[m_select_reg]; - -// arbiter instance -wire [S_COUNT*2-1:0] request; -wire [S_COUNT*2-1:0] acknowledge; -wire [S_COUNT*2-1:0] grant; -wire grant_valid; -wire [CL_S_COUNT:0] grant_encoded; - -wire read = grant_encoded[0]; -assign s_select = grant_encoded >> 1; - -arbiter #( - .PORTS(S_COUNT*2), - .ARB_TYPE_ROUND_ROBIN(1), - .ARB_BLOCK(1), - .ARB_BLOCK_ACK(1), - .ARB_LSB_HIGH_PRIORITY(1) -) -arb_inst ( - .clk(clk), - .rst(rst), - .request(request), - .acknowledge(acknowledge), - .grant(grant), - .grant_valid(grant_valid), - .grant_encoded(grant_encoded) -); - -genvar n; - -// request generation -generate -for (n = 0; n < S_COUNT; n = n + 1) begin - assign request[2*n] = s_axi_awvalid[n]; - assign request[2*n+1] = s_axi_arvalid[n]; -end -endgenerate - -// acknowledge generation -generate -for (n = 0; n < S_COUNT; n = n + 1) begin - assign acknowledge[2*n] = grant[2*n] && s_axi_bvalid[n] && s_axi_bready[n]; - assign acknowledge[2*n+1] = grant[2*n+1] && s_axi_rvalid[n] && s_axi_rready[n] && s_axi_rlast[n]; -end -endgenerate - -always @* begin - state_next = STATE_IDLE; - - match = 1'b0; - - m_select_next = m_select_reg; - axi_id_next = axi_id_reg; - axi_addr_next = axi_addr_reg; - axi_addr_valid_next = axi_addr_valid_reg; - axi_len_next = axi_len_reg; - axi_size_next = axi_size_reg; - axi_burst_next = axi_burst_reg; - axi_lock_next = axi_lock_reg; - axi_cache_next = axi_cache_reg; - axi_prot_next = axi_prot_reg; - axi_qos_next = axi_qos_reg; - axi_region_next = axi_region_reg; - axi_auser_next = axi_auser_reg; - axi_bresp_next = axi_bresp_reg; - axi_buser_next = axi_buser_reg; - - s_axi_awready_next = 0; - s_axi_wready_next = 0; - s_axi_bvalid_next = s_axi_bvalid_reg & ~s_axi_bready; - s_axi_arready_next = 0; - - m_axi_awvalid_next = m_axi_awvalid_reg & ~m_axi_awready; - m_axi_bready_next = 0; - m_axi_arvalid_next = m_axi_arvalid_reg & ~m_axi_arready; - m_axi_rready_next = 0; - - s_axi_rid_int = axi_id_reg; - s_axi_rdata_int = current_m_axi_rdata; - s_axi_rresp_int = current_m_axi_rresp; - s_axi_rlast_int = current_m_axi_rlast; - s_axi_ruser_int = current_m_axi_ruser; - s_axi_rvalid_int = 1'b0; - - m_axi_wdata_int = current_s_axi_wdata; - m_axi_wstrb_int = current_s_axi_wstrb; - m_axi_wlast_int = current_s_axi_wlast; - m_axi_wuser_int = current_s_axi_wuser; - m_axi_wvalid_int = 1'b0; - - case (state_reg) - STATE_IDLE: begin - // idle state; wait for arbitration - - if (grant_valid) begin - - axi_addr_valid_next = 1'b1; - - if (read) begin - // reading - axi_addr_next = current_s_axi_araddr; - axi_prot_next = current_s_axi_arprot; - axi_id_next = current_s_axi_arid; - axi_addr_next = current_s_axi_araddr; - axi_len_next = current_s_axi_arlen; - axi_size_next = current_s_axi_arsize; - axi_burst_next = current_s_axi_arburst; - axi_lock_next = current_s_axi_arlock; - axi_cache_next = current_s_axi_arcache; - axi_prot_next = current_s_axi_arprot; - axi_qos_next = current_s_axi_arqos; - axi_auser_next = current_s_axi_aruser; - s_axi_arready_next[s_select] = 1'b1; - end else begin - // writing - axi_addr_next = current_s_axi_awaddr; - axi_prot_next = current_s_axi_awprot; - axi_id_next = current_s_axi_awid; - axi_addr_next = current_s_axi_awaddr; - axi_len_next = current_s_axi_awlen; - axi_size_next = current_s_axi_awsize; - axi_burst_next = current_s_axi_awburst; - axi_lock_next = current_s_axi_awlock; - axi_cache_next = current_s_axi_awcache; - axi_prot_next = current_s_axi_awprot; - axi_qos_next = current_s_axi_awqos; - axi_auser_next = current_s_axi_awuser; - s_axi_awready_next[s_select] = 1'b1; - end - - state_next = STATE_DECODE; - end else begin - state_next = STATE_IDLE; - end - end - STATE_DECODE: begin - // decode state; determine master interface - - match = 1'b0; - for (i = 0; i < M_COUNT; i = i + 1) begin - for (j = 0; j < M_REGIONS; j = j + 1) begin - if (M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32] && (!M_SECURE[i] || !axi_prot_reg[1]) && ((read ? M_CONNECT_READ : M_CONNECT_WRITE) & (1 << (s_select+i*S_COUNT))) && (axi_addr_reg >> M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32]) == (M_BASE_ADDR_INT[(i*M_REGIONS+j)*ADDR_WIDTH +: ADDR_WIDTH] >> M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32])) begin - m_select_next = i; - axi_region_next = j; - match = 1'b1; - end - end - end - - if (match) begin - if (read) begin - // reading - m_axi_rready_next[m_select_reg] = s_axi_rready_int_early; - state_next = STATE_READ; - end else begin - // writing - s_axi_wready_next[s_select] = m_axi_wready_int_early; - state_next = STATE_WRITE; - end - end else begin - // no match; return decode error - if (read) begin - // reading - state_next = STATE_READ_DROP; - end else begin - // writing - axi_bresp_next = 2'b11; - s_axi_wready_next[s_select] = 1'b1; - state_next = STATE_WRITE_DROP; - end - end - end - STATE_WRITE: begin - // write state; store and forward write data - s_axi_wready_next[s_select] = m_axi_wready_int_early; - - if (axi_addr_valid_reg) begin - m_axi_awvalid_next[m_select_reg] = 1'b1; - end - axi_addr_valid_next = 1'b0; - - if (current_s_axi_wready && current_s_axi_wvalid) begin - m_axi_wdata_int = current_s_axi_wdata; - m_axi_wstrb_int = current_s_axi_wstrb; - m_axi_wlast_int = current_s_axi_wlast; - m_axi_wuser_int = current_s_axi_wuser; - m_axi_wvalid_int = 1'b1; - - if (current_s_axi_wlast) begin - s_axi_wready_next[s_select] = 1'b0; - m_axi_bready_next[m_select_reg] = 1'b1; - state_next = STATE_WRITE_RESP; - end else begin - state_next = STATE_WRITE; - end - end else begin - state_next = STATE_WRITE; - end - end - STATE_WRITE_RESP: begin - // write response state; store and forward write response - m_axi_bready_next[m_select_reg] = 1'b1; - - if (current_m_axi_bready && current_m_axi_bvalid) begin - m_axi_bready_next[m_select_reg] = 1'b0; - axi_bresp_next = current_m_axi_bresp; - s_axi_bvalid_next[s_select] = 1'b1; - state_next = STATE_WAIT_IDLE; - end else begin - state_next = STATE_WRITE_RESP; - end - end - STATE_WRITE_DROP: begin - // write drop state; drop write data - s_axi_wready_next[s_select] = 1'b1; - - axi_addr_valid_next = 1'b0; - - if (current_s_axi_wready && current_s_axi_wvalid && current_s_axi_wlast) begin - s_axi_wready_next[s_select] = 1'b0; - s_axi_bvalid_next[s_select] = 1'b1; - state_next = STATE_WAIT_IDLE; - end else begin - state_next = STATE_WRITE_DROP; - end - end - STATE_READ: begin - // read state; store and forward read response - m_axi_rready_next[m_select_reg] = s_axi_rready_int_early; - - if (axi_addr_valid_reg) begin - m_axi_arvalid_next[m_select_reg] = 1'b1; - end - axi_addr_valid_next = 1'b0; - - if (current_m_axi_rready && current_m_axi_rvalid) begin - s_axi_rid_int = axi_id_reg; - s_axi_rdata_int = current_m_axi_rdata; - s_axi_rresp_int = current_m_axi_rresp; - s_axi_rlast_int = current_m_axi_rlast; - s_axi_ruser_int = current_m_axi_ruser; - s_axi_rvalid_int = 1'b1; - - if (current_m_axi_rlast) begin - m_axi_rready_next[m_select_reg] = 1'b0; - state_next = STATE_WAIT_IDLE; - end else begin - state_next = STATE_READ; - end - end else begin - state_next = STATE_READ; - end - end - STATE_READ_DROP: begin - // read drop state; generate decode error read response - - s_axi_rid_int = axi_id_reg; - s_axi_rdata_int = {DATA_WIDTH{1'b0}}; - s_axi_rresp_int = 2'b11; - s_axi_rlast_int = axi_len_reg == 0; - s_axi_ruser_int = {RUSER_WIDTH{1'b0}}; - s_axi_rvalid_int = 1'b1; - - if (s_axi_rready_int_reg) begin - axi_len_next = axi_len_reg - 1; - if (axi_len_reg == 0) begin - state_next = STATE_WAIT_IDLE; - end else begin - state_next = STATE_READ_DROP; - end - end else begin - state_next = STATE_READ_DROP; - end - end - STATE_WAIT_IDLE: begin - // wait for idle state; wait untl grant valid is deasserted - - if (!grant_valid || acknowledge) begin - state_next = STATE_IDLE; - end else begin - state_next = STATE_WAIT_IDLE; - end - end - endcase -end - -always @(posedge clk) begin - if (rst) begin - state_reg <= STATE_IDLE; - - s_axi_awready_reg <= 0; - s_axi_wready_reg <= 0; - s_axi_bvalid_reg <= 0; - s_axi_arready_reg <= 0; - - m_axi_awvalid_reg <= 0; - m_axi_bready_reg <= 0; - m_axi_arvalid_reg <= 0; - m_axi_rready_reg <= 0; - end else begin - state_reg <= state_next; - - s_axi_awready_reg <= s_axi_awready_next; - s_axi_wready_reg <= s_axi_wready_next; - s_axi_bvalid_reg <= s_axi_bvalid_next; - s_axi_arready_reg <= s_axi_arready_next; - - m_axi_awvalid_reg <= m_axi_awvalid_next; - m_axi_bready_reg <= m_axi_bready_next; - m_axi_arvalid_reg <= m_axi_arvalid_next; - m_axi_rready_reg <= m_axi_rready_next; - end - - m_select_reg <= m_select_next; - axi_id_reg <= axi_id_next; - axi_addr_reg <= axi_addr_next; - axi_addr_valid_reg <= axi_addr_valid_next; - axi_len_reg <= axi_len_next; - axi_size_reg <= axi_size_next; - axi_burst_reg <= axi_burst_next; - axi_lock_reg <= axi_lock_next; - axi_cache_reg <= axi_cache_next; - axi_prot_reg <= axi_prot_next; - axi_qos_reg <= axi_qos_next; - axi_region_reg <= axi_region_next; - axi_auser_reg <= axi_auser_next; - axi_bresp_reg <= axi_bresp_next; - axi_buser_reg <= axi_buser_next; -end - -// output datapath logic (R channel) -reg [ID_WIDTH-1:0] s_axi_rid_reg = {ID_WIDTH{1'b0}}; -reg [DATA_WIDTH-1:0] s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; -reg [1:0] s_axi_rresp_reg = 2'd0; -reg s_axi_rlast_reg = 1'b0; -reg [RUSER_WIDTH-1:0] s_axi_ruser_reg = 1'b0; -reg [S_COUNT-1:0] s_axi_rvalid_reg = 1'b0, s_axi_rvalid_next; - -reg [ID_WIDTH-1:0] temp_s_axi_rid_reg = {ID_WIDTH{1'b0}}; -reg [DATA_WIDTH-1:0] temp_s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; -reg [1:0] temp_s_axi_rresp_reg = 2'd0; -reg temp_s_axi_rlast_reg = 1'b0; -reg [RUSER_WIDTH-1:0] temp_s_axi_ruser_reg = 1'b0; -reg temp_s_axi_rvalid_reg = 1'b0, temp_s_axi_rvalid_next; - -// datapath control -reg store_axi_r_int_to_output; -reg store_axi_r_int_to_temp; -reg store_axi_r_temp_to_output; - -assign s_axi_rid = {S_COUNT{s_axi_rid_reg}}; -assign s_axi_rdata = {S_COUNT{s_axi_rdata_reg}}; -assign s_axi_rresp = {S_COUNT{s_axi_rresp_reg}}; -assign s_axi_rlast = {S_COUNT{s_axi_rlast_reg}}; -assign s_axi_ruser = {S_COUNT{RUSER_ENABLE ? s_axi_ruser_reg : {RUSER_WIDTH{1'b0}}}}; -assign s_axi_rvalid = s_axi_rvalid_reg; - -// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) -assign s_axi_rready_int_early = current_s_axi_rready | (~temp_s_axi_rvalid_reg & (~current_s_axi_rvalid | ~s_axi_rvalid_int)); - -always @* begin - // transfer sink ready state to source - s_axi_rvalid_next = s_axi_rvalid_reg; - temp_s_axi_rvalid_next = temp_s_axi_rvalid_reg; - - store_axi_r_int_to_output = 1'b0; - store_axi_r_int_to_temp = 1'b0; - store_axi_r_temp_to_output = 1'b0; - - if (s_axi_rready_int_reg) begin - // input is ready - if (current_s_axi_rready | ~current_s_axi_rvalid) begin - // output is ready or currently not valid, transfer data to output - s_axi_rvalid_next[s_select] = s_axi_rvalid_int; - store_axi_r_int_to_output = 1'b1; - end else begin - // output is not ready, store input in temp - temp_s_axi_rvalid_next = s_axi_rvalid_int; - store_axi_r_int_to_temp = 1'b1; - end - end else if (current_s_axi_rready) begin - // input is not ready, but output is ready - s_axi_rvalid_next[s_select] = temp_s_axi_rvalid_reg; - temp_s_axi_rvalid_next = 1'b0; - store_axi_r_temp_to_output = 1'b1; - end -end - -always @(posedge clk) begin - if (rst) begin - s_axi_rvalid_reg <= 1'b0; - s_axi_rready_int_reg <= 1'b0; - temp_s_axi_rvalid_reg <= 1'b0; - end else begin - s_axi_rvalid_reg <= s_axi_rvalid_next; - s_axi_rready_int_reg <= s_axi_rready_int_early; - temp_s_axi_rvalid_reg <= temp_s_axi_rvalid_next; - end - - // datapath - if (store_axi_r_int_to_output) begin - s_axi_rid_reg <= s_axi_rid_int; - s_axi_rdata_reg <= s_axi_rdata_int; - s_axi_rresp_reg <= s_axi_rresp_int; - s_axi_rlast_reg <= s_axi_rlast_int; - s_axi_ruser_reg <= s_axi_ruser_int; - end else if (store_axi_r_temp_to_output) begin - s_axi_rid_reg <= temp_s_axi_rid_reg; - s_axi_rdata_reg <= temp_s_axi_rdata_reg; - s_axi_rresp_reg <= temp_s_axi_rresp_reg; - s_axi_rlast_reg <= temp_s_axi_rlast_reg; - s_axi_ruser_reg <= temp_s_axi_ruser_reg; - end - - if (store_axi_r_int_to_temp) begin - temp_s_axi_rid_reg <= s_axi_rid_int; - temp_s_axi_rdata_reg <= s_axi_rdata_int; - temp_s_axi_rresp_reg <= s_axi_rresp_int; - temp_s_axi_rlast_reg <= s_axi_rlast_int; - temp_s_axi_ruser_reg <= s_axi_ruser_int; - end -end - -// output datapath logic (W channel) -reg [DATA_WIDTH-1:0] m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; -reg [STRB_WIDTH-1:0] m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; -reg m_axi_wlast_reg = 1'b0; -reg [WUSER_WIDTH-1:0] m_axi_wuser_reg = 1'b0; -reg [M_COUNT-1:0] m_axi_wvalid_reg = 1'b0, m_axi_wvalid_next; - -reg [DATA_WIDTH-1:0] temp_m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; -reg [STRB_WIDTH-1:0] temp_m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; -reg temp_m_axi_wlast_reg = 1'b0; -reg [WUSER_WIDTH-1:0] temp_m_axi_wuser_reg = 1'b0; -reg temp_m_axi_wvalid_reg = 1'b0, temp_m_axi_wvalid_next; - -// datapath control -reg store_axi_w_int_to_output; -reg store_axi_w_int_to_temp; -reg store_axi_w_temp_to_output; - -assign m_axi_wdata = {M_COUNT{m_axi_wdata_reg}}; -assign m_axi_wstrb = {M_COUNT{m_axi_wstrb_reg}}; -assign m_axi_wlast = {M_COUNT{m_axi_wlast_reg}}; -assign m_axi_wuser = {M_COUNT{WUSER_ENABLE ? m_axi_wuser_reg : {WUSER_WIDTH{1'b0}}}}; -assign m_axi_wvalid = m_axi_wvalid_reg; - -// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) -assign m_axi_wready_int_early = current_m_axi_wready | (~temp_m_axi_wvalid_reg & (~current_m_axi_wvalid | ~m_axi_wvalid_int)); - -always @* begin - // transfer sink ready state to source - m_axi_wvalid_next = m_axi_wvalid_reg; - temp_m_axi_wvalid_next = temp_m_axi_wvalid_reg; - - store_axi_w_int_to_output = 1'b0; - store_axi_w_int_to_temp = 1'b0; - store_axi_w_temp_to_output = 1'b0; - - if (m_axi_wready_int_reg) begin - // input is ready - if (current_m_axi_wready | ~current_m_axi_wvalid) begin - // output is ready or currently not valid, transfer data to output - m_axi_wvalid_next[m_select_reg] = m_axi_wvalid_int; - store_axi_w_int_to_output = 1'b1; - end else begin - // output is not ready, store input in temp - temp_m_axi_wvalid_next = m_axi_wvalid_int; - store_axi_w_int_to_temp = 1'b1; - end - end else if (current_m_axi_wready) begin - // input is not ready, but output is ready - m_axi_wvalid_next[m_select_reg] = temp_m_axi_wvalid_reg; - temp_m_axi_wvalid_next = 1'b0; - store_axi_w_temp_to_output = 1'b1; - end -end - -always @(posedge clk) begin - if (rst) begin - m_axi_wvalid_reg <= 1'b0; - m_axi_wready_int_reg <= 1'b0; - temp_m_axi_wvalid_reg <= 1'b0; - end else begin - m_axi_wvalid_reg <= m_axi_wvalid_next; - m_axi_wready_int_reg <= m_axi_wready_int_early; - temp_m_axi_wvalid_reg <= temp_m_axi_wvalid_next; - end - - // datapath - if (store_axi_w_int_to_output) begin - m_axi_wdata_reg <= m_axi_wdata_int; - m_axi_wstrb_reg <= m_axi_wstrb_int; - m_axi_wlast_reg <= m_axi_wlast_int; - m_axi_wuser_reg <= m_axi_wuser_int; - end else if (store_axi_w_temp_to_output) begin - m_axi_wdata_reg <= temp_m_axi_wdata_reg; - m_axi_wstrb_reg <= temp_m_axi_wstrb_reg; - m_axi_wlast_reg <= temp_m_axi_wlast_reg; - m_axi_wuser_reg <= temp_m_axi_wuser_reg; - end - - if (store_axi_w_int_to_temp) begin - temp_m_axi_wdata_reg <= m_axi_wdata_int; - temp_m_axi_wstrb_reg <= m_axi_wstrb_int; - temp_m_axi_wlast_reg <= m_axi_wlast_int; - temp_m_axi_wuser_reg <= m_axi_wuser_int; - end -end - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/BUILD b/xls/modules/zstd/external/BUILD new file mode 100644 index 0000000000..f24cb69fe0 --- /dev/null +++ b/xls/modules/zstd/external/BUILD @@ -0,0 +1,33 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +exports_files( + [ + "arbiter.v", + "axi_crossbar.v", + "axi_crossbar_addr.v", + "axi_crossbar_rd.v", + "axi_crossbar_wr.v", + "axi_crossbar_wrapper.v", + "axi_register_rd.v", + "axi_register_wr.v", + "priority_encoder.v", + ], +) diff --git a/xls/modules/zstd/arbiter.v b/xls/modules/zstd/external/arbiter.v similarity index 100% rename from xls/modules/zstd/arbiter.v rename to xls/modules/zstd/external/arbiter.v diff --git a/xls/modules/zstd/external/axi_crossbar.v b/xls/modules/zstd/external/axi_crossbar.v new file mode 100644 index 0000000000..991d45403a --- /dev/null +++ b/xls/modules/zstd/external/axi_crossbar.v @@ -0,0 +1,391 @@ +/* + +Copyright (c) 2018 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 crossbar + */ +module axi_crossbar # +( + // Number of AXI inputs (slave interfaces) + parameter S_COUNT = 4, + // Number of AXI outputs (master interfaces) + parameter M_COUNT = 4, + // Width of data bus in bits + parameter DATA_WIDTH = 32, + // Width of address bus in bits + parameter ADDR_WIDTH = 32, + // Width of wstrb (width of data bus in words) + parameter STRB_WIDTH = (DATA_WIDTH/8), + // Input ID field width (from AXI masters) + parameter S_ID_WIDTH = 8, + // Output ID field width (towards AXI slaves) + // Additional bits required for response routing + parameter M_ID_WIDTH = S_ID_WIDTH+$clog2(S_COUNT), + // Propagate awuser signal + parameter AWUSER_ENABLE = 0, + // Width of awuser signal + parameter AWUSER_WIDTH = 1, + // Propagate wuser signal + parameter WUSER_ENABLE = 0, + // Width of wuser signal + parameter WUSER_WIDTH = 1, + // Propagate buser signal + parameter BUSER_ENABLE = 0, + // Width of buser signal + parameter BUSER_WIDTH = 1, + // Propagate aruser signal + parameter ARUSER_ENABLE = 0, + // Width of aruser signal + parameter ARUSER_WIDTH = 1, + // Propagate ruser signal + parameter RUSER_ENABLE = 0, + // Width of ruser signal + parameter RUSER_WIDTH = 1, + // Number of concurrent unique IDs for each slave interface + // S_COUNT concatenated fields of 32 bits + parameter S_THREADS = {S_COUNT{32'd2}}, + // Number of concurrent operations for each slave interface + // S_COUNT concatenated fields of 32 bits + parameter S_ACCEPT = {S_COUNT{32'd16}}, + // Number of regions per master interface + parameter M_REGIONS = 1, + // Master interface base addresses + // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits + // set to zero for default addressing based on M_ADDR_WIDTH + parameter M_BASE_ADDR = 0, + // Master interface address widths + // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, + // Read connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT_READ = {M_COUNT{{S_COUNT{1'b1}}}}, + // Write connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT_WRITE = {M_COUNT{{S_COUNT{1'b1}}}}, + // Number of concurrent operations for each master interface + // M_COUNT concatenated fields of 32 bits + parameter M_ISSUE = {M_COUNT{32'd4}}, + // Secure master (fail operations based on awprot/arprot) + // M_COUNT bits + parameter M_SECURE = {M_COUNT{1'b0}}, + // Slave interface AW channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_AW_REG_TYPE = {S_COUNT{2'd0}}, + // Slave interface W channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_W_REG_TYPE = {S_COUNT{2'd0}}, + // Slave interface B channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_B_REG_TYPE = {S_COUNT{2'd1}}, + // Slave interface AR channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_AR_REG_TYPE = {S_COUNT{2'd0}}, + // Slave interface R channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_R_REG_TYPE = {S_COUNT{2'd2}}, + // Master interface AW channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_AW_REG_TYPE = {M_COUNT{2'd1}}, + // Master interface W channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_W_REG_TYPE = {M_COUNT{2'd2}}, + // Master interface B channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_B_REG_TYPE = {M_COUNT{2'd0}}, + // Master interface AR channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_AR_REG_TYPE = {M_COUNT{2'd1}}, + // Master interface R channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_R_REG_TYPE = {M_COUNT{2'd0}} +) +( + input wire clk, + input wire rst, + + /* + * AXI slave interfaces + */ + input wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_awid, + input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_awaddr, + input wire [S_COUNT*8-1:0] s_axi_awlen, + input wire [S_COUNT*3-1:0] s_axi_awsize, + input wire [S_COUNT*2-1:0] s_axi_awburst, + input wire [S_COUNT-1:0] s_axi_awlock, + input wire [S_COUNT*4-1:0] s_axi_awcache, + input wire [S_COUNT*3-1:0] s_axi_awprot, + input wire [S_COUNT*4-1:0] s_axi_awqos, + input wire [S_COUNT*AWUSER_WIDTH-1:0] s_axi_awuser, + input wire [S_COUNT-1:0] s_axi_awvalid, + output wire [S_COUNT-1:0] s_axi_awready, + input wire [S_COUNT*DATA_WIDTH-1:0] s_axi_wdata, + input wire [S_COUNT*STRB_WIDTH-1:0] s_axi_wstrb, + input wire [S_COUNT-1:0] s_axi_wlast, + input wire [S_COUNT*WUSER_WIDTH-1:0] s_axi_wuser, + input wire [S_COUNT-1:0] s_axi_wvalid, + output wire [S_COUNT-1:0] s_axi_wready, + output wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_bid, + output wire [S_COUNT*2-1:0] s_axi_bresp, + output wire [S_COUNT*BUSER_WIDTH-1:0] s_axi_buser, + output wire [S_COUNT-1:0] s_axi_bvalid, + input wire [S_COUNT-1:0] s_axi_bready, + input wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_arid, + input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_araddr, + input wire [S_COUNT*8-1:0] s_axi_arlen, + input wire [S_COUNT*3-1:0] s_axi_arsize, + input wire [S_COUNT*2-1:0] s_axi_arburst, + input wire [S_COUNT-1:0] s_axi_arlock, + input wire [S_COUNT*4-1:0] s_axi_arcache, + input wire [S_COUNT*3-1:0] s_axi_arprot, + input wire [S_COUNT*4-1:0] s_axi_arqos, + input wire [S_COUNT*ARUSER_WIDTH-1:0] s_axi_aruser, + input wire [S_COUNT-1:0] s_axi_arvalid, + output wire [S_COUNT-1:0] s_axi_arready, + output wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_rid, + output wire [S_COUNT*DATA_WIDTH-1:0] s_axi_rdata, + output wire [S_COUNT*2-1:0] s_axi_rresp, + output wire [S_COUNT-1:0] s_axi_rlast, + output wire [S_COUNT*RUSER_WIDTH-1:0] s_axi_ruser, + output wire [S_COUNT-1:0] s_axi_rvalid, + input wire [S_COUNT-1:0] s_axi_rready, + + /* + * AXI master interfaces + */ + output wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_awid, + output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_awaddr, + output wire [M_COUNT*8-1:0] m_axi_awlen, + output wire [M_COUNT*3-1:0] m_axi_awsize, + output wire [M_COUNT*2-1:0] m_axi_awburst, + output wire [M_COUNT-1:0] m_axi_awlock, + output wire [M_COUNT*4-1:0] m_axi_awcache, + output wire [M_COUNT*3-1:0] m_axi_awprot, + output wire [M_COUNT*4-1:0] m_axi_awqos, + output wire [M_COUNT*4-1:0] m_axi_awregion, + output wire [M_COUNT*AWUSER_WIDTH-1:0] m_axi_awuser, + output wire [M_COUNT-1:0] m_axi_awvalid, + input wire [M_COUNT-1:0] m_axi_awready, + output wire [M_COUNT*DATA_WIDTH-1:0] m_axi_wdata, + output wire [M_COUNT*STRB_WIDTH-1:0] m_axi_wstrb, + output wire [M_COUNT-1:0] m_axi_wlast, + output wire [M_COUNT*WUSER_WIDTH-1:0] m_axi_wuser, + output wire [M_COUNT-1:0] m_axi_wvalid, + input wire [M_COUNT-1:0] m_axi_wready, + input wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_bid, + input wire [M_COUNT*2-1:0] m_axi_bresp, + input wire [M_COUNT*BUSER_WIDTH-1:0] m_axi_buser, + input wire [M_COUNT-1:0] m_axi_bvalid, + output wire [M_COUNT-1:0] m_axi_bready, + output wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_arid, + output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_araddr, + output wire [M_COUNT*8-1:0] m_axi_arlen, + output wire [M_COUNT*3-1:0] m_axi_arsize, + output wire [M_COUNT*2-1:0] m_axi_arburst, + output wire [M_COUNT-1:0] m_axi_arlock, + output wire [M_COUNT*4-1:0] m_axi_arcache, + output wire [M_COUNT*3-1:0] m_axi_arprot, + output wire [M_COUNT*4-1:0] m_axi_arqos, + output wire [M_COUNT*4-1:0] m_axi_arregion, + output wire [M_COUNT*ARUSER_WIDTH-1:0] m_axi_aruser, + output wire [M_COUNT-1:0] m_axi_arvalid, + input wire [M_COUNT-1:0] m_axi_arready, + input wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_rid, + input wire [M_COUNT*DATA_WIDTH-1:0] m_axi_rdata, + input wire [M_COUNT*2-1:0] m_axi_rresp, + input wire [M_COUNT-1:0] m_axi_rlast, + input wire [M_COUNT*RUSER_WIDTH-1:0] m_axi_ruser, + input wire [M_COUNT-1:0] m_axi_rvalid, + output wire [M_COUNT-1:0] m_axi_rready +); + +axi_crossbar_wr #( + .S_COUNT(S_COUNT), + .M_COUNT(M_COUNT), + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .STRB_WIDTH(STRB_WIDTH), + .S_ID_WIDTH(S_ID_WIDTH), + .M_ID_WIDTH(M_ID_WIDTH), + .AWUSER_ENABLE(AWUSER_ENABLE), + .AWUSER_WIDTH(AWUSER_WIDTH), + .WUSER_ENABLE(WUSER_ENABLE), + .WUSER_WIDTH(WUSER_WIDTH), + .BUSER_ENABLE(BUSER_ENABLE), + .BUSER_WIDTH(BUSER_WIDTH), + .S_THREADS(S_THREADS), + .S_ACCEPT(S_ACCEPT), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR(M_BASE_ADDR), + .M_ADDR_WIDTH(M_ADDR_WIDTH), + .M_CONNECT(M_CONNECT_WRITE), + .M_ISSUE(M_ISSUE), + .M_SECURE(M_SECURE), + .S_AW_REG_TYPE(S_AW_REG_TYPE), + .S_W_REG_TYPE (S_W_REG_TYPE), + .S_B_REG_TYPE (S_B_REG_TYPE) +) +axi_crossbar_wr_inst ( + .clk(clk), + .rst(rst), + + /* + * AXI slave interfaces + */ + .s_axi_awid(s_axi_awid), + .s_axi_awaddr(s_axi_awaddr), + .s_axi_awlen(s_axi_awlen), + .s_axi_awsize(s_axi_awsize), + .s_axi_awburst(s_axi_awburst), + .s_axi_awlock(s_axi_awlock), + .s_axi_awcache(s_axi_awcache), + .s_axi_awprot(s_axi_awprot), + .s_axi_awqos(s_axi_awqos), + .s_axi_awuser(s_axi_awuser), + .s_axi_awvalid(s_axi_awvalid), + .s_axi_awready(s_axi_awready), + .s_axi_wdata(s_axi_wdata), + .s_axi_wstrb(s_axi_wstrb), + .s_axi_wlast(s_axi_wlast), + .s_axi_wuser(s_axi_wuser), + .s_axi_wvalid(s_axi_wvalid), + .s_axi_wready(s_axi_wready), + .s_axi_bid(s_axi_bid), + .s_axi_bresp(s_axi_bresp), + .s_axi_buser(s_axi_buser), + .s_axi_bvalid(s_axi_bvalid), + .s_axi_bready(s_axi_bready), + + /* + * AXI master interfaces + */ + .m_axi_awid(m_axi_awid), + .m_axi_awaddr(m_axi_awaddr), + .m_axi_awlen(m_axi_awlen), + .m_axi_awsize(m_axi_awsize), + .m_axi_awburst(m_axi_awburst), + .m_axi_awlock(m_axi_awlock), + .m_axi_awcache(m_axi_awcache), + .m_axi_awprot(m_axi_awprot), + .m_axi_awqos(m_axi_awqos), + .m_axi_awregion(m_axi_awregion), + .m_axi_awuser(m_axi_awuser), + .m_axi_awvalid(m_axi_awvalid), + .m_axi_awready(m_axi_awready), + .m_axi_wdata(m_axi_wdata), + .m_axi_wstrb(m_axi_wstrb), + .m_axi_wlast(m_axi_wlast), + .m_axi_wuser(m_axi_wuser), + .m_axi_wvalid(m_axi_wvalid), + .m_axi_wready(m_axi_wready), + .m_axi_bid(m_axi_bid), + .m_axi_bresp(m_axi_bresp), + .m_axi_buser(m_axi_buser), + .m_axi_bvalid(m_axi_bvalid), + .m_axi_bready(m_axi_bready) +); + +axi_crossbar_rd #( + .S_COUNT(S_COUNT), + .M_COUNT(M_COUNT), + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .STRB_WIDTH(STRB_WIDTH), + .S_ID_WIDTH(S_ID_WIDTH), + .M_ID_WIDTH(M_ID_WIDTH), + .ARUSER_ENABLE(ARUSER_ENABLE), + .ARUSER_WIDTH(ARUSER_WIDTH), + .RUSER_ENABLE(RUSER_ENABLE), + .RUSER_WIDTH(RUSER_WIDTH), + .S_THREADS(S_THREADS), + .S_ACCEPT(S_ACCEPT), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR(M_BASE_ADDR), + .M_ADDR_WIDTH(M_ADDR_WIDTH), + .M_CONNECT(M_CONNECT_READ), + .M_ISSUE(M_ISSUE), + .M_SECURE(M_SECURE), + .S_AR_REG_TYPE(S_AR_REG_TYPE), + .S_R_REG_TYPE (S_R_REG_TYPE) +) +axi_crossbar_rd_inst ( + .clk(clk), + .rst(rst), + + /* + * AXI slave interfaces + */ + .s_axi_arid(s_axi_arid), + .s_axi_araddr(s_axi_araddr), + .s_axi_arlen(s_axi_arlen), + .s_axi_arsize(s_axi_arsize), + .s_axi_arburst(s_axi_arburst), + .s_axi_arlock(s_axi_arlock), + .s_axi_arcache(s_axi_arcache), + .s_axi_arprot(s_axi_arprot), + .s_axi_arqos(s_axi_arqos), + .s_axi_aruser(s_axi_aruser), + .s_axi_arvalid(s_axi_arvalid), + .s_axi_arready(s_axi_arready), + .s_axi_rid(s_axi_rid), + .s_axi_rdata(s_axi_rdata), + .s_axi_rresp(s_axi_rresp), + .s_axi_rlast(s_axi_rlast), + .s_axi_ruser(s_axi_ruser), + .s_axi_rvalid(s_axi_rvalid), + .s_axi_rready(s_axi_rready), + + /* + * AXI master interfaces + */ + .m_axi_arid(m_axi_arid), + .m_axi_araddr(m_axi_araddr), + .m_axi_arlen(m_axi_arlen), + .m_axi_arsize(m_axi_arsize), + .m_axi_arburst(m_axi_arburst), + .m_axi_arlock(m_axi_arlock), + .m_axi_arcache(m_axi_arcache), + .m_axi_arprot(m_axi_arprot), + .m_axi_arqos(m_axi_arqos), + .m_axi_arregion(m_axi_arregion), + .m_axi_aruser(m_axi_aruser), + .m_axi_arvalid(m_axi_arvalid), + .m_axi_arready(m_axi_arready), + .m_axi_rid(m_axi_rid), + .m_axi_rdata(m_axi_rdata), + .m_axi_rresp(m_axi_rresp), + .m_axi_rlast(m_axi_rlast), + .m_axi_ruser(m_axi_ruser), + .m_axi_rvalid(m_axi_rvalid), + .m_axi_rready(m_axi_rready) +); + +endmodule + +`resetall diff --git a/xls/modules/zstd/external/axi_crossbar_addr.v b/xls/modules/zstd/external/axi_crossbar_addr.v new file mode 100644 index 0000000000..7b7846526b --- /dev/null +++ b/xls/modules/zstd/external/axi_crossbar_addr.v @@ -0,0 +1,418 @@ +/* + +Copyright (c) 2018 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 crossbar address decode and admission control + */ +module axi_crossbar_addr # +( + // Slave interface index + parameter S = 0, + // Number of AXI inputs (slave interfaces) + parameter S_COUNT = 4, + // Number of AXI outputs (master interfaces) + parameter M_COUNT = 4, + // Width of address bus in bits + parameter ADDR_WIDTH = 32, + // ID field width + parameter ID_WIDTH = 8, + // Number of concurrent unique IDs + parameter S_THREADS = 32'd2, + // Number of concurrent operations + parameter S_ACCEPT = 32'd16, + // Number of regions per master interface + parameter M_REGIONS = 1, + // Master interface base addresses + // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits + // set to zero for default addressing based on M_ADDR_WIDTH + parameter M_BASE_ADDR = 0, + // Master interface address widths + // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, + // Connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT = {M_COUNT{{S_COUNT{1'b1}}}}, + // Secure master (fail operations based on awprot/arprot) + // M_COUNT bits + parameter M_SECURE = {M_COUNT{1'b0}}, + // Enable write command output + parameter WC_OUTPUT = 0 +) +( + input wire clk, + input wire rst, + + /* + * Address input + */ + input wire [ID_WIDTH-1:0] s_axi_aid, + input wire [ADDR_WIDTH-1:0] s_axi_aaddr, + input wire [2:0] s_axi_aprot, + input wire [3:0] s_axi_aqos, + input wire s_axi_avalid, + output wire s_axi_aready, + + /* + * Address output + */ + output wire [3:0] m_axi_aregion, + output wire [$clog2(M_COUNT)-1:0] m_select, + output wire m_axi_avalid, + input wire m_axi_aready, + + /* + * Write command output + */ + output wire [$clog2(M_COUNT)-1:0] m_wc_select, + output wire m_wc_decerr, + output wire m_wc_valid, + input wire m_wc_ready, + + /* + * Reply command output + */ + output wire m_rc_decerr, + output wire m_rc_valid, + input wire m_rc_ready, + + /* + * Completion input + */ + input wire [ID_WIDTH-1:0] s_cpl_id, + input wire s_cpl_valid +); + +parameter CL_S_COUNT = $clog2(S_COUNT); +parameter CL_M_COUNT = $clog2(M_COUNT); + +parameter S_INT_THREADS = S_THREADS > S_ACCEPT ? S_ACCEPT : S_THREADS; +parameter CL_S_INT_THREADS = $clog2(S_INT_THREADS); +parameter CL_S_ACCEPT = $clog2(S_ACCEPT); + +// default address computation +function [M_COUNT*M_REGIONS*ADDR_WIDTH-1:0] calcBaseAddrs(input [31:0] dummy); + integer i; + reg [ADDR_WIDTH-1:0] base; + reg [ADDR_WIDTH-1:0] width; + reg [ADDR_WIDTH-1:0] size; + reg [ADDR_WIDTH-1:0] mask; + begin + calcBaseAddrs = {M_COUNT*M_REGIONS*ADDR_WIDTH{1'b0}}; + base = 0; + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + width = M_ADDR_WIDTH[i*32 +: 32]; + mask = {ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - width); + size = mask + 1; + if (width > 0) begin + if ((base & mask) != 0) begin + base = base + size - (base & mask); // align + end + calcBaseAddrs[i * ADDR_WIDTH +: ADDR_WIDTH] = base; + base = base + size; // increment + end + end + end +endfunction + +parameter M_BASE_ADDR_INT = M_BASE_ADDR ? M_BASE_ADDR : calcBaseAddrs(0); + +integer i, j; + +// check configuration +initial begin + if (S_ACCEPT < 1) begin + $error("Error: need at least 1 accept (instance %m)"); + $finish; + end + + if (S_THREADS < 1) begin + $error("Error: need at least 1 thread (instance %m)"); + $finish; + end + + if (S_THREADS > S_ACCEPT) begin + $warning("Warning: requested thread count larger than accept count; limiting thread count to accept count (instance %m)"); + end + + if (M_REGIONS < 1) begin + $error("Error: need at least 1 region (instance %m)"); + $finish; + end + + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + if (M_ADDR_WIDTH[i*32 +: 32] && (M_ADDR_WIDTH[i*32 +: 32] < 12 || M_ADDR_WIDTH[i*32 +: 32] > ADDR_WIDTH)) begin + $error("Error: address width out of range (instance %m)"); + $finish; + end + end + + $display("Addressing configuration for axi_crossbar_addr instance %m"); + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + if (M_ADDR_WIDTH[i*32 +: 32]) begin + $display("%2d (%2d): %x / %02d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], + M_ADDR_WIDTH[i*32 +: 32], + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) + ); + end + end + + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + if ((M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & (2**M_ADDR_WIDTH[i*32 +: 32]-1)) != 0) begin + $display("Region not aligned:"); + $display("%2d (%2d): %x / %2d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], + M_ADDR_WIDTH[i*32 +: 32], + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) + ); + $error("Error: address range not aligned (instance %m)"); + $finish; + end + end + + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + for (j = i+1; j < M_COUNT*M_REGIONS; j = j + 1) begin + if (M_ADDR_WIDTH[i*32 +: 32] && M_ADDR_WIDTH[j*32 +: 32]) begin + if (((M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32])) <= (M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[j*32 +: 32])))) + && ((M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[j*32 +: 32])) <= (M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32]))))) begin + $display("Overlapping regions:"); + $display("%2d (%2d): %x / %2d -- %x-%x", + i/M_REGIONS, i%M_REGIONS, + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], + M_ADDR_WIDTH[i*32 +: 32], + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), + M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) + ); + $display("%2d (%2d): %x / %2d -- %x-%x", + j/M_REGIONS, j%M_REGIONS, + M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH], + M_ADDR_WIDTH[j*32 +: 32], + M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[j*32 +: 32]), + M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[j*32 +: 32])) + ); + $error("Error: address ranges overlap (instance %m)"); + $finish; + end + end + end + end +end + +localparam [2:0] + STATE_IDLE = 3'd0, + STATE_DECODE = 3'd1; + +reg [2:0] state_reg = STATE_IDLE, state_next; + +reg s_axi_aready_reg = 0, s_axi_aready_next; + +reg [3:0] m_axi_aregion_reg = 4'd0, m_axi_aregion_next; +reg [CL_M_COUNT-1:0] m_select_reg = 0, m_select_next; +reg m_axi_avalid_reg = 1'b0, m_axi_avalid_next; +reg m_decerr_reg = 1'b0, m_decerr_next; +reg m_wc_valid_reg = 1'b0, m_wc_valid_next; +reg m_rc_valid_reg = 1'b0, m_rc_valid_next; + +assign s_axi_aready = s_axi_aready_reg; + +assign m_axi_aregion = m_axi_aregion_reg; +assign m_select = m_select_reg; +assign m_axi_avalid = m_axi_avalid_reg; + +assign m_wc_select = m_select_reg; +assign m_wc_decerr = m_decerr_reg; +assign m_wc_valid = m_wc_valid_reg; + +assign m_rc_decerr = m_decerr_reg; +assign m_rc_valid = m_rc_valid_reg; + +reg match; +reg trans_start; +reg trans_complete; + +reg [$clog2(S_ACCEPT+1)-1:0] trans_count_reg = 0; +wire trans_limit = trans_count_reg >= S_ACCEPT && !trans_complete; + +// transfer ID thread tracking +reg [ID_WIDTH-1:0] thread_id_reg[S_INT_THREADS-1:0]; +reg [CL_M_COUNT-1:0] thread_m_reg[S_INT_THREADS-1:0]; +reg [3:0] thread_region_reg[S_INT_THREADS-1:0]; +reg [$clog2(S_ACCEPT+1)-1:0] thread_count_reg[S_INT_THREADS-1:0]; + +wire [S_INT_THREADS-1:0] thread_active; +wire [S_INT_THREADS-1:0] thread_match; +wire [S_INT_THREADS-1:0] thread_match_dest; +wire [S_INT_THREADS-1:0] thread_cpl_match; +wire [S_INT_THREADS-1:0] thread_trans_start; +wire [S_INT_THREADS-1:0] thread_trans_complete; + +generate + genvar n; + + for (n = 0; n < S_INT_THREADS; n = n + 1) begin + initial begin + thread_count_reg[n] <= 0; + end + + assign thread_active[n] = thread_count_reg[n] != 0; + assign thread_match[n] = thread_active[n] && thread_id_reg[n] == s_axi_aid; + assign thread_match_dest[n] = thread_match[n] && thread_m_reg[n] == m_select_next && (M_REGIONS < 2 || thread_region_reg[n] == m_axi_aregion_next); + assign thread_cpl_match[n] = thread_active[n] && thread_id_reg[n] == s_cpl_id; + assign thread_trans_start[n] = (thread_match[n] || (!thread_active[n] && !thread_match && !(thread_trans_start & ({S_INT_THREADS{1'b1}} >> (S_INT_THREADS-n))))) && trans_start; + assign thread_trans_complete[n] = thread_cpl_match[n] && trans_complete; + + always @(posedge clk) begin + if (rst) begin + thread_count_reg[n] <= 0; + end else begin + if (thread_trans_start[n] && !thread_trans_complete[n]) begin + thread_count_reg[n] <= thread_count_reg[n] + 1; + end else if (!thread_trans_start[n] && thread_trans_complete[n]) begin + thread_count_reg[n] <= thread_count_reg[n] - 1; + end + end + + if (thread_trans_start[n]) begin + thread_id_reg[n] <= s_axi_aid; + thread_m_reg[n] <= m_select_next; + thread_region_reg[n] <= m_axi_aregion_next; + end + end + end +endgenerate + +always @* begin + state_next = STATE_IDLE; + + match = 1'b0; + trans_start = 1'b0; + trans_complete = 1'b0; + + s_axi_aready_next = 1'b0; + + m_axi_aregion_next = m_axi_aregion_reg; + m_select_next = m_select_reg; + m_axi_avalid_next = m_axi_avalid_reg && !m_axi_aready; + m_decerr_next = m_decerr_reg; + m_wc_valid_next = m_wc_valid_reg && !m_wc_ready; + m_rc_valid_next = m_rc_valid_reg && !m_rc_ready; + + case (state_reg) + STATE_IDLE: begin + // idle state, store values + s_axi_aready_next = 1'b0; + + if (s_axi_avalid && !s_axi_aready) begin + match = 1'b0; + for (i = 0; i < M_COUNT; i = i + 1) begin + for (j = 0; j < M_REGIONS; j = j + 1) begin + if (M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32] && (!M_SECURE[i] || !s_axi_aprot[1]) && (M_CONNECT & (1 << (S+i*S_COUNT))) && (s_axi_aaddr >> M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32]) == (M_BASE_ADDR_INT[(i*M_REGIONS+j)*ADDR_WIDTH +: ADDR_WIDTH] >> M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32])) begin + m_select_next = i; + m_axi_aregion_next = j; + match = 1'b1; + end + end + end + + if (match) begin + // address decode successful + if (!trans_limit && (thread_match_dest || (!(&thread_active) && !thread_match))) begin + // transaction limit not reached + m_axi_avalid_next = 1'b1; + m_decerr_next = 1'b0; + m_wc_valid_next = WC_OUTPUT; + m_rc_valid_next = 1'b0; + trans_start = 1'b1; + state_next = STATE_DECODE; + end else begin + // transaction limit reached; block in idle + state_next = STATE_IDLE; + end + end else begin + // decode error + m_axi_avalid_next = 1'b0; + m_decerr_next = 1'b1; + m_wc_valid_next = WC_OUTPUT; + m_rc_valid_next = 1'b1; + state_next = STATE_DECODE; + end + end else begin + state_next = STATE_IDLE; + end + end + STATE_DECODE: begin + if (!m_axi_avalid_next && (!m_wc_valid_next || !WC_OUTPUT) && !m_rc_valid_next) begin + s_axi_aready_next = 1'b1; + state_next = STATE_IDLE; + end else begin + state_next = STATE_DECODE; + end + end + endcase + + // manage completions + trans_complete = s_cpl_valid; +end + +always @(posedge clk) begin + if (rst) begin + state_reg <= STATE_IDLE; + s_axi_aready_reg <= 1'b0; + m_axi_avalid_reg <= 1'b0; + m_wc_valid_reg <= 1'b0; + m_rc_valid_reg <= 1'b0; + + trans_count_reg <= 0; + end else begin + state_reg <= state_next; + s_axi_aready_reg <= s_axi_aready_next; + m_axi_avalid_reg <= m_axi_avalid_next; + m_wc_valid_reg <= m_wc_valid_next; + m_rc_valid_reg <= m_rc_valid_next; + + if (trans_start && !trans_complete) begin + trans_count_reg <= trans_count_reg + 1; + end else if (!trans_start && trans_complete) begin + trans_count_reg <= trans_count_reg - 1; + end + end + + m_axi_aregion_reg <= m_axi_aregion_next; + m_select_reg <= m_select_next; + m_decerr_reg <= m_decerr_next; +end + +endmodule + +`resetall diff --git a/xls/modules/zstd/external/axi_crossbar_rd.v b/xls/modules/zstd/external/axi_crossbar_rd.v new file mode 100644 index 0000000000..2b1410ac62 --- /dev/null +++ b/xls/modules/zstd/external/axi_crossbar_rd.v @@ -0,0 +1,569 @@ +/* + +Copyright (c) 2018 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 crossbar (read) + */ +module axi_crossbar_rd # +( + // Number of AXI inputs (slave interfaces) + parameter S_COUNT = 4, + // Number of AXI outputs (master interfaces) + parameter M_COUNT = 4, + // Width of data bus in bits + parameter DATA_WIDTH = 32, + // Width of address bus in bits + parameter ADDR_WIDTH = 32, + // Width of wstrb (width of data bus in words) + parameter STRB_WIDTH = (DATA_WIDTH/8), + // Input ID field width (from AXI masters) + parameter S_ID_WIDTH = 8, + // Output ID field width (towards AXI slaves) + // Additional bits required for response routing + parameter M_ID_WIDTH = S_ID_WIDTH+$clog2(S_COUNT), + // Propagate aruser signal + parameter ARUSER_ENABLE = 0, + // Width of aruser signal + parameter ARUSER_WIDTH = 1, + // Propagate ruser signal + parameter RUSER_ENABLE = 0, + // Width of ruser signal + parameter RUSER_WIDTH = 1, + // Number of concurrent unique IDs for each slave interface + // S_COUNT concatenated fields of 32 bits + parameter S_THREADS = {S_COUNT{32'd2}}, + // Number of concurrent operations for each slave interface + // S_COUNT concatenated fields of 32 bits + parameter S_ACCEPT = {S_COUNT{32'd16}}, + // Number of regions per master interface + parameter M_REGIONS = 1, + // Master interface base addresses + // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits + // set to zero for default addressing based on M_ADDR_WIDTH + parameter M_BASE_ADDR = 0, + // Master interface address widths + // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, + // Read connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT = {M_COUNT{{S_COUNT{1'b1}}}}, + // Number of concurrent operations for each master interface + // M_COUNT concatenated fields of 32 bits + parameter M_ISSUE = {M_COUNT{32'd4}}, + // Secure master (fail operations based on awprot/arprot) + // M_COUNT bits + parameter M_SECURE = {M_COUNT{1'b0}}, + // Slave interface AR channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_AR_REG_TYPE = {S_COUNT{2'd0}}, + // Slave interface R channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_R_REG_TYPE = {S_COUNT{2'd2}}, + // Master interface AR channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_AR_REG_TYPE = {M_COUNT{2'd1}}, + // Master interface R channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_R_REG_TYPE = {M_COUNT{2'd0}} +) +( + input wire clk, + input wire rst, + + /* + * AXI slave interfaces + */ + input wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_arid, + input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_araddr, + input wire [S_COUNT*8-1:0] s_axi_arlen, + input wire [S_COUNT*3-1:0] s_axi_arsize, + input wire [S_COUNT*2-1:0] s_axi_arburst, + input wire [S_COUNT-1:0] s_axi_arlock, + input wire [S_COUNT*4-1:0] s_axi_arcache, + input wire [S_COUNT*3-1:0] s_axi_arprot, + input wire [S_COUNT*4-1:0] s_axi_arqos, + input wire [S_COUNT*ARUSER_WIDTH-1:0] s_axi_aruser, + input wire [S_COUNT-1:0] s_axi_arvalid, + output wire [S_COUNT-1:0] s_axi_arready, + output wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_rid, + output wire [S_COUNT*DATA_WIDTH-1:0] s_axi_rdata, + output wire [S_COUNT*2-1:0] s_axi_rresp, + output wire [S_COUNT-1:0] s_axi_rlast, + output wire [S_COUNT*RUSER_WIDTH-1:0] s_axi_ruser, + output wire [S_COUNT-1:0] s_axi_rvalid, + input wire [S_COUNT-1:0] s_axi_rready, + + /* + * AXI master interfaces + */ + output wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_arid, + output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_araddr, + output wire [M_COUNT*8-1:0] m_axi_arlen, + output wire [M_COUNT*3-1:0] m_axi_arsize, + output wire [M_COUNT*2-1:0] m_axi_arburst, + output wire [M_COUNT-1:0] m_axi_arlock, + output wire [M_COUNT*4-1:0] m_axi_arcache, + output wire [M_COUNT*3-1:0] m_axi_arprot, + output wire [M_COUNT*4-1:0] m_axi_arqos, + output wire [M_COUNT*4-1:0] m_axi_arregion, + output wire [M_COUNT*ARUSER_WIDTH-1:0] m_axi_aruser, + output wire [M_COUNT-1:0] m_axi_arvalid, + input wire [M_COUNT-1:0] m_axi_arready, + input wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_rid, + input wire [M_COUNT*DATA_WIDTH-1:0] m_axi_rdata, + input wire [M_COUNT*2-1:0] m_axi_rresp, + input wire [M_COUNT-1:0] m_axi_rlast, + input wire [M_COUNT*RUSER_WIDTH-1:0] m_axi_ruser, + input wire [M_COUNT-1:0] m_axi_rvalid, + output wire [M_COUNT-1:0] m_axi_rready +); + +parameter CL_S_COUNT = $clog2(S_COUNT); +parameter CL_M_COUNT = $clog2(M_COUNT); +parameter M_COUNT_P1 = M_COUNT+1; +parameter CL_M_COUNT_P1 = $clog2(M_COUNT_P1); + +integer i; + +// check configuration +initial begin + if (M_ID_WIDTH < S_ID_WIDTH+$clog2(S_COUNT)) begin + $error("Error: M_ID_WIDTH must be at least $clog2(S_COUNT) larger than S_ID_WIDTH (instance %m)"); + $finish; + end + + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + if (M_ADDR_WIDTH[i*32 +: 32] && (M_ADDR_WIDTH[i*32 +: 32] < 12 || M_ADDR_WIDTH[i*32 +: 32] > ADDR_WIDTH)) begin + $error("Error: value out of range (instance %m)"); + $finish; + end + end +end + +wire [S_COUNT*S_ID_WIDTH-1:0] int_s_axi_arid; +wire [S_COUNT*ADDR_WIDTH-1:0] int_s_axi_araddr; +wire [S_COUNT*8-1:0] int_s_axi_arlen; +wire [S_COUNT*3-1:0] int_s_axi_arsize; +wire [S_COUNT*2-1:0] int_s_axi_arburst; +wire [S_COUNT-1:0] int_s_axi_arlock; +wire [S_COUNT*4-1:0] int_s_axi_arcache; +wire [S_COUNT*3-1:0] int_s_axi_arprot; +wire [S_COUNT*4-1:0] int_s_axi_arqos; +wire [S_COUNT*4-1:0] int_s_axi_arregion; +wire [S_COUNT*ARUSER_WIDTH-1:0] int_s_axi_aruser; +wire [S_COUNT-1:0] int_s_axi_arvalid; +wire [S_COUNT-1:0] int_s_axi_arready; + +wire [S_COUNT*M_COUNT-1:0] int_axi_arvalid; +wire [M_COUNT*S_COUNT-1:0] int_axi_arready; + +wire [M_COUNT*M_ID_WIDTH-1:0] int_m_axi_rid; +wire [M_COUNT*DATA_WIDTH-1:0] int_m_axi_rdata; +wire [M_COUNT*2-1:0] int_m_axi_rresp; +wire [M_COUNT-1:0] int_m_axi_rlast; +wire [M_COUNT*RUSER_WIDTH-1:0] int_m_axi_ruser; +wire [M_COUNT-1:0] int_m_axi_rvalid; +wire [M_COUNT-1:0] int_m_axi_rready; + +wire [M_COUNT*S_COUNT-1:0] int_axi_rvalid; +wire [S_COUNT*M_COUNT-1:0] int_axi_rready; + +generate + + genvar m, n; + + for (m = 0; m < S_COUNT; m = m + 1) begin : s_ifaces + // address decode and admission control + wire [CL_M_COUNT-1:0] a_select; + + wire m_axi_avalid; + wire m_axi_aready; + + wire m_rc_decerr; + wire m_rc_valid; + wire m_rc_ready; + + wire [S_ID_WIDTH-1:0] s_cpl_id; + wire s_cpl_valid; + + axi_crossbar_addr #( + .S(m), + .S_COUNT(S_COUNT), + .M_COUNT(M_COUNT), + .ADDR_WIDTH(ADDR_WIDTH), + .ID_WIDTH(S_ID_WIDTH), + .S_THREADS(S_THREADS[m*32 +: 32]), + .S_ACCEPT(S_ACCEPT[m*32 +: 32]), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR(M_BASE_ADDR), + .M_ADDR_WIDTH(M_ADDR_WIDTH), + .M_CONNECT(M_CONNECT), + .M_SECURE(M_SECURE), + .WC_OUTPUT(0) + ) + addr_inst ( + .clk(clk), + .rst(rst), + + /* + * Address input + */ + .s_axi_aid(int_s_axi_arid[m*S_ID_WIDTH +: S_ID_WIDTH]), + .s_axi_aaddr(int_s_axi_araddr[m*ADDR_WIDTH +: ADDR_WIDTH]), + .s_axi_aprot(int_s_axi_arprot[m*3 +: 3]), + .s_axi_aqos(int_s_axi_arqos[m*4 +: 4]), + .s_axi_avalid(int_s_axi_arvalid[m]), + .s_axi_aready(int_s_axi_arready[m]), + + /* + * Address output + */ + .m_axi_aregion(int_s_axi_arregion[m*4 +: 4]), + .m_select(a_select), + .m_axi_avalid(m_axi_avalid), + .m_axi_aready(m_axi_aready), + + /* + * Write command output + */ + .m_wc_select(), + .m_wc_decerr(), + .m_wc_valid(), + .m_wc_ready(1'b1), + + /* + * Response command output + */ + .m_rc_decerr(m_rc_decerr), + .m_rc_valid(m_rc_valid), + .m_rc_ready(m_rc_ready), + + /* + * Completion input + */ + .s_cpl_id(s_cpl_id), + .s_cpl_valid(s_cpl_valid) + ); + + assign int_axi_arvalid[m*M_COUNT +: M_COUNT] = m_axi_avalid << a_select; + assign m_axi_aready = int_axi_arready[a_select*S_COUNT+m]; + + // decode error handling + reg [S_ID_WIDTH-1:0] decerr_m_axi_rid_reg = {S_ID_WIDTH{1'b0}}, decerr_m_axi_rid_next; + reg decerr_m_axi_rlast_reg = 1'b0, decerr_m_axi_rlast_next; + reg decerr_m_axi_rvalid_reg = 1'b0, decerr_m_axi_rvalid_next; + wire decerr_m_axi_rready; + + reg [7:0] decerr_len_reg = 8'd0, decerr_len_next; + + assign m_rc_ready = !decerr_m_axi_rvalid_reg; + + always @* begin + decerr_len_next = decerr_len_reg; + decerr_m_axi_rid_next = decerr_m_axi_rid_reg; + decerr_m_axi_rlast_next = decerr_m_axi_rlast_reg; + decerr_m_axi_rvalid_next = decerr_m_axi_rvalid_reg; + + if (decerr_m_axi_rvalid_reg) begin + if (decerr_m_axi_rready) begin + if (decerr_len_reg > 0) begin + decerr_len_next = decerr_len_reg-1; + decerr_m_axi_rlast_next = (decerr_len_next == 0); + decerr_m_axi_rvalid_next = 1'b1; + end else begin + decerr_m_axi_rvalid_next = 1'b0; + end + end + end else if (m_rc_valid && m_rc_ready) begin + decerr_len_next = int_s_axi_arlen[m*8 +: 8]; + decerr_m_axi_rid_next = int_s_axi_arid[m*S_ID_WIDTH +: S_ID_WIDTH]; + decerr_m_axi_rlast_next = (decerr_len_next == 0); + decerr_m_axi_rvalid_next = 1'b1; + end + end + + always @(posedge clk) begin + if (rst) begin + decerr_m_axi_rvalid_reg <= 1'b0; + end else begin + decerr_m_axi_rvalid_reg <= decerr_m_axi_rvalid_next; + end + + decerr_m_axi_rid_reg <= decerr_m_axi_rid_next; + decerr_m_axi_rlast_reg <= decerr_m_axi_rlast_next; + decerr_len_reg <= decerr_len_next; + end + + // read response arbitration + wire [M_COUNT_P1-1:0] r_request; + wire [M_COUNT_P1-1:0] r_acknowledge; + wire [M_COUNT_P1-1:0] r_grant; + wire r_grant_valid; + wire [CL_M_COUNT_P1-1:0] r_grant_encoded; + + arbiter #( + .PORTS(M_COUNT_P1), + .ARB_TYPE_ROUND_ROBIN(1), + .ARB_BLOCK(1), + .ARB_BLOCK_ACK(1), + .ARB_LSB_HIGH_PRIORITY(1) + ) + r_arb_inst ( + .clk(clk), + .rst(rst), + .request(r_request), + .acknowledge(r_acknowledge), + .grant(r_grant), + .grant_valid(r_grant_valid), + .grant_encoded(r_grant_encoded) + ); + + // read response mux + wire [S_ID_WIDTH-1:0] m_axi_rid_mux = {decerr_m_axi_rid_reg, int_m_axi_rid} >> r_grant_encoded*M_ID_WIDTH; + wire [DATA_WIDTH-1:0] m_axi_rdata_mux = {{DATA_WIDTH{1'b0}}, int_m_axi_rdata} >> r_grant_encoded*DATA_WIDTH; + wire [1:0] m_axi_rresp_mux = {2'b11, int_m_axi_rresp} >> r_grant_encoded*2; + wire m_axi_rlast_mux = {decerr_m_axi_rlast_reg, int_m_axi_rlast} >> r_grant_encoded; + wire [RUSER_WIDTH-1:0] m_axi_ruser_mux = {{RUSER_WIDTH{1'b0}}, int_m_axi_ruser} >> r_grant_encoded*RUSER_WIDTH; + wire m_axi_rvalid_mux = ({decerr_m_axi_rvalid_reg, int_m_axi_rvalid} >> r_grant_encoded) & r_grant_valid; + wire m_axi_rready_mux; + + assign int_axi_rready[m*M_COUNT +: M_COUNT] = (r_grant_valid && m_axi_rready_mux) << r_grant_encoded; + assign decerr_m_axi_rready = (r_grant_valid && m_axi_rready_mux) && (r_grant_encoded == M_COUNT_P1-1); + + for (n = 0; n < M_COUNT; n = n + 1) begin + assign r_request[n] = int_axi_rvalid[n*S_COUNT+m] && !r_grant[n]; + assign r_acknowledge[n] = r_grant[n] && int_axi_rvalid[n*S_COUNT+m] && m_axi_rlast_mux && m_axi_rready_mux; + end + + assign r_request[M_COUNT_P1-1] = decerr_m_axi_rvalid_reg && !r_grant[M_COUNT_P1-1]; + assign r_acknowledge[M_COUNT_P1-1] = r_grant[M_COUNT_P1-1] && decerr_m_axi_rvalid_reg && decerr_m_axi_rlast_reg && m_axi_rready_mux; + + assign s_cpl_id = m_axi_rid_mux; + assign s_cpl_valid = m_axi_rvalid_mux && m_axi_rready_mux && m_axi_rlast_mux; + + // S side register + axi_register_rd #( + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .STRB_WIDTH(STRB_WIDTH), + .ID_WIDTH(S_ID_WIDTH), + .ARUSER_ENABLE(ARUSER_ENABLE), + .ARUSER_WIDTH(ARUSER_WIDTH), + .RUSER_ENABLE(RUSER_ENABLE), + .RUSER_WIDTH(RUSER_WIDTH), + .AR_REG_TYPE(S_AR_REG_TYPE[m*2 +: 2]), + .R_REG_TYPE(S_R_REG_TYPE[m*2 +: 2]) + ) + reg_inst ( + .clk(clk), + .rst(rst), + .s_axi_arid(s_axi_arid[m*S_ID_WIDTH +: S_ID_WIDTH]), + .s_axi_araddr(s_axi_araddr[m*ADDR_WIDTH +: ADDR_WIDTH]), + .s_axi_arlen(s_axi_arlen[m*8 +: 8]), + .s_axi_arsize(s_axi_arsize[m*3 +: 3]), + .s_axi_arburst(s_axi_arburst[m*2 +: 2]), + .s_axi_arlock(s_axi_arlock[m]), + .s_axi_arcache(s_axi_arcache[m*4 +: 4]), + .s_axi_arprot(s_axi_arprot[m*3 +: 3]), + .s_axi_arqos(s_axi_arqos[m*4 +: 4]), + .s_axi_arregion(4'd0), + .s_axi_aruser(s_axi_aruser[m*ARUSER_WIDTH +: ARUSER_WIDTH]), + .s_axi_arvalid(s_axi_arvalid[m]), + .s_axi_arready(s_axi_arready[m]), + .s_axi_rid(s_axi_rid[m*S_ID_WIDTH +: S_ID_WIDTH]), + .s_axi_rdata(s_axi_rdata[m*DATA_WIDTH +: DATA_WIDTH]), + .s_axi_rresp(s_axi_rresp[m*2 +: 2]), + .s_axi_rlast(s_axi_rlast[m]), + .s_axi_ruser(s_axi_ruser[m*RUSER_WIDTH +: RUSER_WIDTH]), + .s_axi_rvalid(s_axi_rvalid[m]), + .s_axi_rready(s_axi_rready[m]), + .m_axi_arid(int_s_axi_arid[m*S_ID_WIDTH +: S_ID_WIDTH]), + .m_axi_araddr(int_s_axi_araddr[m*ADDR_WIDTH +: ADDR_WIDTH]), + .m_axi_arlen(int_s_axi_arlen[m*8 +: 8]), + .m_axi_arsize(int_s_axi_arsize[m*3 +: 3]), + .m_axi_arburst(int_s_axi_arburst[m*2 +: 2]), + .m_axi_arlock(int_s_axi_arlock[m]), + .m_axi_arcache(int_s_axi_arcache[m*4 +: 4]), + .m_axi_arprot(int_s_axi_arprot[m*3 +: 3]), + .m_axi_arqos(int_s_axi_arqos[m*4 +: 4]), + .m_axi_arregion(), + .m_axi_aruser(int_s_axi_aruser[m*ARUSER_WIDTH +: ARUSER_WIDTH]), + .m_axi_arvalid(int_s_axi_arvalid[m]), + .m_axi_arready(int_s_axi_arready[m]), + .m_axi_rid(m_axi_rid_mux), + .m_axi_rdata(m_axi_rdata_mux), + .m_axi_rresp(m_axi_rresp_mux), + .m_axi_rlast(m_axi_rlast_mux), + .m_axi_ruser(m_axi_ruser_mux), + .m_axi_rvalid(m_axi_rvalid_mux), + .m_axi_rready(m_axi_rready_mux) + ); + end // s_ifaces + + for (n = 0; n < M_COUNT; n = n + 1) begin : m_ifaces + // in-flight transaction count + wire trans_start; + wire trans_complete; + reg [$clog2(M_ISSUE[n*32 +: 32]+1)-1:0] trans_count_reg = 0; + + wire trans_limit = trans_count_reg >= M_ISSUE[n*32 +: 32] && !trans_complete; + + always @(posedge clk) begin + if (rst) begin + trans_count_reg <= 0; + end else begin + if (trans_start && !trans_complete) begin + trans_count_reg <= trans_count_reg + 1; + end else if (!trans_start && trans_complete) begin + trans_count_reg <= trans_count_reg - 1; + end + end + end + + // address arbitration + wire [S_COUNT-1:0] a_request; + wire [S_COUNT-1:0] a_acknowledge; + wire [S_COUNT-1:0] a_grant; + wire a_grant_valid; + wire [CL_S_COUNT-1:0] a_grant_encoded; + + arbiter #( + .PORTS(S_COUNT), + .ARB_TYPE_ROUND_ROBIN(1), + .ARB_BLOCK(1), + .ARB_BLOCK_ACK(1), + .ARB_LSB_HIGH_PRIORITY(1) + ) + a_arb_inst ( + .clk(clk), + .rst(rst), + .request(a_request), + .acknowledge(a_acknowledge), + .grant(a_grant), + .grant_valid(a_grant_valid), + .grant_encoded(a_grant_encoded) + ); + + // address mux + wire [M_ID_WIDTH-1:0] s_axi_arid_mux = int_s_axi_arid[a_grant_encoded*S_ID_WIDTH +: S_ID_WIDTH] | (a_grant_encoded << S_ID_WIDTH); + wire [ADDR_WIDTH-1:0] s_axi_araddr_mux = int_s_axi_araddr[a_grant_encoded*ADDR_WIDTH +: ADDR_WIDTH]; + wire [7:0] s_axi_arlen_mux = int_s_axi_arlen[a_grant_encoded*8 +: 8]; + wire [2:0] s_axi_arsize_mux = int_s_axi_arsize[a_grant_encoded*3 +: 3]; + wire [1:0] s_axi_arburst_mux = int_s_axi_arburst[a_grant_encoded*2 +: 2]; + wire s_axi_arlock_mux = int_s_axi_arlock[a_grant_encoded]; + wire [3:0] s_axi_arcache_mux = int_s_axi_arcache[a_grant_encoded*4 +: 4]; + wire [2:0] s_axi_arprot_mux = int_s_axi_arprot[a_grant_encoded*3 +: 3]; + wire [3:0] s_axi_arqos_mux = int_s_axi_arqos[a_grant_encoded*4 +: 4]; + wire [3:0] s_axi_arregion_mux = int_s_axi_arregion[a_grant_encoded*4 +: 4]; + wire [ARUSER_WIDTH-1:0] s_axi_aruser_mux = int_s_axi_aruser[a_grant_encoded*ARUSER_WIDTH +: ARUSER_WIDTH]; + wire s_axi_arvalid_mux = int_axi_arvalid[a_grant_encoded*M_COUNT+n] && a_grant_valid; + wire s_axi_arready_mux; + + assign int_axi_arready[n*S_COUNT +: S_COUNT] = (a_grant_valid && s_axi_arready_mux) << a_grant_encoded; + + for (m = 0; m < S_COUNT; m = m + 1) begin + assign a_request[m] = int_axi_arvalid[m*M_COUNT+n] && !a_grant[m] && !trans_limit; + assign a_acknowledge[m] = a_grant[m] && int_axi_arvalid[m*M_COUNT+n] && s_axi_arready_mux; + end + + assign trans_start = s_axi_arvalid_mux && s_axi_arready_mux && a_grant_valid; + + // read response forwarding + wire [CL_S_COUNT-1:0] r_select = m_axi_rid[n*M_ID_WIDTH +: M_ID_WIDTH] >> S_ID_WIDTH; + + assign int_axi_rvalid[n*S_COUNT +: S_COUNT] = int_m_axi_rvalid[n] << r_select; + assign int_m_axi_rready[n] = int_axi_rready[r_select*M_COUNT+n]; + + assign trans_complete = int_m_axi_rvalid[n] && int_m_axi_rready[n] && int_m_axi_rlast[n]; + + // M side register + axi_register_rd #( + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .STRB_WIDTH(STRB_WIDTH), + .ID_WIDTH(M_ID_WIDTH), + .ARUSER_ENABLE(ARUSER_ENABLE), + .ARUSER_WIDTH(ARUSER_WIDTH), + .RUSER_ENABLE(RUSER_ENABLE), + .RUSER_WIDTH(RUSER_WIDTH), + .AR_REG_TYPE(M_AR_REG_TYPE[n*2 +: 2]), + .R_REG_TYPE(M_R_REG_TYPE[n*2 +: 2]) + ) + reg_inst ( + .clk(clk), + .rst(rst), + .s_axi_arid(s_axi_arid_mux), + .s_axi_araddr(s_axi_araddr_mux), + .s_axi_arlen(s_axi_arlen_mux), + .s_axi_arsize(s_axi_arsize_mux), + .s_axi_arburst(s_axi_arburst_mux), + .s_axi_arlock(s_axi_arlock_mux), + .s_axi_arcache(s_axi_arcache_mux), + .s_axi_arprot(s_axi_arprot_mux), + .s_axi_arqos(s_axi_arqos_mux), + .s_axi_arregion(s_axi_arregion_mux), + .s_axi_aruser(s_axi_aruser_mux), + .s_axi_arvalid(s_axi_arvalid_mux), + .s_axi_arready(s_axi_arready_mux), + .s_axi_rid(int_m_axi_rid[n*M_ID_WIDTH +: M_ID_WIDTH]), + .s_axi_rdata(int_m_axi_rdata[n*DATA_WIDTH +: DATA_WIDTH]), + .s_axi_rresp(int_m_axi_rresp[n*2 +: 2]), + .s_axi_rlast(int_m_axi_rlast[n]), + .s_axi_ruser(int_m_axi_ruser[n*RUSER_WIDTH +: RUSER_WIDTH]), + .s_axi_rvalid(int_m_axi_rvalid[n]), + .s_axi_rready(int_m_axi_rready[n]), + .m_axi_arid(m_axi_arid[n*M_ID_WIDTH +: M_ID_WIDTH]), + .m_axi_araddr(m_axi_araddr[n*ADDR_WIDTH +: ADDR_WIDTH]), + .m_axi_arlen(m_axi_arlen[n*8 +: 8]), + .m_axi_arsize(m_axi_arsize[n*3 +: 3]), + .m_axi_arburst(m_axi_arburst[n*2 +: 2]), + .m_axi_arlock(m_axi_arlock[n]), + .m_axi_arcache(m_axi_arcache[n*4 +: 4]), + .m_axi_arprot(m_axi_arprot[n*3 +: 3]), + .m_axi_arqos(m_axi_arqos[n*4 +: 4]), + .m_axi_arregion(m_axi_arregion[n*4 +: 4]), + .m_axi_aruser(m_axi_aruser[n*ARUSER_WIDTH +: ARUSER_WIDTH]), + .m_axi_arvalid(m_axi_arvalid[n]), + .m_axi_arready(m_axi_arready[n]), + .m_axi_rid(m_axi_rid[n*M_ID_WIDTH +: M_ID_WIDTH]), + .m_axi_rdata(m_axi_rdata[n*DATA_WIDTH +: DATA_WIDTH]), + .m_axi_rresp(m_axi_rresp[n*2 +: 2]), + .m_axi_rlast(m_axi_rlast[n]), + .m_axi_ruser(m_axi_ruser[n*RUSER_WIDTH +: RUSER_WIDTH]), + .m_axi_rvalid(m_axi_rvalid[n]), + .m_axi_rready(m_axi_rready[n]) + ); + end // m_ifaces + +endgenerate + +endmodule + +`resetall diff --git a/xls/modules/zstd/external/axi_crossbar_wr.v b/xls/modules/zstd/external/axi_crossbar_wr.v new file mode 100644 index 0000000000..5f55665351 --- /dev/null +++ b/xls/modules/zstd/external/axi_crossbar_wr.v @@ -0,0 +1,678 @@ +/* + +Copyright (c) 2018 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 crossbar (write) + */ +module axi_crossbar_wr # +( + // Number of AXI inputs (slave interfaces) + parameter S_COUNT = 4, + // Number of AXI outputs (master interfaces) + parameter M_COUNT = 4, + // Width of data bus in bits + parameter DATA_WIDTH = 32, + // Width of address bus in bits + parameter ADDR_WIDTH = 32, + // Width of wstrb (width of data bus in words) + parameter STRB_WIDTH = (DATA_WIDTH/8), + // Input ID field width (from AXI masters) + parameter S_ID_WIDTH = 8, + // Output ID field width (towards AXI slaves) + // Additional bits required for response routing + parameter M_ID_WIDTH = S_ID_WIDTH+$clog2(S_COUNT), + // Propagate awuser signal + parameter AWUSER_ENABLE = 0, + // Width of awuser signal + parameter AWUSER_WIDTH = 1, + // Propagate wuser signal + parameter WUSER_ENABLE = 0, + // Width of wuser signal + parameter WUSER_WIDTH = 1, + // Propagate buser signal + parameter BUSER_ENABLE = 0, + // Width of buser signal + parameter BUSER_WIDTH = 1, + // Number of concurrent unique IDs for each slave interface + // S_COUNT concatenated fields of 32 bits + parameter S_THREADS = {S_COUNT{32'd2}}, + // Number of concurrent operations for each slave interface + // S_COUNT concatenated fields of 32 bits + parameter S_ACCEPT = {S_COUNT{32'd16}}, + // Number of regions per master interface + parameter M_REGIONS = 1, + // Master interface base addresses + // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits + // set to zero for default addressing based on M_ADDR_WIDTH + parameter M_BASE_ADDR = 0, + // Master interface address widths + // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits + parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, + // Write connections between interfaces + // M_COUNT concatenated fields of S_COUNT bits + parameter M_CONNECT = {M_COUNT{{S_COUNT{1'b1}}}}, + // Number of concurrent operations for each master interface + // M_COUNT concatenated fields of 32 bits + parameter M_ISSUE = {M_COUNT{32'd4}}, + // Secure master (fail operations based on awprot/arprot) + // M_COUNT bits + parameter M_SECURE = {M_COUNT{1'b0}}, + // Slave interface AW channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_AW_REG_TYPE = {S_COUNT{2'd0}}, + // Slave interface W channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_W_REG_TYPE = {S_COUNT{2'd0}}, + // Slave interface B channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S_B_REG_TYPE = {S_COUNT{2'd1}}, + // Master interface AW channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_AW_REG_TYPE = {M_COUNT{2'd1}}, + // Master interface W channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_W_REG_TYPE = {M_COUNT{2'd2}}, + // Master interface B channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M_B_REG_TYPE = {M_COUNT{2'd0}} +) +( + input wire clk, + input wire rst, + + /* + * AXI slave interfaces + */ + input wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_awid, + input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_awaddr, + input wire [S_COUNT*8-1:0] s_axi_awlen, + input wire [S_COUNT*3-1:0] s_axi_awsize, + input wire [S_COUNT*2-1:0] s_axi_awburst, + input wire [S_COUNT-1:0] s_axi_awlock, + input wire [S_COUNT*4-1:0] s_axi_awcache, + input wire [S_COUNT*3-1:0] s_axi_awprot, + input wire [S_COUNT*4-1:0] s_axi_awqos, + input wire [S_COUNT*AWUSER_WIDTH-1:0] s_axi_awuser, + input wire [S_COUNT-1:0] s_axi_awvalid, + output wire [S_COUNT-1:0] s_axi_awready, + input wire [S_COUNT*DATA_WIDTH-1:0] s_axi_wdata, + input wire [S_COUNT*STRB_WIDTH-1:0] s_axi_wstrb, + input wire [S_COUNT-1:0] s_axi_wlast, + input wire [S_COUNT*WUSER_WIDTH-1:0] s_axi_wuser, + input wire [S_COUNT-1:0] s_axi_wvalid, + output wire [S_COUNT-1:0] s_axi_wready, + output wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_bid, + output wire [S_COUNT*2-1:0] s_axi_bresp, + output wire [S_COUNT*BUSER_WIDTH-1:0] s_axi_buser, + output wire [S_COUNT-1:0] s_axi_bvalid, + input wire [S_COUNT-1:0] s_axi_bready, + + /* + * AXI master interfaces + */ + output wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_awid, + output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_awaddr, + output wire [M_COUNT*8-1:0] m_axi_awlen, + output wire [M_COUNT*3-1:0] m_axi_awsize, + output wire [M_COUNT*2-1:0] m_axi_awburst, + output wire [M_COUNT-1:0] m_axi_awlock, + output wire [M_COUNT*4-1:0] m_axi_awcache, + output wire [M_COUNT*3-1:0] m_axi_awprot, + output wire [M_COUNT*4-1:0] m_axi_awqos, + output wire [M_COUNT*4-1:0] m_axi_awregion, + output wire [M_COUNT*AWUSER_WIDTH-1:0] m_axi_awuser, + output wire [M_COUNT-1:0] m_axi_awvalid, + input wire [M_COUNT-1:0] m_axi_awready, + output wire [M_COUNT*DATA_WIDTH-1:0] m_axi_wdata, + output wire [M_COUNT*STRB_WIDTH-1:0] m_axi_wstrb, + output wire [M_COUNT-1:0] m_axi_wlast, + output wire [M_COUNT*WUSER_WIDTH-1:0] m_axi_wuser, + output wire [M_COUNT-1:0] m_axi_wvalid, + input wire [M_COUNT-1:0] m_axi_wready, + input wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_bid, + input wire [M_COUNT*2-1:0] m_axi_bresp, + input wire [M_COUNT*BUSER_WIDTH-1:0] m_axi_buser, + input wire [M_COUNT-1:0] m_axi_bvalid, + output wire [M_COUNT-1:0] m_axi_bready +); + +parameter CL_S_COUNT = $clog2(S_COUNT); +parameter CL_M_COUNT = $clog2(M_COUNT); +parameter M_COUNT_P1 = M_COUNT+1; +parameter CL_M_COUNT_P1 = $clog2(M_COUNT_P1); + +integer i; + +// check configuration +initial begin + if (M_ID_WIDTH < S_ID_WIDTH+$clog2(S_COUNT)) begin + $error("Error: M_ID_WIDTH must be at least $clog2(S_COUNT) larger than S_ID_WIDTH (instance %m)"); + $finish; + end + + for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin + if (M_ADDR_WIDTH[i*32 +: 32] && (M_ADDR_WIDTH[i*32 +: 32] < 12 || M_ADDR_WIDTH[i*32 +: 32] > ADDR_WIDTH)) begin + $error("Error: value out of range (instance %m)"); + $finish; + end + end +end + +wire [S_COUNT*S_ID_WIDTH-1:0] int_s_axi_awid; +wire [S_COUNT*ADDR_WIDTH-1:0] int_s_axi_awaddr; +wire [S_COUNT*8-1:0] int_s_axi_awlen; +wire [S_COUNT*3-1:0] int_s_axi_awsize; +wire [S_COUNT*2-1:0] int_s_axi_awburst; +wire [S_COUNT-1:0] int_s_axi_awlock; +wire [S_COUNT*4-1:0] int_s_axi_awcache; +wire [S_COUNT*3-1:0] int_s_axi_awprot; +wire [S_COUNT*4-1:0] int_s_axi_awqos; +wire [S_COUNT*4-1:0] int_s_axi_awregion; +wire [S_COUNT*AWUSER_WIDTH-1:0] int_s_axi_awuser; +wire [S_COUNT-1:0] int_s_axi_awvalid; +wire [S_COUNT-1:0] int_s_axi_awready; + +wire [S_COUNT*M_COUNT-1:0] int_axi_awvalid; +wire [M_COUNT*S_COUNT-1:0] int_axi_awready; + +wire [S_COUNT*DATA_WIDTH-1:0] int_s_axi_wdata; +wire [S_COUNT*STRB_WIDTH-1:0] int_s_axi_wstrb; +wire [S_COUNT-1:0] int_s_axi_wlast; +wire [S_COUNT*WUSER_WIDTH-1:0] int_s_axi_wuser; +wire [S_COUNT-1:0] int_s_axi_wvalid; +wire [S_COUNT-1:0] int_s_axi_wready; + +wire [S_COUNT*M_COUNT-1:0] int_axi_wvalid; +wire [M_COUNT*S_COUNT-1:0] int_axi_wready; + +wire [M_COUNT*M_ID_WIDTH-1:0] int_m_axi_bid; +wire [M_COUNT*2-1:0] int_m_axi_bresp; +wire [M_COUNT*BUSER_WIDTH-1:0] int_m_axi_buser; +wire [M_COUNT-1:0] int_m_axi_bvalid; +wire [M_COUNT-1:0] int_m_axi_bready; + +wire [M_COUNT*S_COUNT-1:0] int_axi_bvalid; +wire [S_COUNT*M_COUNT-1:0] int_axi_bready; + +generate + + genvar m, n; + + for (m = 0; m < S_COUNT; m = m + 1) begin : s_ifaces + // address decode and admission control + wire [CL_M_COUNT-1:0] a_select; + + wire m_axi_avalid; + wire m_axi_aready; + + wire [CL_M_COUNT-1:0] m_wc_select; + wire m_wc_decerr; + wire m_wc_valid; + wire m_wc_ready; + + wire m_rc_decerr; + wire m_rc_valid; + wire m_rc_ready; + + wire [S_ID_WIDTH-1:0] s_cpl_id; + wire s_cpl_valid; + + axi_crossbar_addr #( + .S(m), + .S_COUNT(S_COUNT), + .M_COUNT(M_COUNT), + .ADDR_WIDTH(ADDR_WIDTH), + .ID_WIDTH(S_ID_WIDTH), + .S_THREADS(S_THREADS[m*32 +: 32]), + .S_ACCEPT(S_ACCEPT[m*32 +: 32]), + .M_REGIONS(M_REGIONS), + .M_BASE_ADDR(M_BASE_ADDR), + .M_ADDR_WIDTH(M_ADDR_WIDTH), + .M_CONNECT(M_CONNECT), + .M_SECURE(M_SECURE), + .WC_OUTPUT(1) + ) + addr_inst ( + .clk(clk), + .rst(rst), + + /* + * Address input + */ + .s_axi_aid(int_s_axi_awid[m*S_ID_WIDTH +: S_ID_WIDTH]), + .s_axi_aaddr(int_s_axi_awaddr[m*ADDR_WIDTH +: ADDR_WIDTH]), + .s_axi_aprot(int_s_axi_awprot[m*3 +: 3]), + .s_axi_aqos(int_s_axi_awqos[m*4 +: 4]), + .s_axi_avalid(int_s_axi_awvalid[m]), + .s_axi_aready(int_s_axi_awready[m]), + + /* + * Address output + */ + .m_axi_aregion(int_s_axi_awregion[m*4 +: 4]), + .m_select(a_select), + .m_axi_avalid(m_axi_avalid), + .m_axi_aready(m_axi_aready), + + /* + * Write command output + */ + .m_wc_select(m_wc_select), + .m_wc_decerr(m_wc_decerr), + .m_wc_valid(m_wc_valid), + .m_wc_ready(m_wc_ready), + + /* + * Response command output + */ + .m_rc_decerr(m_rc_decerr), + .m_rc_valid(m_rc_valid), + .m_rc_ready(m_rc_ready), + + /* + * Completion input + */ + .s_cpl_id(s_cpl_id), + .s_cpl_valid(s_cpl_valid) + ); + + assign int_axi_awvalid[m*M_COUNT +: M_COUNT] = m_axi_avalid << a_select; + assign m_axi_aready = int_axi_awready[a_select*S_COUNT+m]; + + // write command handling + reg [CL_M_COUNT-1:0] w_select_reg = 0, w_select_next; + reg w_drop_reg = 1'b0, w_drop_next; + reg w_select_valid_reg = 1'b0, w_select_valid_next; + + assign m_wc_ready = !w_select_valid_reg; + + always @* begin + w_select_next = w_select_reg; + w_drop_next = w_drop_reg && !(int_s_axi_wvalid[m] && int_s_axi_wready[m] && int_s_axi_wlast[m]); + w_select_valid_next = w_select_valid_reg && !(int_s_axi_wvalid[m] && int_s_axi_wready[m] && int_s_axi_wlast[m]); + + if (m_wc_valid && !w_select_valid_reg) begin + w_select_next = m_wc_select; + w_drop_next = m_wc_decerr; + w_select_valid_next = m_wc_valid; + end + end + + always @(posedge clk) begin + if (rst) begin + w_select_valid_reg <= 1'b0; + end else begin + w_select_valid_reg <= w_select_valid_next; + end + + w_select_reg <= w_select_next; + w_drop_reg <= w_drop_next; + end + + // write data forwarding + assign int_axi_wvalid[m*M_COUNT +: M_COUNT] = (int_s_axi_wvalid[m] && w_select_valid_reg && !w_drop_reg) << w_select_reg; + assign int_s_axi_wready[m] = int_axi_wready[w_select_reg*S_COUNT+m] || w_drop_reg; + + // decode error handling + reg [S_ID_WIDTH-1:0] decerr_m_axi_bid_reg = {S_ID_WIDTH{1'b0}}, decerr_m_axi_bid_next; + reg decerr_m_axi_bvalid_reg = 1'b0, decerr_m_axi_bvalid_next; + wire decerr_m_axi_bready; + + assign m_rc_ready = !decerr_m_axi_bvalid_reg; + + always @* begin + decerr_m_axi_bid_next = decerr_m_axi_bid_reg; + decerr_m_axi_bvalid_next = decerr_m_axi_bvalid_reg; + + if (decerr_m_axi_bvalid_reg) begin + if (decerr_m_axi_bready) begin + decerr_m_axi_bvalid_next = 1'b0; + end + end else if (m_rc_valid && m_rc_ready) begin + decerr_m_axi_bid_next = int_s_axi_awid[m*S_ID_WIDTH +: S_ID_WIDTH]; + decerr_m_axi_bvalid_next = 1'b1; + end + end + + always @(posedge clk) begin + if (rst) begin + decerr_m_axi_bvalid_reg <= 1'b0; + end else begin + decerr_m_axi_bvalid_reg <= decerr_m_axi_bvalid_next; + end + + decerr_m_axi_bid_reg <= decerr_m_axi_bid_next; + end + + // write response arbitration + wire [M_COUNT_P1-1:0] b_request; + wire [M_COUNT_P1-1:0] b_acknowledge; + wire [M_COUNT_P1-1:0] b_grant; + wire b_grant_valid; + wire [CL_M_COUNT_P1-1:0] b_grant_encoded; + + arbiter #( + .PORTS(M_COUNT_P1), + .ARB_TYPE_ROUND_ROBIN(1), + .ARB_BLOCK(1), + .ARB_BLOCK_ACK(1), + .ARB_LSB_HIGH_PRIORITY(1) + ) + b_arb_inst ( + .clk(clk), + .rst(rst), + .request(b_request), + .acknowledge(b_acknowledge), + .grant(b_grant), + .grant_valid(b_grant_valid), + .grant_encoded(b_grant_encoded) + ); + + // write response mux + wire [S_ID_WIDTH-1:0] m_axi_bid_mux = {decerr_m_axi_bid_reg, int_m_axi_bid} >> b_grant_encoded*M_ID_WIDTH; + wire [1:0] m_axi_bresp_mux = {2'b11, int_m_axi_bresp} >> b_grant_encoded*2; + wire [BUSER_WIDTH-1:0] m_axi_buser_mux = {{BUSER_WIDTH{1'b0}}, int_m_axi_buser} >> b_grant_encoded*BUSER_WIDTH; + wire m_axi_bvalid_mux = ({decerr_m_axi_bvalid_reg, int_m_axi_bvalid} >> b_grant_encoded) & b_grant_valid; + wire m_axi_bready_mux; + + assign int_axi_bready[m*M_COUNT +: M_COUNT] = (b_grant_valid && m_axi_bready_mux) << b_grant_encoded; + assign decerr_m_axi_bready = (b_grant_valid && m_axi_bready_mux) && (b_grant_encoded == M_COUNT_P1-1); + + for (n = 0; n < M_COUNT; n = n + 1) begin + assign b_request[n] = int_axi_bvalid[n*S_COUNT+m] && !b_grant[n]; + assign b_acknowledge[n] = b_grant[n] && int_axi_bvalid[n*S_COUNT+m] && m_axi_bready_mux; + end + + assign b_request[M_COUNT_P1-1] = decerr_m_axi_bvalid_reg && !b_grant[M_COUNT_P1-1]; + assign b_acknowledge[M_COUNT_P1-1] = b_grant[M_COUNT_P1-1] && decerr_m_axi_bvalid_reg && m_axi_bready_mux; + + assign s_cpl_id = m_axi_bid_mux; + assign s_cpl_valid = m_axi_bvalid_mux && m_axi_bready_mux; + + // S side register + axi_register_wr #( + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .STRB_WIDTH(STRB_WIDTH), + .ID_WIDTH(S_ID_WIDTH), + .AWUSER_ENABLE(AWUSER_ENABLE), + .AWUSER_WIDTH(AWUSER_WIDTH), + .WUSER_ENABLE(WUSER_ENABLE), + .WUSER_WIDTH(WUSER_WIDTH), + .BUSER_ENABLE(BUSER_ENABLE), + .BUSER_WIDTH(BUSER_WIDTH), + .AW_REG_TYPE(S_AW_REG_TYPE[m*2 +: 2]), + .W_REG_TYPE(S_W_REG_TYPE[m*2 +: 2]), + .B_REG_TYPE(S_B_REG_TYPE[m*2 +: 2]) + ) + reg_inst ( + .clk(clk), + .rst(rst), + .s_axi_awid(s_axi_awid[m*S_ID_WIDTH +: S_ID_WIDTH]), + .s_axi_awaddr(s_axi_awaddr[m*ADDR_WIDTH +: ADDR_WIDTH]), + .s_axi_awlen(s_axi_awlen[m*8 +: 8]), + .s_axi_awsize(s_axi_awsize[m*3 +: 3]), + .s_axi_awburst(s_axi_awburst[m*2 +: 2]), + .s_axi_awlock(s_axi_awlock[m]), + .s_axi_awcache(s_axi_awcache[m*4 +: 4]), + .s_axi_awprot(s_axi_awprot[m*3 +: 3]), + .s_axi_awqos(s_axi_awqos[m*4 +: 4]), + .s_axi_awregion(4'd0), + .s_axi_awuser(s_axi_awuser[m*AWUSER_WIDTH +: AWUSER_WIDTH]), + .s_axi_awvalid(s_axi_awvalid[m]), + .s_axi_awready(s_axi_awready[m]), + .s_axi_wdata(s_axi_wdata[m*DATA_WIDTH +: DATA_WIDTH]), + .s_axi_wstrb(s_axi_wstrb[m*STRB_WIDTH +: STRB_WIDTH]), + .s_axi_wlast(s_axi_wlast[m]), + .s_axi_wuser(s_axi_wuser[m*WUSER_WIDTH +: WUSER_WIDTH]), + .s_axi_wvalid(s_axi_wvalid[m]), + .s_axi_wready(s_axi_wready[m]), + .s_axi_bid(s_axi_bid[m*S_ID_WIDTH +: S_ID_WIDTH]), + .s_axi_bresp(s_axi_bresp[m*2 +: 2]), + .s_axi_buser(s_axi_buser[m*BUSER_WIDTH +: BUSER_WIDTH]), + .s_axi_bvalid(s_axi_bvalid[m]), + .s_axi_bready(s_axi_bready[m]), + .m_axi_awid(int_s_axi_awid[m*S_ID_WIDTH +: S_ID_WIDTH]), + .m_axi_awaddr(int_s_axi_awaddr[m*ADDR_WIDTH +: ADDR_WIDTH]), + .m_axi_awlen(int_s_axi_awlen[m*8 +: 8]), + .m_axi_awsize(int_s_axi_awsize[m*3 +: 3]), + .m_axi_awburst(int_s_axi_awburst[m*2 +: 2]), + .m_axi_awlock(int_s_axi_awlock[m]), + .m_axi_awcache(int_s_axi_awcache[m*4 +: 4]), + .m_axi_awprot(int_s_axi_awprot[m*3 +: 3]), + .m_axi_awqos(int_s_axi_awqos[m*4 +: 4]), + .m_axi_awregion(), + .m_axi_awuser(int_s_axi_awuser[m*AWUSER_WIDTH +: AWUSER_WIDTH]), + .m_axi_awvalid(int_s_axi_awvalid[m]), + .m_axi_awready(int_s_axi_awready[m]), + .m_axi_wdata(int_s_axi_wdata[m*DATA_WIDTH +: DATA_WIDTH]), + .m_axi_wstrb(int_s_axi_wstrb[m*STRB_WIDTH +: STRB_WIDTH]), + .m_axi_wlast(int_s_axi_wlast[m]), + .m_axi_wuser(int_s_axi_wuser[m*WUSER_WIDTH +: WUSER_WIDTH]), + .m_axi_wvalid(int_s_axi_wvalid[m]), + .m_axi_wready(int_s_axi_wready[m]), + .m_axi_bid(m_axi_bid_mux), + .m_axi_bresp(m_axi_bresp_mux), + .m_axi_buser(m_axi_buser_mux), + .m_axi_bvalid(m_axi_bvalid_mux), + .m_axi_bready(m_axi_bready_mux) + ); + end // s_ifaces + + for (n = 0; n < M_COUNT; n = n + 1) begin : m_ifaces + // in-flight transaction count + wire trans_start; + wire trans_complete; + reg [$clog2(M_ISSUE[n*32 +: 32]+1)-1:0] trans_count_reg = 0; + + wire trans_limit = trans_count_reg >= M_ISSUE[n*32 +: 32] && !trans_complete; + + always @(posedge clk) begin + if (rst) begin + trans_count_reg <= 0; + end else begin + if (trans_start && !trans_complete) begin + trans_count_reg <= trans_count_reg + 1; + end else if (!trans_start && trans_complete) begin + trans_count_reg <= trans_count_reg - 1; + end + end + end + + // address arbitration + reg [CL_S_COUNT-1:0] w_select_reg = 0, w_select_next; + reg w_select_valid_reg = 1'b0, w_select_valid_next; + reg w_select_new_reg = 1'b0, w_select_new_next; + + wire [S_COUNT-1:0] a_request; + wire [S_COUNT-1:0] a_acknowledge; + wire [S_COUNT-1:0] a_grant; + wire a_grant_valid; + wire [CL_S_COUNT-1:0] a_grant_encoded; + + arbiter #( + .PORTS(S_COUNT), + .ARB_TYPE_ROUND_ROBIN(1), + .ARB_BLOCK(1), + .ARB_BLOCK_ACK(1), + .ARB_LSB_HIGH_PRIORITY(1) + ) + a_arb_inst ( + .clk(clk), + .rst(rst), + .request(a_request), + .acknowledge(a_acknowledge), + .grant(a_grant), + .grant_valid(a_grant_valid), + .grant_encoded(a_grant_encoded) + ); + + // address mux + wire [M_ID_WIDTH-1:0] s_axi_awid_mux = int_s_axi_awid[a_grant_encoded*S_ID_WIDTH +: S_ID_WIDTH] | (a_grant_encoded << S_ID_WIDTH); + wire [ADDR_WIDTH-1:0] s_axi_awaddr_mux = int_s_axi_awaddr[a_grant_encoded*ADDR_WIDTH +: ADDR_WIDTH]; + wire [7:0] s_axi_awlen_mux = int_s_axi_awlen[a_grant_encoded*8 +: 8]; + wire [2:0] s_axi_awsize_mux = int_s_axi_awsize[a_grant_encoded*3 +: 3]; + wire [1:0] s_axi_awburst_mux = int_s_axi_awburst[a_grant_encoded*2 +: 2]; + wire s_axi_awlock_mux = int_s_axi_awlock[a_grant_encoded]; + wire [3:0] s_axi_awcache_mux = int_s_axi_awcache[a_grant_encoded*4 +: 4]; + wire [2:0] s_axi_awprot_mux = int_s_axi_awprot[a_grant_encoded*3 +: 3]; + wire [3:0] s_axi_awqos_mux = int_s_axi_awqos[a_grant_encoded*4 +: 4]; + wire [3:0] s_axi_awregion_mux = int_s_axi_awregion[a_grant_encoded*4 +: 4]; + wire [AWUSER_WIDTH-1:0] s_axi_awuser_mux = int_s_axi_awuser[a_grant_encoded*AWUSER_WIDTH +: AWUSER_WIDTH]; + wire s_axi_awvalid_mux = int_axi_awvalid[a_grant_encoded*M_COUNT+n] && a_grant_valid; + wire s_axi_awready_mux; + + assign int_axi_awready[n*S_COUNT +: S_COUNT] = (a_grant_valid && s_axi_awready_mux) << a_grant_encoded; + + for (m = 0; m < S_COUNT; m = m + 1) begin + assign a_request[m] = int_axi_awvalid[m*M_COUNT+n] && !a_grant[m] && !trans_limit && !w_select_valid_next; + assign a_acknowledge[m] = a_grant[m] && int_axi_awvalid[m*M_COUNT+n] && s_axi_awready_mux; + end + + assign trans_start = s_axi_awvalid_mux && s_axi_awready_mux && a_grant_valid; + + // write data mux + wire [DATA_WIDTH-1:0] s_axi_wdata_mux = int_s_axi_wdata[w_select_reg*DATA_WIDTH +: DATA_WIDTH]; + wire [STRB_WIDTH-1:0] s_axi_wstrb_mux = int_s_axi_wstrb[w_select_reg*STRB_WIDTH +: STRB_WIDTH]; + wire s_axi_wlast_mux = int_s_axi_wlast[w_select_reg]; + wire [WUSER_WIDTH-1:0] s_axi_wuser_mux = int_s_axi_wuser[w_select_reg*WUSER_WIDTH +: WUSER_WIDTH]; + wire s_axi_wvalid_mux = int_axi_wvalid[w_select_reg*M_COUNT+n] && w_select_valid_reg; + wire s_axi_wready_mux; + + assign int_axi_wready[n*S_COUNT +: S_COUNT] = (w_select_valid_reg && s_axi_wready_mux) << w_select_reg; + + // write data routing + always @* begin + w_select_next = w_select_reg; + w_select_valid_next = w_select_valid_reg && !(s_axi_wvalid_mux && s_axi_wready_mux && s_axi_wlast_mux); + w_select_new_next = w_select_new_reg || !a_grant_valid || a_acknowledge; + + if (a_grant_valid && !w_select_valid_reg && w_select_new_reg) begin + w_select_next = a_grant_encoded; + w_select_valid_next = a_grant_valid; + w_select_new_next = 1'b0; + end + end + + always @(posedge clk) begin + if (rst) begin + w_select_valid_reg <= 1'b0; + w_select_new_reg <= 1'b1; + end else begin + w_select_valid_reg <= w_select_valid_next; + w_select_new_reg <= w_select_new_next; + end + + w_select_reg <= w_select_next; + end + + // write response forwarding + wire [CL_S_COUNT-1:0] b_select = m_axi_bid[n*M_ID_WIDTH +: M_ID_WIDTH] >> S_ID_WIDTH; + + assign int_axi_bvalid[n*S_COUNT +: S_COUNT] = int_m_axi_bvalid[n] << b_select; + assign int_m_axi_bready[n] = int_axi_bready[b_select*M_COUNT+n]; + + assign trans_complete = int_m_axi_bvalid[n] && int_m_axi_bready[n]; + + // M side register + axi_register_wr #( + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .STRB_WIDTH(STRB_WIDTH), + .ID_WIDTH(M_ID_WIDTH), + .AWUSER_ENABLE(AWUSER_ENABLE), + .AWUSER_WIDTH(AWUSER_WIDTH), + .WUSER_ENABLE(WUSER_ENABLE), + .WUSER_WIDTH(WUSER_WIDTH), + .BUSER_ENABLE(BUSER_ENABLE), + .BUSER_WIDTH(BUSER_WIDTH), + .AW_REG_TYPE(M_AW_REG_TYPE[n*2 +: 2]), + .W_REG_TYPE(M_W_REG_TYPE[n*2 +: 2]), + .B_REG_TYPE(M_B_REG_TYPE[n*2 +: 2]) + ) + reg_inst ( + .clk(clk), + .rst(rst), + .s_axi_awid(s_axi_awid_mux), + .s_axi_awaddr(s_axi_awaddr_mux), + .s_axi_awlen(s_axi_awlen_mux), + .s_axi_awsize(s_axi_awsize_mux), + .s_axi_awburst(s_axi_awburst_mux), + .s_axi_awlock(s_axi_awlock_mux), + .s_axi_awcache(s_axi_awcache_mux), + .s_axi_awprot(s_axi_awprot_mux), + .s_axi_awqos(s_axi_awqos_mux), + .s_axi_awregion(s_axi_awregion_mux), + .s_axi_awuser(s_axi_awuser_mux), + .s_axi_awvalid(s_axi_awvalid_mux), + .s_axi_awready(s_axi_awready_mux), + .s_axi_wdata(s_axi_wdata_mux), + .s_axi_wstrb(s_axi_wstrb_mux), + .s_axi_wlast(s_axi_wlast_mux), + .s_axi_wuser(s_axi_wuser_mux), + .s_axi_wvalid(s_axi_wvalid_mux), + .s_axi_wready(s_axi_wready_mux), + .s_axi_bid(int_m_axi_bid[n*M_ID_WIDTH +: M_ID_WIDTH]), + .s_axi_bresp(int_m_axi_bresp[n*2 +: 2]), + .s_axi_buser(int_m_axi_buser[n*BUSER_WIDTH +: BUSER_WIDTH]), + .s_axi_bvalid(int_m_axi_bvalid[n]), + .s_axi_bready(int_m_axi_bready[n]), + .m_axi_awid(m_axi_awid[n*M_ID_WIDTH +: M_ID_WIDTH]), + .m_axi_awaddr(m_axi_awaddr[n*ADDR_WIDTH +: ADDR_WIDTH]), + .m_axi_awlen(m_axi_awlen[n*8 +: 8]), + .m_axi_awsize(m_axi_awsize[n*3 +: 3]), + .m_axi_awburst(m_axi_awburst[n*2 +: 2]), + .m_axi_awlock(m_axi_awlock[n]), + .m_axi_awcache(m_axi_awcache[n*4 +: 4]), + .m_axi_awprot(m_axi_awprot[n*3 +: 3]), + .m_axi_awqos(m_axi_awqos[n*4 +: 4]), + .m_axi_awregion(m_axi_awregion[n*4 +: 4]), + .m_axi_awuser(m_axi_awuser[n*AWUSER_WIDTH +: AWUSER_WIDTH]), + .m_axi_awvalid(m_axi_awvalid[n]), + .m_axi_awready(m_axi_awready[n]), + .m_axi_wdata(m_axi_wdata[n*DATA_WIDTH +: DATA_WIDTH]), + .m_axi_wstrb(m_axi_wstrb[n*STRB_WIDTH +: STRB_WIDTH]), + .m_axi_wlast(m_axi_wlast[n]), + .m_axi_wuser(m_axi_wuser[n*WUSER_WIDTH +: WUSER_WIDTH]), + .m_axi_wvalid(m_axi_wvalid[n]), + .m_axi_wready(m_axi_wready[n]), + .m_axi_bid(m_axi_bid[n*M_ID_WIDTH +: M_ID_WIDTH]), + .m_axi_bresp(m_axi_bresp[n*2 +: 2]), + .m_axi_buser(m_axi_buser[n*BUSER_WIDTH +: BUSER_WIDTH]), + .m_axi_bvalid(m_axi_bvalid[n]), + .m_axi_bready(m_axi_bready[n]) + ); + end // m_ifaces + +endgenerate + +endmodule + +`resetall diff --git a/xls/modules/zstd/axi_interconnect_wrapper.v b/xls/modules/zstd/external/axi_crossbar_wrapper.v similarity index 70% rename from xls/modules/zstd/axi_interconnect_wrapper.v rename to xls/modules/zstd/external/axi_crossbar_wrapper.v index 50017314f9..c244575e98 100644 --- a/xls/modules/zstd/axi_interconnect_wrapper.v +++ b/xls/modules/zstd/external/axi_crossbar_wrapper.v @@ -29,31 +29,150 @@ THE SOFTWARE. `default_nettype none /* - * AXI4 4x1 interconnect (wrapper) + * AXI4 4x1 crossbar (wrapper) */ -module axi_interconnect_wrapper # +module axi_crossbar_wrapper # ( + // Width of data bus in bits parameter DATA_WIDTH = 32, + // Width of address bus in bits parameter ADDR_WIDTH = 32, + // Width of wstrb (width of data bus in words) parameter STRB_WIDTH = (DATA_WIDTH/8), - parameter ID_WIDTH = 8, + // Input ID field width (from AXI masters) + parameter S_ID_WIDTH = 8, + // Output ID field width (towards AXI slaves) + // Additional bits required for response routing + parameter M_ID_WIDTH = S_ID_WIDTH+$clog2(S_COUNT), + // Propagate awuser signal parameter AWUSER_ENABLE = 0, + // Width of awuser signal parameter AWUSER_WIDTH = 1, + // Propagate wuser signal parameter WUSER_ENABLE = 0, + // Width of wuser signal parameter WUSER_WIDTH = 1, + // Propagate buser signal parameter BUSER_ENABLE = 0, + // Width of buser signal parameter BUSER_WIDTH = 1, + // Propagate aruser signal parameter ARUSER_ENABLE = 0, + // Width of aruser signal parameter ARUSER_WIDTH = 1, + // Propagate ruser signal parameter RUSER_ENABLE = 0, + // Width of ruser signal parameter RUSER_WIDTH = 1, - parameter FORWARD_ID = 0, + // Number of concurrent unique IDs + parameter S00_THREADS = 2, + // Number of concurrent operations + parameter S00_ACCEPT = 16, + // Number of concurrent unique IDs + parameter S01_THREADS = 2, + // Number of concurrent operations + parameter S01_ACCEPT = 16, + // Number of concurrent unique IDs + parameter S02_THREADS = 2, + // Number of concurrent operations + parameter S02_ACCEPT = 16, + // Number of concurrent unique IDs + parameter S03_THREADS = 2, + // Number of concurrent operations + parameter S03_ACCEPT = 16, + // Number of regions per master interface parameter M_REGIONS = 1, + // Master interface base addresses + // M_REGIONS concatenated fields of ADDR_WIDTH bits parameter M00_BASE_ADDR = 0, + // Master interface address widths + // M_REGIONS concatenated fields of 32 bits parameter M00_ADDR_WIDTH = {M_REGIONS{32'd24}}, + // Read connections between interfaces + // S_COUNT bits parameter M00_CONNECT_READ = 4'b1111, + // Write connections between interfaces + // S_COUNT bits parameter M00_CONNECT_WRITE = 4'b1111, - parameter M00_SECURE = 1'b0 + // Number of concurrent operations for each master interface + parameter M00_ISSUE = 4, + // Secure master (fail operations based on awprot/arprot) + parameter M00_SECURE = 0, + // Slave interface AW channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S00_AW_REG_TYPE = 0, + // Slave interface W channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S00_W_REG_TYPE = 0, + // Slave interface B channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S00_B_REG_TYPE = 1, + // Slave interface AR channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S00_AR_REG_TYPE = 0, + // Slave interface R channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S00_R_REG_TYPE = 2, + // Slave interface AW channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S01_AW_REG_TYPE = 0, + // Slave interface W channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S01_W_REG_TYPE = 0, + // Slave interface B channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S01_B_REG_TYPE = 1, + // Slave interface AR channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S01_AR_REG_TYPE = 0, + // Slave interface R channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S01_R_REG_TYPE = 2, + // Slave interface AW channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S02_AW_REG_TYPE = 0, + // Slave interface W channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S02_W_REG_TYPE = 0, + // Slave interface B channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S02_B_REG_TYPE = 1, + // Slave interface AR channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S02_AR_REG_TYPE = 0, + // Slave interface R channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S02_R_REG_TYPE = 2, + // Slave interface AW channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S03_AW_REG_TYPE = 0, + // Slave interface W channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S03_W_REG_TYPE = 0, + // Slave interface B channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S03_B_REG_TYPE = 1, + // Slave interface AR channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S03_AR_REG_TYPE = 0, + // Slave interface R channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter S03_R_REG_TYPE = 2, + // Master interface AW channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M00_AW_REG_TYPE = 1, + // Master interface W channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M00_W_REG_TYPE = 2, + // Master interface B channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M00_B_REG_TYPE = 0, + // Master interface AR channel register type (output) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M00_AR_REG_TYPE = 1, + // Master interface R channel register type (input) + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter M00_R_REG_TYPE = 0 ) ( input wire clk, @@ -62,7 +181,7 @@ module axi_interconnect_wrapper # /* * AXI slave interface */ - input wire [ID_WIDTH-1:0] s00_axi_awid, + input wire [S_ID_WIDTH-1:0] s00_axi_awid, input wire [ADDR_WIDTH-1:0] s00_axi_awaddr, input wire [7:0] s00_axi_awlen, input wire [2:0] s00_axi_awsize, @@ -80,12 +199,12 @@ module axi_interconnect_wrapper # input wire [WUSER_WIDTH-1:0] s00_axi_wuser, input wire s00_axi_wvalid, output wire s00_axi_wready, - output wire [ID_WIDTH-1:0] s00_axi_bid, + output wire [S_ID_WIDTH-1:0] s00_axi_bid, output wire [1:0] s00_axi_bresp, output wire [BUSER_WIDTH-1:0] s00_axi_buser, output wire s00_axi_bvalid, input wire s00_axi_bready, - input wire [ID_WIDTH-1:0] s00_axi_arid, + input wire [S_ID_WIDTH-1:0] s00_axi_arid, input wire [ADDR_WIDTH-1:0] s00_axi_araddr, input wire [7:0] s00_axi_arlen, input wire [2:0] s00_axi_arsize, @@ -97,7 +216,7 @@ module axi_interconnect_wrapper # input wire [ARUSER_WIDTH-1:0] s00_axi_aruser, input wire s00_axi_arvalid, output wire s00_axi_arready, - output wire [ID_WIDTH-1:0] s00_axi_rid, + output wire [S_ID_WIDTH-1:0] s00_axi_rid, output wire [DATA_WIDTH-1:0] s00_axi_rdata, output wire [1:0] s00_axi_rresp, output wire s00_axi_rlast, @@ -105,7 +224,7 @@ module axi_interconnect_wrapper # output wire s00_axi_rvalid, input wire s00_axi_rready, - input wire [ID_WIDTH-1:0] s01_axi_awid, + input wire [S_ID_WIDTH-1:0] s01_axi_awid, input wire [ADDR_WIDTH-1:0] s01_axi_awaddr, input wire [7:0] s01_axi_awlen, input wire [2:0] s01_axi_awsize, @@ -123,12 +242,12 @@ module axi_interconnect_wrapper # input wire [WUSER_WIDTH-1:0] s01_axi_wuser, input wire s01_axi_wvalid, output wire s01_axi_wready, - output wire [ID_WIDTH-1:0] s01_axi_bid, + output wire [S_ID_WIDTH-1:0] s01_axi_bid, output wire [1:0] s01_axi_bresp, output wire [BUSER_WIDTH-1:0] s01_axi_buser, output wire s01_axi_bvalid, input wire s01_axi_bready, - input wire [ID_WIDTH-1:0] s01_axi_arid, + input wire [S_ID_WIDTH-1:0] s01_axi_arid, input wire [ADDR_WIDTH-1:0] s01_axi_araddr, input wire [7:0] s01_axi_arlen, input wire [2:0] s01_axi_arsize, @@ -140,7 +259,7 @@ module axi_interconnect_wrapper # input wire [ARUSER_WIDTH-1:0] s01_axi_aruser, input wire s01_axi_arvalid, output wire s01_axi_arready, - output wire [ID_WIDTH-1:0] s01_axi_rid, + output wire [S_ID_WIDTH-1:0] s01_axi_rid, output wire [DATA_WIDTH-1:0] s01_axi_rdata, output wire [1:0] s01_axi_rresp, output wire s01_axi_rlast, @@ -148,7 +267,7 @@ module axi_interconnect_wrapper # output wire s01_axi_rvalid, input wire s01_axi_rready, - input wire [ID_WIDTH-1:0] s02_axi_awid, + input wire [S_ID_WIDTH-1:0] s02_axi_awid, input wire [ADDR_WIDTH-1:0] s02_axi_awaddr, input wire [7:0] s02_axi_awlen, input wire [2:0] s02_axi_awsize, @@ -166,12 +285,12 @@ module axi_interconnect_wrapper # input wire [WUSER_WIDTH-1:0] s02_axi_wuser, input wire s02_axi_wvalid, output wire s02_axi_wready, - output wire [ID_WIDTH-1:0] s02_axi_bid, + output wire [S_ID_WIDTH-1:0] s02_axi_bid, output wire [1:0] s02_axi_bresp, output wire [BUSER_WIDTH-1:0] s02_axi_buser, output wire s02_axi_bvalid, input wire s02_axi_bready, - input wire [ID_WIDTH-1:0] s02_axi_arid, + input wire [S_ID_WIDTH-1:0] s02_axi_arid, input wire [ADDR_WIDTH-1:0] s02_axi_araddr, input wire [7:0] s02_axi_arlen, input wire [2:0] s02_axi_arsize, @@ -183,7 +302,7 @@ module axi_interconnect_wrapper # input wire [ARUSER_WIDTH-1:0] s02_axi_aruser, input wire s02_axi_arvalid, output wire s02_axi_arready, - output wire [ID_WIDTH-1:0] s02_axi_rid, + output wire [S_ID_WIDTH-1:0] s02_axi_rid, output wire [DATA_WIDTH-1:0] s02_axi_rdata, output wire [1:0] s02_axi_rresp, output wire s02_axi_rlast, @@ -191,7 +310,7 @@ module axi_interconnect_wrapper # output wire s02_axi_rvalid, input wire s02_axi_rready, - input wire [ID_WIDTH-1:0] s03_axi_awid, + input wire [S_ID_WIDTH-1:0] s03_axi_awid, input wire [ADDR_WIDTH-1:0] s03_axi_awaddr, input wire [7:0] s03_axi_awlen, input wire [2:0] s03_axi_awsize, @@ -209,12 +328,12 @@ module axi_interconnect_wrapper # input wire [WUSER_WIDTH-1:0] s03_axi_wuser, input wire s03_axi_wvalid, output wire s03_axi_wready, - output wire [ID_WIDTH-1:0] s03_axi_bid, + output wire [S_ID_WIDTH-1:0] s03_axi_bid, output wire [1:0] s03_axi_bresp, output wire [BUSER_WIDTH-1:0] s03_axi_buser, output wire s03_axi_bvalid, input wire s03_axi_bready, - input wire [ID_WIDTH-1:0] s03_axi_arid, + input wire [S_ID_WIDTH-1:0] s03_axi_arid, input wire [ADDR_WIDTH-1:0] s03_axi_araddr, input wire [7:0] s03_axi_arlen, input wire [2:0] s03_axi_arsize, @@ -226,7 +345,7 @@ module axi_interconnect_wrapper # input wire [ARUSER_WIDTH-1:0] s03_axi_aruser, input wire s03_axi_arvalid, output wire s03_axi_arready, - output wire [ID_WIDTH-1:0] s03_axi_rid, + output wire [S_ID_WIDTH-1:0] s03_axi_rid, output wire [DATA_WIDTH-1:0] s03_axi_rdata, output wire [1:0] s03_axi_rresp, output wire s03_axi_rlast, @@ -237,7 +356,7 @@ module axi_interconnect_wrapper # /* * AXI master interface */ - output wire [ID_WIDTH-1:0] m00_axi_awid, + output wire [M_ID_WIDTH-1:0] m00_axi_awid, output wire [ADDR_WIDTH-1:0] m00_axi_awaddr, output wire [7:0] m00_axi_awlen, output wire [2:0] m00_axi_awsize, @@ -256,12 +375,12 @@ module axi_interconnect_wrapper # output wire [WUSER_WIDTH-1:0] m00_axi_wuser, output wire m00_axi_wvalid, input wire m00_axi_wready, - input wire [ID_WIDTH-1:0] m00_axi_bid, + input wire [M_ID_WIDTH-1:0] m00_axi_bid, input wire [1:0] m00_axi_bresp, input wire [BUSER_WIDTH-1:0] m00_axi_buser, input wire m00_axi_bvalid, output wire m00_axi_bready, - output wire [ID_WIDTH-1:0] m00_axi_arid, + output wire [M_ID_WIDTH-1:0] m00_axi_arid, output wire [ADDR_WIDTH-1:0] m00_axi_araddr, output wire [7:0] m00_axi_arlen, output wire [2:0] m00_axi_arsize, @@ -274,7 +393,7 @@ module axi_interconnect_wrapper # output wire [ARUSER_WIDTH-1:0] m00_axi_aruser, output wire m00_axi_arvalid, input wire m00_axi_arready, - input wire [ID_WIDTH-1:0] m00_axi_rid, + input wire [M_ID_WIDTH-1:0] m00_axi_rid, input wire [DATA_WIDTH-1:0] m00_axi_rdata, input wire [1:0] m00_axi_rresp, input wire m00_axi_rlast, @@ -299,17 +418,26 @@ function [S_COUNT-1:0] w_s(input [S_COUNT-1:0] val); w_s = val; endfunction +function [31:0] w_32(input [31:0] val); + w_32 = val; +endfunction + +function [1:0] w_2(input [1:0] val); + w_2 = val; +endfunction + function w_1(input val); w_1 = val; endfunction -axi_interconnect #( +axi_crossbar #( .S_COUNT(S_COUNT), .M_COUNT(M_COUNT), .DATA_WIDTH(DATA_WIDTH), .ADDR_WIDTH(ADDR_WIDTH), .STRB_WIDTH(STRB_WIDTH), - .ID_WIDTH(ID_WIDTH), + .S_ID_WIDTH(S_ID_WIDTH), + .M_ID_WIDTH(M_ID_WIDTH), .AWUSER_ENABLE(AWUSER_ENABLE), .AWUSER_WIDTH(AWUSER_WIDTH), .WUSER_ENABLE(WUSER_ENABLE), @@ -320,15 +448,27 @@ axi_interconnect #( .ARUSER_WIDTH(ARUSER_WIDTH), .RUSER_ENABLE(RUSER_ENABLE), .RUSER_WIDTH(RUSER_WIDTH), - .FORWARD_ID(FORWARD_ID), + .S_THREADS({ w_32(S03_THREADS), w_32(S02_THREADS), w_32(S01_THREADS), w_32(S00_THREADS) }), + .S_ACCEPT({ w_32(S03_ACCEPT), w_32(S02_ACCEPT), w_32(S01_ACCEPT), w_32(S00_ACCEPT) }), .M_REGIONS(M_REGIONS), .M_BASE_ADDR({ w_a_r(M00_BASE_ADDR) }), .M_ADDR_WIDTH({ w_32_r(M00_ADDR_WIDTH) }), .M_CONNECT_READ({ w_s(M00_CONNECT_READ) }), .M_CONNECT_WRITE({ w_s(M00_CONNECT_WRITE) }), - .M_SECURE({ w_1(M00_SECURE) }) + .M_ISSUE({ w_32(M00_ISSUE) }), + .M_SECURE({ w_1(M00_SECURE) }), + .S_AR_REG_TYPE({ w_2(S03_AR_REG_TYPE), w_2(S02_AR_REG_TYPE), w_2(S01_AR_REG_TYPE), w_2(S00_AR_REG_TYPE) }), + .S_R_REG_TYPE({ w_2(S03_R_REG_TYPE), w_2(S02_R_REG_TYPE), w_2(S01_R_REG_TYPE), w_2(S00_R_REG_TYPE) }), + .S_AW_REG_TYPE({ w_2(S03_AW_REG_TYPE), w_2(S02_AW_REG_TYPE), w_2(S01_AW_REG_TYPE), w_2(S00_AW_REG_TYPE) }), + .S_W_REG_TYPE({ w_2(S03_W_REG_TYPE), w_2(S02_W_REG_TYPE), w_2(S01_W_REG_TYPE), w_2(S00_W_REG_TYPE) }), + .S_B_REG_TYPE({ w_2(S03_B_REG_TYPE), w_2(S02_B_REG_TYPE), w_2(S01_B_REG_TYPE), w_2(S00_B_REG_TYPE) }), + .M_AR_REG_TYPE({ w_2(M00_AR_REG_TYPE) }), + .M_R_REG_TYPE({ w_2(M00_R_REG_TYPE) }), + .M_AW_REG_TYPE({ w_2(M00_AW_REG_TYPE) }), + .M_W_REG_TYPE({ w_2(M00_W_REG_TYPE) }), + .M_B_REG_TYPE({ w_2(M00_B_REG_TYPE) }) ) -axi_interconnect_inst ( +axi_crossbar_inst ( .clk(clk), .rst(rst), .s_axi_awid({ s03_axi_awid, s02_axi_awid, s01_axi_awid, s00_axi_awid }), diff --git a/xls/modules/zstd/external/axi_register_rd.v b/xls/modules/zstd/external/axi_register_rd.v new file mode 100644 index 0000000000..c0df03a03f --- /dev/null +++ b/xls/modules/zstd/external/axi_register_rd.v @@ -0,0 +1,530 @@ +/* + +Copyright (c) 2018 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 register (read) + */ +module axi_register_rd # +( + // Width of data bus in bits + parameter DATA_WIDTH = 32, + // Width of address bus in bits + parameter ADDR_WIDTH = 32, + // Width of wstrb (width of data bus in words) + parameter STRB_WIDTH = (DATA_WIDTH/8), + // Width of ID signal + parameter ID_WIDTH = 8, + // Propagate aruser signal + parameter ARUSER_ENABLE = 0, + // Width of aruser signal + parameter ARUSER_WIDTH = 1, + // Propagate ruser signal + parameter RUSER_ENABLE = 0, + // Width of ruser signal + parameter RUSER_WIDTH = 1, + // AR channel register type + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter AR_REG_TYPE = 1, + // R channel register type + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter R_REG_TYPE = 2 +) +( + input wire clk, + input wire rst, + + /* + * AXI slave interface + */ + input wire [ID_WIDTH-1:0] s_axi_arid, + input wire [ADDR_WIDTH-1:0] s_axi_araddr, + input wire [7:0] s_axi_arlen, + input wire [2:0] s_axi_arsize, + input wire [1:0] s_axi_arburst, + input wire s_axi_arlock, + input wire [3:0] s_axi_arcache, + input wire [2:0] s_axi_arprot, + input wire [3:0] s_axi_arqos, + input wire [3:0] s_axi_arregion, + input wire [ARUSER_WIDTH-1:0] s_axi_aruser, + input wire s_axi_arvalid, + output wire s_axi_arready, + output wire [ID_WIDTH-1:0] s_axi_rid, + output wire [DATA_WIDTH-1:0] s_axi_rdata, + output wire [1:0] s_axi_rresp, + output wire s_axi_rlast, + output wire [RUSER_WIDTH-1:0] s_axi_ruser, + output wire s_axi_rvalid, + input wire s_axi_rready, + + /* + * AXI master interface + */ + output wire [ID_WIDTH-1:0] m_axi_arid, + output wire [ADDR_WIDTH-1:0] m_axi_araddr, + output wire [7:0] m_axi_arlen, + output wire [2:0] m_axi_arsize, + output wire [1:0] m_axi_arburst, + output wire m_axi_arlock, + output wire [3:0] m_axi_arcache, + output wire [2:0] m_axi_arprot, + output wire [3:0] m_axi_arqos, + output wire [3:0] m_axi_arregion, + output wire [ARUSER_WIDTH-1:0] m_axi_aruser, + output wire m_axi_arvalid, + input wire m_axi_arready, + input wire [ID_WIDTH-1:0] m_axi_rid, + input wire [DATA_WIDTH-1:0] m_axi_rdata, + input wire [1:0] m_axi_rresp, + input wire m_axi_rlast, + input wire [RUSER_WIDTH-1:0] m_axi_ruser, + input wire m_axi_rvalid, + output wire m_axi_rready +); + +generate + +// AR channel + +if (AR_REG_TYPE > 1) begin +// skid buffer, no bubble cycles + +// datapath registers +reg s_axi_arready_reg = 1'b0; + +reg [ID_WIDTH-1:0] m_axi_arid_reg = {ID_WIDTH{1'b0}}; +reg [ADDR_WIDTH-1:0] m_axi_araddr_reg = {ADDR_WIDTH{1'b0}}; +reg [7:0] m_axi_arlen_reg = 8'd0; +reg [2:0] m_axi_arsize_reg = 3'd0; +reg [1:0] m_axi_arburst_reg = 2'd0; +reg m_axi_arlock_reg = 1'b0; +reg [3:0] m_axi_arcache_reg = 4'd0; +reg [2:0] m_axi_arprot_reg = 3'd0; +reg [3:0] m_axi_arqos_reg = 4'd0; +reg [3:0] m_axi_arregion_reg = 4'd0; +reg [ARUSER_WIDTH-1:0] m_axi_aruser_reg = {ARUSER_WIDTH{1'b0}}; +reg m_axi_arvalid_reg = 1'b0, m_axi_arvalid_next; + +reg [ID_WIDTH-1:0] temp_m_axi_arid_reg = {ID_WIDTH{1'b0}}; +reg [ADDR_WIDTH-1:0] temp_m_axi_araddr_reg = {ADDR_WIDTH{1'b0}}; +reg [7:0] temp_m_axi_arlen_reg = 8'd0; +reg [2:0] temp_m_axi_arsize_reg = 3'd0; +reg [1:0] temp_m_axi_arburst_reg = 2'd0; +reg temp_m_axi_arlock_reg = 1'b0; +reg [3:0] temp_m_axi_arcache_reg = 4'd0; +reg [2:0] temp_m_axi_arprot_reg = 3'd0; +reg [3:0] temp_m_axi_arqos_reg = 4'd0; +reg [3:0] temp_m_axi_arregion_reg = 4'd0; +reg [ARUSER_WIDTH-1:0] temp_m_axi_aruser_reg = {ARUSER_WIDTH{1'b0}}; +reg temp_m_axi_arvalid_reg = 1'b0, temp_m_axi_arvalid_next; + +// datapath control +reg store_axi_ar_input_to_output; +reg store_axi_ar_input_to_temp; +reg store_axi_ar_temp_to_output; + +assign s_axi_arready = s_axi_arready_reg; + +assign m_axi_arid = m_axi_arid_reg; +assign m_axi_araddr = m_axi_araddr_reg; +assign m_axi_arlen = m_axi_arlen_reg; +assign m_axi_arsize = m_axi_arsize_reg; +assign m_axi_arburst = m_axi_arburst_reg; +assign m_axi_arlock = m_axi_arlock_reg; +assign m_axi_arcache = m_axi_arcache_reg; +assign m_axi_arprot = m_axi_arprot_reg; +assign m_axi_arqos = m_axi_arqos_reg; +assign m_axi_arregion = m_axi_arregion_reg; +assign m_axi_aruser = ARUSER_ENABLE ? m_axi_aruser_reg : {ARUSER_WIDTH{1'b0}}; +assign m_axi_arvalid = m_axi_arvalid_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +wire s_axi_arready_early = m_axi_arready | (~temp_m_axi_arvalid_reg & (~m_axi_arvalid_reg | ~s_axi_arvalid)); + +always @* begin + // transfer sink ready state to source + m_axi_arvalid_next = m_axi_arvalid_reg; + temp_m_axi_arvalid_next = temp_m_axi_arvalid_reg; + + store_axi_ar_input_to_output = 1'b0; + store_axi_ar_input_to_temp = 1'b0; + store_axi_ar_temp_to_output = 1'b0; + + if (s_axi_arready_reg) begin + // input is ready + if (m_axi_arready | ~m_axi_arvalid_reg) begin + // output is ready or currently not valid, transfer data to output + m_axi_arvalid_next = s_axi_arvalid; + store_axi_ar_input_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_m_axi_arvalid_next = s_axi_arvalid; + store_axi_ar_input_to_temp = 1'b1; + end + end else if (m_axi_arready) begin + // input is not ready, but output is ready + m_axi_arvalid_next = temp_m_axi_arvalid_reg; + temp_m_axi_arvalid_next = 1'b0; + store_axi_ar_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + s_axi_arready_reg <= 1'b0; + m_axi_arvalid_reg <= 1'b0; + temp_m_axi_arvalid_reg <= 1'b0; + end else begin + s_axi_arready_reg <= s_axi_arready_early; + m_axi_arvalid_reg <= m_axi_arvalid_next; + temp_m_axi_arvalid_reg <= temp_m_axi_arvalid_next; + end + + // datapath + if (store_axi_ar_input_to_output) begin + m_axi_arid_reg <= s_axi_arid; + m_axi_araddr_reg <= s_axi_araddr; + m_axi_arlen_reg <= s_axi_arlen; + m_axi_arsize_reg <= s_axi_arsize; + m_axi_arburst_reg <= s_axi_arburst; + m_axi_arlock_reg <= s_axi_arlock; + m_axi_arcache_reg <= s_axi_arcache; + m_axi_arprot_reg <= s_axi_arprot; + m_axi_arqos_reg <= s_axi_arqos; + m_axi_arregion_reg <= s_axi_arregion; + m_axi_aruser_reg <= s_axi_aruser; + end else if (store_axi_ar_temp_to_output) begin + m_axi_arid_reg <= temp_m_axi_arid_reg; + m_axi_araddr_reg <= temp_m_axi_araddr_reg; + m_axi_arlen_reg <= temp_m_axi_arlen_reg; + m_axi_arsize_reg <= temp_m_axi_arsize_reg; + m_axi_arburst_reg <= temp_m_axi_arburst_reg; + m_axi_arlock_reg <= temp_m_axi_arlock_reg; + m_axi_arcache_reg <= temp_m_axi_arcache_reg; + m_axi_arprot_reg <= temp_m_axi_arprot_reg; + m_axi_arqos_reg <= temp_m_axi_arqos_reg; + m_axi_arregion_reg <= temp_m_axi_arregion_reg; + m_axi_aruser_reg <= temp_m_axi_aruser_reg; + end + + if (store_axi_ar_input_to_temp) begin + temp_m_axi_arid_reg <= s_axi_arid; + temp_m_axi_araddr_reg <= s_axi_araddr; + temp_m_axi_arlen_reg <= s_axi_arlen; + temp_m_axi_arsize_reg <= s_axi_arsize; + temp_m_axi_arburst_reg <= s_axi_arburst; + temp_m_axi_arlock_reg <= s_axi_arlock; + temp_m_axi_arcache_reg <= s_axi_arcache; + temp_m_axi_arprot_reg <= s_axi_arprot; + temp_m_axi_arqos_reg <= s_axi_arqos; + temp_m_axi_arregion_reg <= s_axi_arregion; + temp_m_axi_aruser_reg <= s_axi_aruser; + end +end + +end else if (AR_REG_TYPE == 1) begin +// simple register, inserts bubble cycles + +// datapath registers +reg s_axi_arready_reg = 1'b0; + +reg [ID_WIDTH-1:0] m_axi_arid_reg = {ID_WIDTH{1'b0}}; +reg [ADDR_WIDTH-1:0] m_axi_araddr_reg = {ADDR_WIDTH{1'b0}}; +reg [7:0] m_axi_arlen_reg = 8'd0; +reg [2:0] m_axi_arsize_reg = 3'd0; +reg [1:0] m_axi_arburst_reg = 2'd0; +reg m_axi_arlock_reg = 1'b0; +reg [3:0] m_axi_arcache_reg = 4'd0; +reg [2:0] m_axi_arprot_reg = 3'd0; +reg [3:0] m_axi_arqos_reg = 4'd0; +reg [3:0] m_axi_arregion_reg = 4'd0; +reg [ARUSER_WIDTH-1:0] m_axi_aruser_reg = {ARUSER_WIDTH{1'b0}}; +reg m_axi_arvalid_reg = 1'b0, m_axi_arvalid_next; + +// datapath control +reg store_axi_ar_input_to_output; + +assign s_axi_arready = s_axi_arready_reg; + +assign m_axi_arid = m_axi_arid_reg; +assign m_axi_araddr = m_axi_araddr_reg; +assign m_axi_arlen = m_axi_arlen_reg; +assign m_axi_arsize = m_axi_arsize_reg; +assign m_axi_arburst = m_axi_arburst_reg; +assign m_axi_arlock = m_axi_arlock_reg; +assign m_axi_arcache = m_axi_arcache_reg; +assign m_axi_arprot = m_axi_arprot_reg; +assign m_axi_arqos = m_axi_arqos_reg; +assign m_axi_arregion = m_axi_arregion_reg; +assign m_axi_aruser = ARUSER_ENABLE ? m_axi_aruser_reg : {ARUSER_WIDTH{1'b0}}; +assign m_axi_arvalid = m_axi_arvalid_reg; + +// enable ready input next cycle if output buffer will be empty +wire s_axi_arready_early = !m_axi_arvalid_next; + +always @* begin + // transfer sink ready state to source + m_axi_arvalid_next = m_axi_arvalid_reg; + + store_axi_ar_input_to_output = 1'b0; + + if (s_axi_arready_reg) begin + m_axi_arvalid_next = s_axi_arvalid; + store_axi_ar_input_to_output = 1'b1; + end else if (m_axi_arready) begin + m_axi_arvalid_next = 1'b0; + end +end + +always @(posedge clk) begin + if (rst) begin + s_axi_arready_reg <= 1'b0; + m_axi_arvalid_reg <= 1'b0; + end else begin + s_axi_arready_reg <= s_axi_arready_early; + m_axi_arvalid_reg <= m_axi_arvalid_next; + end + + // datapath + if (store_axi_ar_input_to_output) begin + m_axi_arid_reg <= s_axi_arid; + m_axi_araddr_reg <= s_axi_araddr; + m_axi_arlen_reg <= s_axi_arlen; + m_axi_arsize_reg <= s_axi_arsize; + m_axi_arburst_reg <= s_axi_arburst; + m_axi_arlock_reg <= s_axi_arlock; + m_axi_arcache_reg <= s_axi_arcache; + m_axi_arprot_reg <= s_axi_arprot; + m_axi_arqos_reg <= s_axi_arqos; + m_axi_arregion_reg <= s_axi_arregion; + m_axi_aruser_reg <= s_axi_aruser; + end +end + +end else begin + + // bypass AR channel + assign m_axi_arid = s_axi_arid; + assign m_axi_araddr = s_axi_araddr; + assign m_axi_arlen = s_axi_arlen; + assign m_axi_arsize = s_axi_arsize; + assign m_axi_arburst = s_axi_arburst; + assign m_axi_arlock = s_axi_arlock; + assign m_axi_arcache = s_axi_arcache; + assign m_axi_arprot = s_axi_arprot; + assign m_axi_arqos = s_axi_arqos; + assign m_axi_arregion = s_axi_arregion; + assign m_axi_aruser = ARUSER_ENABLE ? s_axi_aruser : {ARUSER_WIDTH{1'b0}}; + assign m_axi_arvalid = s_axi_arvalid; + assign s_axi_arready = m_axi_arready; + +end + +// R channel + +if (R_REG_TYPE > 1) begin +// skid buffer, no bubble cycles + +// datapath registers +reg m_axi_rready_reg = 1'b0; + +reg [ID_WIDTH-1:0] s_axi_rid_reg = {ID_WIDTH{1'b0}}; +reg [DATA_WIDTH-1:0] s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; +reg [1:0] s_axi_rresp_reg = 2'b0; +reg s_axi_rlast_reg = 1'b0; +reg [RUSER_WIDTH-1:0] s_axi_ruser_reg = {RUSER_WIDTH{1'b0}}; +reg s_axi_rvalid_reg = 1'b0, s_axi_rvalid_next; + +reg [ID_WIDTH-1:0] temp_s_axi_rid_reg = {ID_WIDTH{1'b0}}; +reg [DATA_WIDTH-1:0] temp_s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; +reg [1:0] temp_s_axi_rresp_reg = 2'b0; +reg temp_s_axi_rlast_reg = 1'b0; +reg [RUSER_WIDTH-1:0] temp_s_axi_ruser_reg = {RUSER_WIDTH{1'b0}}; +reg temp_s_axi_rvalid_reg = 1'b0, temp_s_axi_rvalid_next; + +// datapath control +reg store_axi_r_input_to_output; +reg store_axi_r_input_to_temp; +reg store_axi_r_temp_to_output; + +assign m_axi_rready = m_axi_rready_reg; + +assign s_axi_rid = s_axi_rid_reg; +assign s_axi_rdata = s_axi_rdata_reg; +assign s_axi_rresp = s_axi_rresp_reg; +assign s_axi_rlast = s_axi_rlast_reg; +assign s_axi_ruser = RUSER_ENABLE ? s_axi_ruser_reg : {RUSER_WIDTH{1'b0}}; +assign s_axi_rvalid = s_axi_rvalid_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +wire m_axi_rready_early = s_axi_rready | (~temp_s_axi_rvalid_reg & (~s_axi_rvalid_reg | ~m_axi_rvalid)); + +always @* begin + // transfer sink ready state to source + s_axi_rvalid_next = s_axi_rvalid_reg; + temp_s_axi_rvalid_next = temp_s_axi_rvalid_reg; + + store_axi_r_input_to_output = 1'b0; + store_axi_r_input_to_temp = 1'b0; + store_axi_r_temp_to_output = 1'b0; + + if (m_axi_rready_reg) begin + // input is ready + if (s_axi_rready | ~s_axi_rvalid_reg) begin + // output is ready or currently not valid, transfer data to output + s_axi_rvalid_next = m_axi_rvalid; + store_axi_r_input_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_s_axi_rvalid_next = m_axi_rvalid; + store_axi_r_input_to_temp = 1'b1; + end + end else if (s_axi_rready) begin + // input is not ready, but output is ready + s_axi_rvalid_next = temp_s_axi_rvalid_reg; + temp_s_axi_rvalid_next = 1'b0; + store_axi_r_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + m_axi_rready_reg <= 1'b0; + s_axi_rvalid_reg <= 1'b0; + temp_s_axi_rvalid_reg <= 1'b0; + end else begin + m_axi_rready_reg <= m_axi_rready_early; + s_axi_rvalid_reg <= s_axi_rvalid_next; + temp_s_axi_rvalid_reg <= temp_s_axi_rvalid_next; + end + + // datapath + if (store_axi_r_input_to_output) begin + s_axi_rid_reg <= m_axi_rid; + s_axi_rdata_reg <= m_axi_rdata; + s_axi_rresp_reg <= m_axi_rresp; + s_axi_rlast_reg <= m_axi_rlast; + s_axi_ruser_reg <= m_axi_ruser; + end else if (store_axi_r_temp_to_output) begin + s_axi_rid_reg <= temp_s_axi_rid_reg; + s_axi_rdata_reg <= temp_s_axi_rdata_reg; + s_axi_rresp_reg <= temp_s_axi_rresp_reg; + s_axi_rlast_reg <= temp_s_axi_rlast_reg; + s_axi_ruser_reg <= temp_s_axi_ruser_reg; + end + + if (store_axi_r_input_to_temp) begin + temp_s_axi_rid_reg <= m_axi_rid; + temp_s_axi_rdata_reg <= m_axi_rdata; + temp_s_axi_rresp_reg <= m_axi_rresp; + temp_s_axi_rlast_reg <= m_axi_rlast; + temp_s_axi_ruser_reg <= m_axi_ruser; + end +end + +end else if (R_REG_TYPE == 1) begin +// simple register, inserts bubble cycles + +// datapath registers +reg m_axi_rready_reg = 1'b0; + +reg [ID_WIDTH-1:0] s_axi_rid_reg = {ID_WIDTH{1'b0}}; +reg [DATA_WIDTH-1:0] s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; +reg [1:0] s_axi_rresp_reg = 2'b0; +reg s_axi_rlast_reg = 1'b0; +reg [RUSER_WIDTH-1:0] s_axi_ruser_reg = {RUSER_WIDTH{1'b0}}; +reg s_axi_rvalid_reg = 1'b0, s_axi_rvalid_next; + +// datapath control +reg store_axi_r_input_to_output; + +assign m_axi_rready = m_axi_rready_reg; + +assign s_axi_rid = s_axi_rid_reg; +assign s_axi_rdata = s_axi_rdata_reg; +assign s_axi_rresp = s_axi_rresp_reg; +assign s_axi_rlast = s_axi_rlast_reg; +assign s_axi_ruser = RUSER_ENABLE ? s_axi_ruser_reg : {RUSER_WIDTH{1'b0}}; +assign s_axi_rvalid = s_axi_rvalid_reg; + +// enable ready input next cycle if output buffer will be empty +wire m_axi_rready_early = !s_axi_rvalid_next; + +always @* begin + // transfer sink ready state to source + s_axi_rvalid_next = s_axi_rvalid_reg; + + store_axi_r_input_to_output = 1'b0; + + if (m_axi_rready_reg) begin + s_axi_rvalid_next = m_axi_rvalid; + store_axi_r_input_to_output = 1'b1; + end else if (s_axi_rready) begin + s_axi_rvalid_next = 1'b0; + end +end + +always @(posedge clk) begin + if (rst) begin + m_axi_rready_reg <= 1'b0; + s_axi_rvalid_reg <= 1'b0; + end else begin + m_axi_rready_reg <= m_axi_rready_early; + s_axi_rvalid_reg <= s_axi_rvalid_next; + end + + // datapath + if (store_axi_r_input_to_output) begin + s_axi_rid_reg <= m_axi_rid; + s_axi_rdata_reg <= m_axi_rdata; + s_axi_rresp_reg <= m_axi_rresp; + s_axi_rlast_reg <= m_axi_rlast; + s_axi_ruser_reg <= m_axi_ruser; + end +end + +end else begin + + // bypass R channel + assign s_axi_rid = m_axi_rid; + assign s_axi_rdata = m_axi_rdata; + assign s_axi_rresp = m_axi_rresp; + assign s_axi_rlast = m_axi_rlast; + assign s_axi_ruser = RUSER_ENABLE ? m_axi_ruser : {RUSER_WIDTH{1'b0}}; + assign s_axi_rvalid = m_axi_rvalid; + assign m_axi_rready = s_axi_rready; + +end + +endgenerate + +endmodule + +`resetall diff --git a/xls/modules/zstd/external/axi_register_wr.v b/xls/modules/zstd/external/axi_register_wr.v new file mode 100644 index 0000000000..9176d6ba95 --- /dev/null +++ b/xls/modules/zstd/external/axi_register_wr.v @@ -0,0 +1,691 @@ +/* + +Copyright (c) 2018 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`resetall +`timescale 1ns / 1ps +`default_nettype none + +/* + * AXI4 register (write) + */ +module axi_register_wr # +( + // Width of data bus in bits + parameter DATA_WIDTH = 32, + // Width of address bus in bits + parameter ADDR_WIDTH = 32, + // Width of wstrb (width of data bus in words) + parameter STRB_WIDTH = (DATA_WIDTH/8), + // Width of ID signal + parameter ID_WIDTH = 8, + // Propagate awuser signal + parameter AWUSER_ENABLE = 0, + // Width of awuser signal + parameter AWUSER_WIDTH = 1, + // Propagate wuser signal + parameter WUSER_ENABLE = 0, + // Width of wuser signal + parameter WUSER_WIDTH = 1, + // Propagate buser signal + parameter BUSER_ENABLE = 0, + // Width of buser signal + parameter BUSER_WIDTH = 1, + // AW channel register type + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter AW_REG_TYPE = 1, + // W channel register type + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter W_REG_TYPE = 2, + // B channel register type + // 0 to bypass, 1 for simple buffer, 2 for skid buffer + parameter B_REG_TYPE = 1 +) +( + input wire clk, + input wire rst, + + /* + * AXI slave interface + */ + input wire [ID_WIDTH-1:0] s_axi_awid, + input wire [ADDR_WIDTH-1:0] s_axi_awaddr, + input wire [7:0] s_axi_awlen, + input wire [2:0] s_axi_awsize, + input wire [1:0] s_axi_awburst, + input wire s_axi_awlock, + input wire [3:0] s_axi_awcache, + input wire [2:0] s_axi_awprot, + input wire [3:0] s_axi_awqos, + input wire [3:0] s_axi_awregion, + input wire [AWUSER_WIDTH-1:0] s_axi_awuser, + input wire s_axi_awvalid, + output wire s_axi_awready, + input wire [DATA_WIDTH-1:0] s_axi_wdata, + input wire [STRB_WIDTH-1:0] s_axi_wstrb, + input wire s_axi_wlast, + input wire [WUSER_WIDTH-1:0] s_axi_wuser, + input wire s_axi_wvalid, + output wire s_axi_wready, + output wire [ID_WIDTH-1:0] s_axi_bid, + output wire [1:0] s_axi_bresp, + output wire [BUSER_WIDTH-1:0] s_axi_buser, + output wire s_axi_bvalid, + input wire s_axi_bready, + + /* + * AXI master interface + */ + output wire [ID_WIDTH-1:0] m_axi_awid, + output wire [ADDR_WIDTH-1:0] m_axi_awaddr, + output wire [7:0] m_axi_awlen, + output wire [2:0] m_axi_awsize, + output wire [1:0] m_axi_awburst, + output wire m_axi_awlock, + output wire [3:0] m_axi_awcache, + output wire [2:0] m_axi_awprot, + output wire [3:0] m_axi_awqos, + output wire [3:0] m_axi_awregion, + output wire [AWUSER_WIDTH-1:0] m_axi_awuser, + output wire m_axi_awvalid, + input wire m_axi_awready, + output wire [DATA_WIDTH-1:0] m_axi_wdata, + output wire [STRB_WIDTH-1:0] m_axi_wstrb, + output wire m_axi_wlast, + output wire [WUSER_WIDTH-1:0] m_axi_wuser, + output wire m_axi_wvalid, + input wire m_axi_wready, + input wire [ID_WIDTH-1:0] m_axi_bid, + input wire [1:0] m_axi_bresp, + input wire [BUSER_WIDTH-1:0] m_axi_buser, + input wire m_axi_bvalid, + output wire m_axi_bready +); + +generate + +// AW channel + +if (AW_REG_TYPE > 1) begin +// skid buffer, no bubble cycles + +// datapath registers +reg s_axi_awready_reg = 1'b0; + +reg [ID_WIDTH-1:0] m_axi_awid_reg = {ID_WIDTH{1'b0}}; +reg [ADDR_WIDTH-1:0] m_axi_awaddr_reg = {ADDR_WIDTH{1'b0}}; +reg [7:0] m_axi_awlen_reg = 8'd0; +reg [2:0] m_axi_awsize_reg = 3'd0; +reg [1:0] m_axi_awburst_reg = 2'd0; +reg m_axi_awlock_reg = 1'b0; +reg [3:0] m_axi_awcache_reg = 4'd0; +reg [2:0] m_axi_awprot_reg = 3'd0; +reg [3:0] m_axi_awqos_reg = 4'd0; +reg [3:0] m_axi_awregion_reg = 4'd0; +reg [AWUSER_WIDTH-1:0] m_axi_awuser_reg = {AWUSER_WIDTH{1'b0}}; +reg m_axi_awvalid_reg = 1'b0, m_axi_awvalid_next; + +reg [ID_WIDTH-1:0] temp_m_axi_awid_reg = {ID_WIDTH{1'b0}}; +reg [ADDR_WIDTH-1:0] temp_m_axi_awaddr_reg = {ADDR_WIDTH{1'b0}}; +reg [7:0] temp_m_axi_awlen_reg = 8'd0; +reg [2:0] temp_m_axi_awsize_reg = 3'd0; +reg [1:0] temp_m_axi_awburst_reg = 2'd0; +reg temp_m_axi_awlock_reg = 1'b0; +reg [3:0] temp_m_axi_awcache_reg = 4'd0; +reg [2:0] temp_m_axi_awprot_reg = 3'd0; +reg [3:0] temp_m_axi_awqos_reg = 4'd0; +reg [3:0] temp_m_axi_awregion_reg = 4'd0; +reg [AWUSER_WIDTH-1:0] temp_m_axi_awuser_reg = {AWUSER_WIDTH{1'b0}}; +reg temp_m_axi_awvalid_reg = 1'b0, temp_m_axi_awvalid_next; + +// datapath control +reg store_axi_aw_input_to_output; +reg store_axi_aw_input_to_temp; +reg store_axi_aw_temp_to_output; + +assign s_axi_awready = s_axi_awready_reg; + +assign m_axi_awid = m_axi_awid_reg; +assign m_axi_awaddr = m_axi_awaddr_reg; +assign m_axi_awlen = m_axi_awlen_reg; +assign m_axi_awsize = m_axi_awsize_reg; +assign m_axi_awburst = m_axi_awburst_reg; +assign m_axi_awlock = m_axi_awlock_reg; +assign m_axi_awcache = m_axi_awcache_reg; +assign m_axi_awprot = m_axi_awprot_reg; +assign m_axi_awqos = m_axi_awqos_reg; +assign m_axi_awregion = m_axi_awregion_reg; +assign m_axi_awuser = AWUSER_ENABLE ? m_axi_awuser_reg : {AWUSER_WIDTH{1'b0}}; +assign m_axi_awvalid = m_axi_awvalid_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +wire s_axi_awready_early = m_axi_awready | (~temp_m_axi_awvalid_reg & (~m_axi_awvalid_reg | ~s_axi_awvalid)); + +always @* begin + // transfer sink ready state to source + m_axi_awvalid_next = m_axi_awvalid_reg; + temp_m_axi_awvalid_next = temp_m_axi_awvalid_reg; + + store_axi_aw_input_to_output = 1'b0; + store_axi_aw_input_to_temp = 1'b0; + store_axi_aw_temp_to_output = 1'b0; + + if (s_axi_awready_reg) begin + // input is ready + if (m_axi_awready | ~m_axi_awvalid_reg) begin + // output is ready or currently not valid, transfer data to output + m_axi_awvalid_next = s_axi_awvalid; + store_axi_aw_input_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_m_axi_awvalid_next = s_axi_awvalid; + store_axi_aw_input_to_temp = 1'b1; + end + end else if (m_axi_awready) begin + // input is not ready, but output is ready + m_axi_awvalid_next = temp_m_axi_awvalid_reg; + temp_m_axi_awvalid_next = 1'b0; + store_axi_aw_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + s_axi_awready_reg <= 1'b0; + m_axi_awvalid_reg <= 1'b0; + temp_m_axi_awvalid_reg <= 1'b0; + end else begin + s_axi_awready_reg <= s_axi_awready_early; + m_axi_awvalid_reg <= m_axi_awvalid_next; + temp_m_axi_awvalid_reg <= temp_m_axi_awvalid_next; + end + + // datapath + if (store_axi_aw_input_to_output) begin + m_axi_awid_reg <= s_axi_awid; + m_axi_awaddr_reg <= s_axi_awaddr; + m_axi_awlen_reg <= s_axi_awlen; + m_axi_awsize_reg <= s_axi_awsize; + m_axi_awburst_reg <= s_axi_awburst; + m_axi_awlock_reg <= s_axi_awlock; + m_axi_awcache_reg <= s_axi_awcache; + m_axi_awprot_reg <= s_axi_awprot; + m_axi_awqos_reg <= s_axi_awqos; + m_axi_awregion_reg <= s_axi_awregion; + m_axi_awuser_reg <= s_axi_awuser; + end else if (store_axi_aw_temp_to_output) begin + m_axi_awid_reg <= temp_m_axi_awid_reg; + m_axi_awaddr_reg <= temp_m_axi_awaddr_reg; + m_axi_awlen_reg <= temp_m_axi_awlen_reg; + m_axi_awsize_reg <= temp_m_axi_awsize_reg; + m_axi_awburst_reg <= temp_m_axi_awburst_reg; + m_axi_awlock_reg <= temp_m_axi_awlock_reg; + m_axi_awcache_reg <= temp_m_axi_awcache_reg; + m_axi_awprot_reg <= temp_m_axi_awprot_reg; + m_axi_awqos_reg <= temp_m_axi_awqos_reg; + m_axi_awregion_reg <= temp_m_axi_awregion_reg; + m_axi_awuser_reg <= temp_m_axi_awuser_reg; + end + + if (store_axi_aw_input_to_temp) begin + temp_m_axi_awid_reg <= s_axi_awid; + temp_m_axi_awaddr_reg <= s_axi_awaddr; + temp_m_axi_awlen_reg <= s_axi_awlen; + temp_m_axi_awsize_reg <= s_axi_awsize; + temp_m_axi_awburst_reg <= s_axi_awburst; + temp_m_axi_awlock_reg <= s_axi_awlock; + temp_m_axi_awcache_reg <= s_axi_awcache; + temp_m_axi_awprot_reg <= s_axi_awprot; + temp_m_axi_awqos_reg <= s_axi_awqos; + temp_m_axi_awregion_reg <= s_axi_awregion; + temp_m_axi_awuser_reg <= s_axi_awuser; + end +end + +end else if (AW_REG_TYPE == 1) begin +// simple register, inserts bubble cycles + +// datapath registers +reg s_axi_awready_reg = 1'b0; + +reg [ID_WIDTH-1:0] m_axi_awid_reg = {ID_WIDTH{1'b0}}; +reg [ADDR_WIDTH-1:0] m_axi_awaddr_reg = {ADDR_WIDTH{1'b0}}; +reg [7:0] m_axi_awlen_reg = 8'd0; +reg [2:0] m_axi_awsize_reg = 3'd0; +reg [1:0] m_axi_awburst_reg = 2'd0; +reg m_axi_awlock_reg = 1'b0; +reg [3:0] m_axi_awcache_reg = 4'd0; +reg [2:0] m_axi_awprot_reg = 3'd0; +reg [3:0] m_axi_awqos_reg = 4'd0; +reg [3:0] m_axi_awregion_reg = 4'd0; +reg [AWUSER_WIDTH-1:0] m_axi_awuser_reg = {AWUSER_WIDTH{1'b0}}; +reg m_axi_awvalid_reg = 1'b0, m_axi_awvalid_next; + +// datapath control +reg store_axi_aw_input_to_output; + +assign s_axi_awready = s_axi_awready_reg; + +assign m_axi_awid = m_axi_awid_reg; +assign m_axi_awaddr = m_axi_awaddr_reg; +assign m_axi_awlen = m_axi_awlen_reg; +assign m_axi_awsize = m_axi_awsize_reg; +assign m_axi_awburst = m_axi_awburst_reg; +assign m_axi_awlock = m_axi_awlock_reg; +assign m_axi_awcache = m_axi_awcache_reg; +assign m_axi_awprot = m_axi_awprot_reg; +assign m_axi_awqos = m_axi_awqos_reg; +assign m_axi_awregion = m_axi_awregion_reg; +assign m_axi_awuser = AWUSER_ENABLE ? m_axi_awuser_reg : {AWUSER_WIDTH{1'b0}}; +assign m_axi_awvalid = m_axi_awvalid_reg; + +// enable ready input next cycle if output buffer will be empty +wire s_axi_awready_eawly = !m_axi_awvalid_next; + +always @* begin + // transfer sink ready state to source + m_axi_awvalid_next = m_axi_awvalid_reg; + + store_axi_aw_input_to_output = 1'b0; + + if (s_axi_awready_reg) begin + m_axi_awvalid_next = s_axi_awvalid; + store_axi_aw_input_to_output = 1'b1; + end else if (m_axi_awready) begin + m_axi_awvalid_next = 1'b0; + end +end + +always @(posedge clk) begin + if (rst) begin + s_axi_awready_reg <= 1'b0; + m_axi_awvalid_reg <= 1'b0; + end else begin + s_axi_awready_reg <= s_axi_awready_eawly; + m_axi_awvalid_reg <= m_axi_awvalid_next; + end + + // datapath + if (store_axi_aw_input_to_output) begin + m_axi_awid_reg <= s_axi_awid; + m_axi_awaddr_reg <= s_axi_awaddr; + m_axi_awlen_reg <= s_axi_awlen; + m_axi_awsize_reg <= s_axi_awsize; + m_axi_awburst_reg <= s_axi_awburst; + m_axi_awlock_reg <= s_axi_awlock; + m_axi_awcache_reg <= s_axi_awcache; + m_axi_awprot_reg <= s_axi_awprot; + m_axi_awqos_reg <= s_axi_awqos; + m_axi_awregion_reg <= s_axi_awregion; + m_axi_awuser_reg <= s_axi_awuser; + end +end + +end else begin + + // bypass AW channel + assign m_axi_awid = s_axi_awid; + assign m_axi_awaddr = s_axi_awaddr; + assign m_axi_awlen = s_axi_awlen; + assign m_axi_awsize = s_axi_awsize; + assign m_axi_awburst = s_axi_awburst; + assign m_axi_awlock = s_axi_awlock; + assign m_axi_awcache = s_axi_awcache; + assign m_axi_awprot = s_axi_awprot; + assign m_axi_awqos = s_axi_awqos; + assign m_axi_awregion = s_axi_awregion; + assign m_axi_awuser = AWUSER_ENABLE ? s_axi_awuser : {AWUSER_WIDTH{1'b0}}; + assign m_axi_awvalid = s_axi_awvalid; + assign s_axi_awready = m_axi_awready; + +end + +// W channel + +if (W_REG_TYPE > 1) begin +// skid buffer, no bubble cycles + +// datapath registers +reg s_axi_wready_reg = 1'b0; + +reg [DATA_WIDTH-1:0] m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; +reg [STRB_WIDTH-1:0] m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; +reg m_axi_wlast_reg = 1'b0; +reg [WUSER_WIDTH-1:0] m_axi_wuser_reg = {WUSER_WIDTH{1'b0}}; +reg m_axi_wvalid_reg = 1'b0, m_axi_wvalid_next; + +reg [DATA_WIDTH-1:0] temp_m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; +reg [STRB_WIDTH-1:0] temp_m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; +reg temp_m_axi_wlast_reg = 1'b0; +reg [WUSER_WIDTH-1:0] temp_m_axi_wuser_reg = {WUSER_WIDTH{1'b0}}; +reg temp_m_axi_wvalid_reg = 1'b0, temp_m_axi_wvalid_next; + +// datapath control +reg store_axi_w_input_to_output; +reg store_axi_w_input_to_temp; +reg store_axi_w_temp_to_output; + +assign s_axi_wready = s_axi_wready_reg; + +assign m_axi_wdata = m_axi_wdata_reg; +assign m_axi_wstrb = m_axi_wstrb_reg; +assign m_axi_wlast = m_axi_wlast_reg; +assign m_axi_wuser = WUSER_ENABLE ? m_axi_wuser_reg : {WUSER_WIDTH{1'b0}}; +assign m_axi_wvalid = m_axi_wvalid_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +wire s_axi_wready_early = m_axi_wready | (~temp_m_axi_wvalid_reg & (~m_axi_wvalid_reg | ~s_axi_wvalid)); + +always @* begin + // transfer sink ready state to source + m_axi_wvalid_next = m_axi_wvalid_reg; + temp_m_axi_wvalid_next = temp_m_axi_wvalid_reg; + + store_axi_w_input_to_output = 1'b0; + store_axi_w_input_to_temp = 1'b0; + store_axi_w_temp_to_output = 1'b0; + + if (s_axi_wready_reg) begin + // input is ready + if (m_axi_wready | ~m_axi_wvalid_reg) begin + // output is ready or currently not valid, transfer data to output + m_axi_wvalid_next = s_axi_wvalid; + store_axi_w_input_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_m_axi_wvalid_next = s_axi_wvalid; + store_axi_w_input_to_temp = 1'b1; + end + end else if (m_axi_wready) begin + // input is not ready, but output is ready + m_axi_wvalid_next = temp_m_axi_wvalid_reg; + temp_m_axi_wvalid_next = 1'b0; + store_axi_w_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + s_axi_wready_reg <= 1'b0; + m_axi_wvalid_reg <= 1'b0; + temp_m_axi_wvalid_reg <= 1'b0; + end else begin + s_axi_wready_reg <= s_axi_wready_early; + m_axi_wvalid_reg <= m_axi_wvalid_next; + temp_m_axi_wvalid_reg <= temp_m_axi_wvalid_next; + end + + // datapath + if (store_axi_w_input_to_output) begin + m_axi_wdata_reg <= s_axi_wdata; + m_axi_wstrb_reg <= s_axi_wstrb; + m_axi_wlast_reg <= s_axi_wlast; + m_axi_wuser_reg <= s_axi_wuser; + end else if (store_axi_w_temp_to_output) begin + m_axi_wdata_reg <= temp_m_axi_wdata_reg; + m_axi_wstrb_reg <= temp_m_axi_wstrb_reg; + m_axi_wlast_reg <= temp_m_axi_wlast_reg; + m_axi_wuser_reg <= temp_m_axi_wuser_reg; + end + + if (store_axi_w_input_to_temp) begin + temp_m_axi_wdata_reg <= s_axi_wdata; + temp_m_axi_wstrb_reg <= s_axi_wstrb; + temp_m_axi_wlast_reg <= s_axi_wlast; + temp_m_axi_wuser_reg <= s_axi_wuser; + end +end + +end else if (W_REG_TYPE == 1) begin +// simple register, inserts bubble cycles + +// datapath registers +reg s_axi_wready_reg = 1'b0; + +reg [DATA_WIDTH-1:0] m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; +reg [STRB_WIDTH-1:0] m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; +reg m_axi_wlast_reg = 1'b0; +reg [WUSER_WIDTH-1:0] m_axi_wuser_reg = {WUSER_WIDTH{1'b0}}; +reg m_axi_wvalid_reg = 1'b0, m_axi_wvalid_next; + +// datapath control +reg store_axi_w_input_to_output; + +assign s_axi_wready = s_axi_wready_reg; + +assign m_axi_wdata = m_axi_wdata_reg; +assign m_axi_wstrb = m_axi_wstrb_reg; +assign m_axi_wlast = m_axi_wlast_reg; +assign m_axi_wuser = WUSER_ENABLE ? m_axi_wuser_reg : {WUSER_WIDTH{1'b0}}; +assign m_axi_wvalid = m_axi_wvalid_reg; + +// enable ready input next cycle if output buffer will be empty +wire s_axi_wready_ewly = !m_axi_wvalid_next; + +always @* begin + // transfer sink ready state to source + m_axi_wvalid_next = m_axi_wvalid_reg; + + store_axi_w_input_to_output = 1'b0; + + if (s_axi_wready_reg) begin + m_axi_wvalid_next = s_axi_wvalid; + store_axi_w_input_to_output = 1'b1; + end else if (m_axi_wready) begin + m_axi_wvalid_next = 1'b0; + end +end + +always @(posedge clk) begin + if (rst) begin + s_axi_wready_reg <= 1'b0; + m_axi_wvalid_reg <= 1'b0; + end else begin + s_axi_wready_reg <= s_axi_wready_ewly; + m_axi_wvalid_reg <= m_axi_wvalid_next; + end + + // datapath + if (store_axi_w_input_to_output) begin + m_axi_wdata_reg <= s_axi_wdata; + m_axi_wstrb_reg <= s_axi_wstrb; + m_axi_wlast_reg <= s_axi_wlast; + m_axi_wuser_reg <= s_axi_wuser; + end +end + +end else begin + + // bypass W channel + assign m_axi_wdata = s_axi_wdata; + assign m_axi_wstrb = s_axi_wstrb; + assign m_axi_wlast = s_axi_wlast; + assign m_axi_wuser = WUSER_ENABLE ? s_axi_wuser : {WUSER_WIDTH{1'b0}}; + assign m_axi_wvalid = s_axi_wvalid; + assign s_axi_wready = m_axi_wready; + +end + +// B channel + +if (B_REG_TYPE > 1) begin +// skid buffer, no bubble cycles + +// datapath registers +reg m_axi_bready_reg = 1'b0; + +reg [ID_WIDTH-1:0] s_axi_bid_reg = {ID_WIDTH{1'b0}}; +reg [1:0] s_axi_bresp_reg = 2'b0; +reg [BUSER_WIDTH-1:0] s_axi_buser_reg = {BUSER_WIDTH{1'b0}}; +reg s_axi_bvalid_reg = 1'b0, s_axi_bvalid_next; + +reg [ID_WIDTH-1:0] temp_s_axi_bid_reg = {ID_WIDTH{1'b0}}; +reg [1:0] temp_s_axi_bresp_reg = 2'b0; +reg [BUSER_WIDTH-1:0] temp_s_axi_buser_reg = {BUSER_WIDTH{1'b0}}; +reg temp_s_axi_bvalid_reg = 1'b0, temp_s_axi_bvalid_next; + +// datapath control +reg store_axi_b_input_to_output; +reg store_axi_b_input_to_temp; +reg store_axi_b_temp_to_output; + +assign m_axi_bready = m_axi_bready_reg; + +assign s_axi_bid = s_axi_bid_reg; +assign s_axi_bresp = s_axi_bresp_reg; +assign s_axi_buser = BUSER_ENABLE ? s_axi_buser_reg : {BUSER_WIDTH{1'b0}}; +assign s_axi_bvalid = s_axi_bvalid_reg; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +wire m_axi_bready_early = s_axi_bready | (~temp_s_axi_bvalid_reg & (~s_axi_bvalid_reg | ~m_axi_bvalid)); + +always @* begin + // transfer sink ready state to source + s_axi_bvalid_next = s_axi_bvalid_reg; + temp_s_axi_bvalid_next = temp_s_axi_bvalid_reg; + + store_axi_b_input_to_output = 1'b0; + store_axi_b_input_to_temp = 1'b0; + store_axi_b_temp_to_output = 1'b0; + + if (m_axi_bready_reg) begin + // input is ready + if (s_axi_bready | ~s_axi_bvalid_reg) begin + // output is ready or currently not valid, transfer data to output + s_axi_bvalid_next = m_axi_bvalid; + store_axi_b_input_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_s_axi_bvalid_next = m_axi_bvalid; + store_axi_b_input_to_temp = 1'b1; + end + end else if (s_axi_bready) begin + // input is not ready, but output is ready + s_axi_bvalid_next = temp_s_axi_bvalid_reg; + temp_s_axi_bvalid_next = 1'b0; + store_axi_b_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + m_axi_bready_reg <= 1'b0; + s_axi_bvalid_reg <= 1'b0; + temp_s_axi_bvalid_reg <= 1'b0; + end else begin + m_axi_bready_reg <= m_axi_bready_early; + s_axi_bvalid_reg <= s_axi_bvalid_next; + temp_s_axi_bvalid_reg <= temp_s_axi_bvalid_next; + end + + // datapath + if (store_axi_b_input_to_output) begin + s_axi_bid_reg <= m_axi_bid; + s_axi_bresp_reg <= m_axi_bresp; + s_axi_buser_reg <= m_axi_buser; + end else if (store_axi_b_temp_to_output) begin + s_axi_bid_reg <= temp_s_axi_bid_reg; + s_axi_bresp_reg <= temp_s_axi_bresp_reg; + s_axi_buser_reg <= temp_s_axi_buser_reg; + end + + if (store_axi_b_input_to_temp) begin + temp_s_axi_bid_reg <= m_axi_bid; + temp_s_axi_bresp_reg <= m_axi_bresp; + temp_s_axi_buser_reg <= m_axi_buser; + end +end + +end else if (B_REG_TYPE == 1) begin +// simple register, inserts bubble cycles + +// datapath registers +reg m_axi_bready_reg = 1'b0; + +reg [ID_WIDTH-1:0] s_axi_bid_reg = {ID_WIDTH{1'b0}}; +reg [1:0] s_axi_bresp_reg = 2'b0; +reg [BUSER_WIDTH-1:0] s_axi_buser_reg = {BUSER_WIDTH{1'b0}}; +reg s_axi_bvalid_reg = 1'b0, s_axi_bvalid_next; + +// datapath control +reg store_axi_b_input_to_output; + +assign m_axi_bready = m_axi_bready_reg; + +assign s_axi_bid = s_axi_bid_reg; +assign s_axi_bresp = s_axi_bresp_reg; +assign s_axi_buser = BUSER_ENABLE ? s_axi_buser_reg : {BUSER_WIDTH{1'b0}}; +assign s_axi_bvalid = s_axi_bvalid_reg; + +// enable ready input next cycle if output buffer will be empty +wire m_axi_bready_early = !s_axi_bvalid_next; + +always @* begin + // transfer sink ready state to source + s_axi_bvalid_next = s_axi_bvalid_reg; + + store_axi_b_input_to_output = 1'b0; + + if (m_axi_bready_reg) begin + s_axi_bvalid_next = m_axi_bvalid; + store_axi_b_input_to_output = 1'b1; + end else if (s_axi_bready) begin + s_axi_bvalid_next = 1'b0; + end +end + +always @(posedge clk) begin + if (rst) begin + m_axi_bready_reg <= 1'b0; + s_axi_bvalid_reg <= 1'b0; + end else begin + m_axi_bready_reg <= m_axi_bready_early; + s_axi_bvalid_reg <= s_axi_bvalid_next; + end + + // datapath + if (store_axi_b_input_to_output) begin + s_axi_bid_reg <= m_axi_bid; + s_axi_bresp_reg <= m_axi_bresp; + s_axi_buser_reg <= m_axi_buser; + end +end + +end else begin + + // bypass B channel + assign s_axi_bid = m_axi_bid; + assign s_axi_bresp = m_axi_bresp; + assign s_axi_buser = BUSER_ENABLE ? m_axi_buser : {BUSER_WIDTH{1'b0}}; + assign s_axi_bvalid = m_axi_bvalid; + assign m_axi_bready = s_axi_bready; + +end + +endgenerate + +endmodule + +`resetall diff --git a/xls/modules/zstd/priority_encoder.v b/xls/modules/zstd/external/priority_encoder.v similarity index 100% rename from xls/modules/zstd/priority_encoder.v rename to xls/modules/zstd/external/priority_encoder.v diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index b696272811..cbf488faab 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -291,6 +291,8 @@ async def test_decoder(dut, seed, block_type, scoreboard, axi_buses, xls_channel expected_decoded_frame = DecompressFrame(encoded.read()) encoded.close() + reference_memory = SparseMemory(mem_size) + reference_memory.write(obuf_addr, expected_decoded_frame) expected_output_packets = generate_expected_output(expected_decoded_frame) assert_expected_output = Event() @@ -303,12 +305,16 @@ async def test_decoder(dut, seed, block_type, scoreboard, axi_buses, xls_channel await configure_decoder(cpu, ibuf_addr, obuf_addr) await start_decoder(cpu) - await assert_expected_output.wait() + await assert_notify.wait() await wait_for_idle(cpu) - # TODO: Check decoded frame in memory under `obuf_addr` when ZstdDecoder - # will fully support memory output interface + # Read decoded frame in chunks of AXI_DATA_W length + # Compare against frame decompressed with the reference library + for read_op in range(0, ((len(expected_decoded_frame) + (AXI_DATA_W_BYTES - 1)) // AXI_DATA_W_BYTES)): + addr = obuf_addr + (read_op * AXI_DATA_W_BYTES) + mem_contents = memory.read(addr, AXI_DATA_W_BYTES) + exp_mem_contents = reference_memory.read(addr, AXI_DATA_W_BYTES) + assert mem_contents == exp_mem_contents, "{} bytes of memory contents at address {} don't match the expected contents:\n{}\nvs\n{}".format(AXI_DATA_W_BYTES, hex(addr), hex(int.from_bytes(mem_contents, byteorder='little')), hex(int.from_bytes(exp_mem_contents, byteorder='little'))) - await assert_notify.wait() await ClockCycles(dut.clk, 20) @cocotb.test(timeout_time=50, timeout_unit="ms") @@ -320,73 +326,73 @@ async def zstd_reset_test(dut): await test_reset(dut) #FIXME: Rework testbench to decode multiple ZSTD frames in one test -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_1(dut): block_type = BlockType.RAW (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 1, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_2(dut): block_type = BlockType.RAW (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 2, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_3(dut): block_type = BlockType.RAW (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 3, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_4(dut): block_type = BlockType.RAW (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 4, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_5(dut): block_type = BlockType.RAW (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 5, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_1(dut): block_type = BlockType.RLE (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 1, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_2(dut): block_type = BlockType.RLE (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 2, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_3(dut): block_type = BlockType.RLE (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 3, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_4(dut): block_type = BlockType.RLE (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 4, block_type, scoreboard, axi_buses, xls_channels, cpu) -@cocotb.test(timeout_time=20000, timeout_unit="ms") +@cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_5(dut): block_type = BlockType.RLE (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) await test_decoder(dut, 5, block_type, scoreboard, axi_buses, xls_channels, cpu) -#@cocotb.test(timeout_time=20000, timeout_unit="ms") +#@cocotb.test(timeout_time=1000, timeout_unit="ms") #async def zstd_compressed_frames_test(dut): # test_cases = 1 # block_type = BlockType.COMPRESSED # await test_decoder(dut, test_cases, block_type) # -#@cocotb.test(timeout_time=20000, timeout_unit="ms") +#@cocotb.test(timeout_time=1000, timeout_unit="ms") #async def zstd_random_frames_test(dut): # test_cases = 1 # block_type = BlockType.RANDOM @@ -398,10 +404,15 @@ async def zstd_rle_frames_test_5(dut): "xls/modules/zstd/zstd_dec.v", "xls/modules/zstd/rtl/xls_fifo_wrapper.sv", "xls/modules/zstd/rtl/zstd_dec_wrapper.sv", - "xls/modules/zstd/axi_interconnect_wrapper.v", - "xls/modules/zstd/axi_interconnect.v", - "xls/modules/zstd/arbiter.v", - "xls/modules/zstd/priority_encoder.v", + "xls/modules/zstd/external/axi_crossbar_wrapper.v", + "xls/modules/zstd/external/axi_crossbar.v", + "xls/modules/zstd/external/axi_crossbar_rd.v", + "xls/modules/zstd/external/axi_crossbar_wr.v", + "xls/modules/zstd/external/axi_crossbar_addr.v", + "xls/modules/zstd/external/axi_register_rd.v", + "xls/modules/zstd/external/axi_register_wr.v", + "xls/modules/zstd/external/arbiter.v", + "xls/modules/zstd/external/priority_encoder.v", ] test_module=[Path(__file__).stem] run_test(toplevel, test_module, verilog_sources) From a0fafec82a7a2ba7afb47200f462e9db7a3c0862 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 12 May 2025 19:58:23 +0200 Subject: [PATCH 010/159] modules/zstd/zstd_dec: Remove stream-based output interface Adjust cocotb test to: * Removal of Repacketizer proc * Removal of stream-based output channels from * SequenceExecutor * ZstdDecoder Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/zstd_dec_cocotb_test.py | 79 +++++++----------------- 1 file changed, 22 insertions(+), 57 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index cbf488faab..64e8cf586a 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -44,7 +44,6 @@ AXI_DATA_W_BYTES = AXI_DATA_W // 8 MAX_ENCODED_FRAME_SIZE_B = 16384 NOTIFY_CHANNEL = "notify" -OUTPUT_CHANNEL = "output" RESET_CHANNEL = "reset" # Override default widths of AXI response signals @@ -69,12 +68,6 @@ class NotifyStruct(XLSStruct): class ResetStruct(XLSStruct): pass -@xls_dataclass -class OutputStruct(XLSStruct): - data: 64 - length: 32 - last: 1 - class CSR(Enum): """ Maps the offsets to the ZSTD Decoder registers @@ -215,23 +208,6 @@ async def wait_for_idle(cpu, timeout=100): timeout -= 1 assert (timeout != 0) -def generate_expected_output(decoded_frame): - packets = [] - frame_len = len(decoded_frame) - last_len = frame_len % 8 - for i in range(frame_len // 8): - start_id = i * 8 - end_id = start_id + 8 - packet_data = int.from_bytes(decoded_frame[start_id:end_id], byteorder='little') - last_packet = (end_id==frame_len) - packet = OutputStruct(data=packet_data, length=64, last=last_packet) - packets.append(packet) - if (last_len): - packet_data = int.from_bytes(decoded_frame[-last_len:], byteorder='little') - packet = OutputStruct(data=packet_data, length=last_len*8, last=True) - packets.append(packet) - return packets - async def reset_dut(dut, rst_len=10): dut.rst.setimmediatevalue(0) await ClockCycles(dut.clk, rst_len) @@ -249,8 +225,6 @@ def prepare_test_environment(dut): clock = Clock(dut.clk, 10, units="us") cocotb.start_soon(clock.start()) - scoreboard = Scoreboard(dut) - memory_bus = connect_axi_bus(dut, "memory") csr_bus = connect_axi_bus(dut, "csr") axi_buses = { @@ -259,21 +233,18 @@ def prepare_test_environment(dut): } notify = connect_xls_channel(dut, NOTIFY_CHANNEL, NotifyStruct) - output = connect_xls_channel(dut, OUTPUT_CHANNEL, OutputStruct) xls_channels = { "notify": notify, - "output": output } cpu = AxiMaster(csr_bus, dut.clk, dut.rst) - return (scoreboard, axi_buses, xls_channels, cpu) + return (axi_buses, xls_channels, cpu) -async def test_decoder(dut, seed, block_type, scoreboard, axi_buses, xls_channels, cpu): +async def test_decoder(dut, seed, block_type, axi_buses, xls_channels, cpu): memory_bus = axi_buses["memory"] csr_bus = axi_buses["csr"] (notify_channel, notify_monitor) = xls_channels[NOTIFY_CHANNEL] - (output_channel, output_monitor) = xls_channels[OUTPUT_CHANNEL] assert_notify = Event() set_termination_event(notify_monitor, assert_notify, 1) @@ -293,12 +264,6 @@ async def test_decoder(dut, seed, block_type, scoreboard, axi_buses, xls_channel encoded.close() reference_memory = SparseMemory(mem_size) reference_memory.write(obuf_addr, expected_decoded_frame) - expected_output_packets = generate_expected_output(expected_decoded_frame) - - assert_expected_output = Event() - set_termination_event(output_monitor, assert_expected_output, len(expected_output_packets)) - # Monitor stream output for packets with the decoded ZSTD frame - scoreboard.add_interface(output_monitor, expected_output_packets) # Initialise testbench memory with generated ZSTD frame memory = AxiRamFromFile(bus=memory_bus, clock=dut.clk, reset=dut.rst, path=encoded.name, size=mem_size) @@ -329,62 +294,62 @@ async def zstd_reset_test(dut): @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_1(dut): block_type = BlockType.RAW - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 1, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 1, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_2(dut): block_type = BlockType.RAW - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 2, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 2, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_3(dut): block_type = BlockType.RAW - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 3, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 3, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_4(dut): block_type = BlockType.RAW - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 4, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 4, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_raw_frames_test_5(dut): block_type = BlockType.RAW - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 5, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 5, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_1(dut): block_type = BlockType.RLE - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 1, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 1, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_2(dut): block_type = BlockType.RLE - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 2, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 2, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_3(dut): block_type = BlockType.RLE - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 3, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 3, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_4(dut): block_type = BlockType.RLE - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 4, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 4, block_type, axi_buses, xls_channels, cpu) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def zstd_rle_frames_test_5(dut): block_type = BlockType.RLE - (scoreboard, axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 5, block_type, scoreboard, axi_buses, xls_channels, cpu) + (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) + await test_decoder(dut, 5, block_type, axi_buses, xls_channels, cpu) #@cocotb.test(timeout_time=1000, timeout_unit="ms") #async def zstd_compressed_frames_test(dut): From a585d16b4740c4723eb1760877b979374ae05135 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 18 Nov 2024 10:31:11 +0100 Subject: [PATCH 011/159] modules/zstd/zstd_dec_cocotb_test: Improve Verilog simulation * Decode multiple ZSTD frames in a single cocotb testbench * Add one cocotb testbench per type of the ZSTD frames: * Frames with RAW blocks only * Frames with RLE blocks only * Frames with Compressed blocks only (disabled) * Frames with mixed blocks (disabled) Internal-tag: [#67272] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/zstd_dec_cocotb_test.py | 88 ++++++------------------ 1 file changed, 20 insertions(+), 68 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index 64e8cf586a..3a4bfbf70f 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -232,20 +232,15 @@ def prepare_test_environment(dut): "csr": csr_bus } - notify = connect_xls_channel(dut, NOTIFY_CHANNEL, NotifyStruct) - xls_channels = { - "notify": notify, - } - cpu = AxiMaster(csr_bus, dut.clk, dut.rst) - return (axi_buses, xls_channels, cpu) + return (axi_buses, cpu) -async def test_decoder(dut, seed, block_type, axi_buses, xls_channels, cpu): +async def test_decoder(dut, seed, block_type, axi_buses, cpu): memory_bus = axi_buses["memory"] csr_bus = axi_buses["csr"] - (notify_channel, notify_monitor) = xls_channels[NOTIFY_CHANNEL] + (notify_channel, notify_monitor) = connect_xls_channel(dut, NOTIFY_CHANNEL, NotifyStruct) assert_notify = Event() set_termination_event(notify_monitor, assert_notify, 1) @@ -282,6 +277,12 @@ async def test_decoder(dut, seed, block_type, axi_buses, xls_channels, cpu): await ClockCycles(dut.clk, 20) +async def testing_routine(dut, test_cases=1, block_type=BlockType.RANDOM): + (axi_buses, cpu) = prepare_test_environment(dut) + for test_case in range(test_cases): + await test_decoder(dut, test_case, block_type, axi_buses, cpu) + print("Decoding {} ZSTD frames done".format(block_type.name)) + @cocotb.test(timeout_time=50, timeout_unit="ms") async def zstd_csr_test(dut): await test_csr(dut) @@ -290,78 +291,29 @@ async def zstd_csr_test(dut): async def zstd_reset_test(dut): await test_reset(dut) -#FIXME: Rework testbench to decode multiple ZSTD frames in one test -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_raw_frames_test_1(dut): - block_type = BlockType.RAW - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 1, block_type, axi_buses, xls_channels, cpu) - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_raw_frames_test_2(dut): - block_type = BlockType.RAW - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 2, block_type, axi_buses, xls_channels, cpu) - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_raw_frames_test_3(dut): - block_type = BlockType.RAW - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 3, block_type, axi_buses, xls_channels, cpu) - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_raw_frames_test_4(dut): - block_type = BlockType.RAW - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 4, block_type, axi_buses, xls_channels, cpu) - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_raw_frames_test_5(dut): +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def zstd_raw_frames_test(dut): + test_cases = 5 block_type = BlockType.RAW - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 5, block_type, axi_buses, xls_channels, cpu) + await testing_routine(dut, test_cases, block_type) -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_rle_frames_test_1(dut): +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def zstd_rle_frames_test(dut): + test_cases = 5 block_type = BlockType.RLE - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 1, block_type, axi_buses, xls_channels, cpu) - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_rle_frames_test_2(dut): - block_type = BlockType.RLE - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 2, block_type, axi_buses, xls_channels, cpu) - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_rle_frames_test_3(dut): - block_type = BlockType.RLE - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 3, block_type, axi_buses, xls_channels, cpu) - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_rle_frames_test_4(dut): - block_type = BlockType.RLE - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 4, block_type, axi_buses, xls_channels, cpu) - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def zstd_rle_frames_test_5(dut): - block_type = BlockType.RLE - (axi_buses, xls_channels, cpu) = prepare_test_environment(dut) - await test_decoder(dut, 5, block_type, axi_buses, xls_channels, cpu) + await testing_routine(dut, test_cases, block_type) #@cocotb.test(timeout_time=1000, timeout_unit="ms") #async def zstd_compressed_frames_test(dut): # test_cases = 1 # block_type = BlockType.COMPRESSED -# await test_decoder(dut, test_cases, block_type) -# +# await testing_routine(dut, test_cases, block_type) + #@cocotb.test(timeout_time=1000, timeout_unit="ms") #async def zstd_random_frames_test(dut): # test_cases = 1 # block_type = BlockType.RANDOM -# await test_decoder(dut, test_cases, block_type) +# await testing_routine(dut, test_cases, block_type) if __name__ == "__main__": toplevel = "zstd_dec_wrapper" From 3796c90c193bea29fdc6f86f12dada93ec375f3b Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Thu, 2 Jan 2025 11:21:32 +0100 Subject: [PATCH 012/159] modules/zstd/memory/mem_writer: Reduce the amount of random test cases Caused by timeouts after rebase - looks like regression in the performance of codegen Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/memory/mem_writer_cocotb_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/memory/mem_writer_cocotb_test.py b/xls/modules/zstd/memory/mem_writer_cocotb_test.py index b617c41fa2..1569227065 100644 --- a/xls/modules/zstd/memory/mem_writer_cocotb_test.py +++ b/xls/modules/zstd/memory/mem_writer_cocotb_test.py @@ -206,7 +206,7 @@ async def ram_test_not_full_packets(dut): @cocotb.test(timeout_time=5000, timeout_unit="ms") async def ram_test_random(dut): mem_size = 2**ADDR_WIDTH - test_count = 200 + test_count = 50 (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_random(test_count, mem_size) await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) From 3de5586b246bb8e5099713b9eeaf41df8644b9a3 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 7 Apr 2025 17:24:48 +0200 Subject: [PATCH 013/159] modules/zstd/memory: Move verilog sources to rtl subdirectory Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 1 + xls/modules/zstd/memory/BUILD | 8 ++-- .../zstd/memory/axi_writer_cocotb_test.py | 2 +- .../zstd/memory/mem_reader_cocotb_test.py | 3 +- .../zstd/memory/mem_writer_cocotb_test.py | 3 +- xls/modules/zstd/memory/rtl/BUILD | 39 +++++++++++++++++++ .../memory/{ => rtl}/axi_writer_wrapper.v | 3 ++ .../memory/{ => rtl}/mem_reader_wrapper.v | 3 ++ .../memory/{ => rtl}/mem_writer_wrapper.v | 3 ++ 9 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 xls/modules/zstd/memory/rtl/BUILD rename xls/modules/zstd/memory/{ => rtl}/axi_writer_wrapper.v (96%) rename xls/modules/zstd/memory/{ => rtl}/mem_reader_wrapper.v (96%) rename xls/modules/zstd/memory/{ => rtl}/mem_writer_wrapper.v (98%) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index d39b49dd1e..f96abb8528 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1689,6 +1689,7 @@ py_test( "//xls/modules/zstd/external:axi_register_rd.v", "//xls/modules/zstd/external:axi_register_wr.v", "//xls/modules/zstd/external:priority_encoder.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", "//xls/modules/zstd/rtl:zstd_dec_wrapper.sv", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index 086e7186c6..d30f9b02c8 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -553,8 +553,7 @@ py_test( srcs = ["mem_reader_cocotb_test.py"], data = [ ":mem_reader_adv.v", - ":mem_reader_wrapper.v", - "//xls/modules/zstd:xls_fifo_wrapper.sv", + "//xls/modules/zstd/memory/rtl:mem_reader_wrapper.v", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], @@ -650,7 +649,7 @@ py_test( srcs = ["axi_writer_cocotb_test.py"], data = [ ":axi_writer.v", - ":axi_writer_wrapper.v", + "//xls/modules/zstd/memory/rtl:axi_writer_wrapper.v", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], @@ -816,8 +815,7 @@ py_test( srcs = ["mem_writer_cocotb_test.py"], data = [ ":mem_writer.v", - ":mem_writer_wrapper.v", - "//xls/modules/zstd:xls_fifo_wrapper.sv", + "//xls/modules/zstd/memory/rtl:mem_writer_wrapper.v", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], diff --git a/xls/modules/zstd/memory/axi_writer_cocotb_test.py b/xls/modules/zstd/memory/axi_writer_cocotb_test.py index b30876a687..d655c6f8e0 100644 --- a/xls/modules/zstd/memory/axi_writer_cocotb_test.py +++ b/xls/modules/zstd/memory/axi_writer_cocotb_test.py @@ -239,7 +239,7 @@ def generate_test_data_arbitrary(mem_size): toplevel = "axi_writer_wrapper" verilog_sources = [ "xls/modules/zstd/memory/axi_writer.v", - "xls/modules/zstd/memory/axi_writer_wrapper.v", + "xls/modules/zstd/memory/rtl/axi_writer_wrapper.v", ] test_module=[Path(__file__).stem] run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/memory/mem_reader_cocotb_test.py b/xls/modules/zstd/memory/mem_reader_cocotb_test.py index 36c90cea1d..3e48904f6a 100644 --- a/xls/modules/zstd/memory/mem_reader_cocotb_test.py +++ b/xls/modules/zstd/memory/mem_reader_cocotb_test.py @@ -264,9 +264,8 @@ async def mem_reader_aligned_transfer_shorter_than_bus4(dut): toplevel = "mem_reader_wrapper" verilog_sources = [ - "xls/modules/zstd/xls_fifo_wrapper.sv", "xls/modules/zstd/memory/mem_reader_adv.v", - "xls/modules/zstd/memory/mem_reader_wrapper.v", + "xls/modules/zstd/memory/rtl/mem_reader_wrapper.v", ] test_module = [Path(__file__).stem] run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/memory/mem_writer_cocotb_test.py b/xls/modules/zstd/memory/mem_writer_cocotb_test.py index 1569227065..69b39a7894 100644 --- a/xls/modules/zstd/memory/mem_writer_cocotb_test.py +++ b/xls/modules/zstd/memory/mem_writer_cocotb_test.py @@ -442,9 +442,8 @@ def generate_padded_test_data_arbitrary(mem_size, test_cases): if __name__ == "__main__": toplevel = "mem_writer_wrapper" verilog_sources = [ - "xls/modules/zstd/xls_fifo_wrapper.sv", "xls/modules/zstd/memory/mem_writer.v", - "xls/modules/zstd/memory/mem_writer_wrapper.v", + "xls/modules/zstd/memory/rtl/mem_writer_wrapper.v", ] test_module=[Path(__file__).stem] run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/memory/rtl/BUILD b/xls/modules/zstd/memory/rtl/BUILD new file mode 100644 index 0000000000..96029da06d --- /dev/null +++ b/xls/modules/zstd/memory/rtl/BUILD @@ -0,0 +1,39 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Collection of simulation sources + +This package exports verilog sources required by the Memory Writer and Read +in the verilog tests. + +The sources contain: + * Wrapper for the MemReader used in the verilog tests + * Wrapper for the MemWriter used in the verilog tests + * Wrapper for the AxiWriter used in the verilog tests + +""" + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +exports_files( + [ + "mem_reader_wrapper.v", + "mem_writer_wrapper.v", + "axi_writer_wrapper.v", + ], +) diff --git a/xls/modules/zstd/memory/axi_writer_wrapper.v b/xls/modules/zstd/memory/rtl/axi_writer_wrapper.v similarity index 96% rename from xls/modules/zstd/memory/axi_writer_wrapper.v rename to xls/modules/zstd/memory/rtl/axi_writer_wrapper.v index 556f839284..15788cd41d 100644 --- a/xls/modules/zstd/memory/axi_writer_wrapper.v +++ b/xls/modules/zstd/memory/rtl/axi_writer_wrapper.v @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// This module wraps the AXI Writer verilog sources generated from DSLX to +// form a DUT for verilog tests with consistent IO. + `default_nettype none module axi_writer_wrapper ( diff --git a/xls/modules/zstd/memory/mem_reader_wrapper.v b/xls/modules/zstd/memory/rtl/mem_reader_wrapper.v similarity index 96% rename from xls/modules/zstd/memory/mem_reader_wrapper.v rename to xls/modules/zstd/memory/rtl/mem_reader_wrapper.v index 3601bcbb0e..0dfd2dd941 100644 --- a/xls/modules/zstd/memory/mem_reader_wrapper.v +++ b/xls/modules/zstd/memory/rtl/mem_reader_wrapper.v @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// This module wraps the Memory Reader verilog sources generated from DSLX to +// form a DUT for verilog tests with consistent IO. + `default_nettype none module mem_reader_wrapper #( diff --git a/xls/modules/zstd/memory/mem_writer_wrapper.v b/xls/modules/zstd/memory/rtl/mem_writer_wrapper.v similarity index 98% rename from xls/modules/zstd/memory/mem_writer_wrapper.v rename to xls/modules/zstd/memory/rtl/mem_writer_wrapper.v index c7513af58a..958383c282 100644 --- a/xls/modules/zstd/memory/mem_writer_wrapper.v +++ b/xls/modules/zstd/memory/rtl/mem_writer_wrapper.v @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// This module wraps the Memory Writer verilog sources generated from DSLX to +// form a DUT for verilog tests with consistent IO. + `default_nettype none module mem_writer_wrapper ( From fe868652b5a57aad831da6ae94f1b09e61f64fe1 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 7 Apr 2025 15:44:54 +0200 Subject: [PATCH 014/159] xls/modules/zstd/external: Add docstring and license for third party sources Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/external/BUILD | 15 +++++++++++++++ xls/modules/zstd/external/LICENSE | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 xls/modules/zstd/external/LICENSE diff --git a/xls/modules/zstd/external/BUILD b/xls/modules/zstd/external/BUILD index f24cb69fe0..f0e3c97626 100644 --- a/xls/modules/zstd/external/BUILD +++ b/xls/modules/zstd/external/BUILD @@ -12,6 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Collection of external RTL sources + +This package exports external verilog sources required by the ZSTD Decoder +in the verilog tests. + +The exported files come from the Open Source library of the AXI4 and AXI4 lite +bus components released under MIT license. + +The sources contain an implementation of the AXI4 crossbar which is used in the +verilog tests of the ZSTD Decoder to connect multiple AXI interfaces into a single +AXI interface that forms the IO of the ZSTD Decoder verilog wrapper used in the tests. + +Source: https://github.com/alexforencich/verilog-axi +""" + package( default_applicable_licenses = ["//:license"], default_visibility = ["//xls:xls_users"], diff --git a/xls/modules/zstd/external/LICENSE b/xls/modules/zstd/external/LICENSE new file mode 100644 index 0000000000..6923387c75 --- /dev/null +++ b/xls/modules/zstd/external/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. From 806c4b4fb8dcbb6cc89e6c3296fd900fb528e93c Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 13 May 2025 19:14:28 +0200 Subject: [PATCH 015/159] modules/zstd: Adjust cocotb test to removal of RESET CSR Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/zstd_dec_cocotb_test.py | 25 ++++-------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index 3a4bfbf70f..a5ca1daec6 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -64,19 +64,14 @@ class NotifyStruct(XLSStruct): pass -@xls_dataclass -class ResetStruct(XLSStruct): - pass - class CSR(Enum): """ Maps the offsets to the ZSTD Decoder registers """ Status = 0 Start = 1 - Reset = 2 - InputBuffer = 3 - OutputBuffer = 4 + InputBuffer = 2 + OutputBuffer = 3 class Status(Enum): """ @@ -146,9 +141,6 @@ async def test_csr(dut): await ClockCycles(dut.clk, 10) i = 0 for reg in CSR: - # Reset CSR tested in a separate test case - if (reg == CSR.Reset): - continue expected_src = bytearray.fromhex("0DF0AD8BEFBEADDE") assert len(expected_src) >= AXI_DATA_W_BYTES expected = expected_src[-AXI_DATA_W_BYTES:] @@ -165,18 +157,9 @@ async def test_reset(dut): await reset_dut(dut, 50) - (reset_channel, reset_monitor) = connect_xls_channel(dut, RESET_CHANNEL, ResetStruct) - csr_bus = connect_axi_bus(dut, "csr") cpu = AxiMaster(csr_bus, dut.clk, dut.rst) - scoreboard = Scoreboard(dut) - - rst_struct = ResetStruct() - # Expect single reset signal on reset output channel - expected_reset = [rst_struct] - scoreboard.add_interface(reset_monitor, expected_reset) - await ClockCycles(dut.clk, 10) await start_decoder(cpu) timeout = 10 @@ -186,7 +169,7 @@ async def test_reset(dut): timeout -= 1 assert (timeout != 0) - await csr_write(cpu, CSR.Reset, 0x1) + await reset_dut(dut, 50) await wait_for_idle(cpu, 10) await ClockCycles(dut.clk, 10) @@ -194,7 +177,7 @@ async def test_reset(dut): async def configure_decoder(cpu, ibuf_addr, obuf_addr): status = await csr_read(cpu, CSR.Status) if int.from_bytes(status.data, byteorder='little') != Status.IDLE.value: - await csr_write(cpu, CSR.Reset, 0x1) + await reset_dut(dut, 50) await csr_write(cpu, CSR.InputBuffer, ibuf_addr) await csr_write(cpu, CSR.OutputBuffer, obuf_addr) From 25d9d9afde2524b8b4325ec6606b3b30218361f7 Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Mon, 7 Apr 2025 15:45:34 +0200 Subject: [PATCH 016/159] Style changes Improve formatting, wording and fix lint issues Co-authored-by: Pawel Czarnecki Signed-off-by: Wojciech Sipak --- xls/modules/zstd/cocotb/channel.py | 39 +- xls/modules/zstd/cocotb/data_generator.py | 17 +- xls/modules/zstd/cocotb/memory.py | 16 +- xls/modules/zstd/cocotb/scoreboard.py | 25 +- xls/modules/zstd/cocotb/utils.py | 20 +- xls/modules/zstd/cocotb/xlsstruct.py | 105 ++-- .../zstd/memory/axi_writer_cocotb_test.py | 228 +++++--- .../zstd/memory/mem_reader_cocotb_test.py | 103 ++-- .../zstd/memory/mem_writer_cocotb_test.py | 517 ++++++++++++++---- .../zstd/memory/rtl/mem_reader_wrapper.v | 24 +- .../zstd/memory/rtl/mem_writer_wrapper.v | 62 ++- xls/modules/zstd/zstd_dec_cocotb_test.py | 204 +++---- xls/modules/zstd/zstd_frame_dslx.py | 95 ++-- 13 files changed, 951 insertions(+), 504 deletions(-) diff --git a/xls/modules/zstd/cocotb/channel.py b/xls/modules/zstd/cocotb/channel.py index 0970ab6e9b..6b6e546e86 100644 --- a/xls/modules/zstd/cocotb/channel.py +++ b/xls/modules/zstd/cocotb/channel.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Cocotb interfaces for XLS channels using data, valid, and ready signals.""" + from typing import Any, Sequence, Type, Union import cocotb @@ -30,18 +32,21 @@ class XLSChannel(Bus): + """Represents an XLS- channel with ready/valid handshake.""" _signals = XLS_CHANNEL_SIGNALS _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS def __init__(self, entity, name, clk, *, start_now=False, **kwargs: Any): - super().__init__(entity, name, self._signals, self._optional_signals, **kwargs) + super().__init__( + entity, name, self._signals, self._optional_signals, **kwargs + ) self.clk = clk if start_now: self.start_recv_loop() @cocotb.coroutine async def recv_channel(self): - """Cocotb coroutine that acts as a proc receiving data from a channel""" + """Cocotb coroutine that acts as a proc receiving data from a channel.""" self.rdy.setimmediatevalue(1) while True: await RisingEdge(self.clk) @@ -51,20 +56,34 @@ def start_recv_loop(self): class XLSChannelDriver(BusDriver): + """Drives transactions on an XLS channel.""" _signals = XLS_CHANNEL_SIGNALS _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS - def __init__(self, entity: SimHandleBase, name: str, clock: SimHandleBase, **kwargs: Any): + def __init__( + self, + entity: SimHandleBase, + name: str, + clock: SimHandleBase, + **kwargs: Any + ): BusDriver.__init__(self, entity, name, clock, **kwargs) self.bus.data.setimmediatevalue(0) self.bus.vld.setimmediatevalue(0) - async def _driver_send(self, transaction: Transaction, sync: bool = True, **kwargs: Any) -> None: + async def _driver_send( + self, + transaction: Transaction, + sync: bool = True, + **kwargs: Any + ) -> None: if sync: await RisingEdge(self.clock) - data_to_send = (transaction if isinstance(transaction, Sequence) else [transaction]) + data_to_send = ( + transaction if isinstance(transaction, Sequence) else [transaction] + ) for word in data_to_send: self.bus.vld.value = 1 @@ -79,10 +98,18 @@ async def _driver_send(self, transaction: Transaction, sync: bool = True, **kwar class XLSChannelMonitor(BusMonitor): + """Monitors and decodes transactions on an XLS channel.""" _signals = XLS_CHANNEL_SIGNALS _optional_signals = XLS_CHANNEL_OPTIONAL_SIGNALS - def __init__(self, entity: SimHandleBase, name: str, clock: SimHandleBase, struct: Type[XLSStruct], **kwargs: Any): + def __init__( + self, + entity: SimHandleBase, + name: str, + clock: SimHandleBase, + struct: Type[XLSStruct], + **kwargs: Any + ): BusMonitor.__init__(self, entity, name, clock, **kwargs) self.struct = struct diff --git a/xls/modules/zstd/cocotb/data_generator.py b/xls/modules/zstd/cocotb/data_generator.py index 72b60c5eee..46a74acbfc 100644 --- a/xls/modules/zstd/cocotb/data_generator.py +++ b/xls/modules/zstd/cocotb/data_generator.py @@ -12,14 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pathlib import Path -from enum import Enum +"""Module for generating and decompressing test frames.""" + +import pathlib +import enum from xls.common import runfiles import subprocess import zstandard -class BlockType(Enum): +class BlockType(enum.Enum): + """Enum encoding of ZSTD block types.""" + RAW = 0 RLE = 1 COMPRESSED = 2 @@ -33,10 +37,12 @@ def from_string(s): try: return BlockType[s] except KeyError as e: - raise ValueError(str(e)) + raise ValueError(str(e)) from e def CallDecodecorpus(args): - decodecorpus = Path(runfiles.get_path("decodecorpus", repository = "zstd")) + decodecorpus = pathlib.Path( + runfiles.get_path("decodecorpus", repository = "zstd") + ) cmd = args cmd.insert(0, str(decodecorpus)) cmd_concat = " ".join(cmd) @@ -58,4 +64,3 @@ def GenerateFrame(seed, btype, output_path): args.append("-vvvvvvv") CallDecodecorpus(args) - diff --git a/xls/modules/zstd/cocotb/memory.py b/xls/modules/zstd/cocotb/memory.py index 52e512e053..a07ef33923 100644 --- a/xls/modules/zstd/cocotb/memory.py +++ b/xls/modules/zstd/cocotb/memory.py @@ -12,13 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Extensions of the cocotb AXI RAM models to memory contents from a binary file. +""" + import os -from cocotbext.axi.axi_ram import AxiRam, AxiRamRead, AxiRamWrite +from cocotbext.axi.axi_ram import AxiRam +from cocotbext.axi.axi_ram import AxiRamRead +from cocotbext.axi.axi_ram import AxiRamWrite from cocotbext.axi.sparse_memory import SparseMemory -def init_axi_mem(path: os.PathLike, kwargs): +def init_axi_mem(path: os.PathLike[str], kwargs): with open(path, "rb") as f: sparse_mem = SparseMemory(size=kwargs["size"]) sparse_mem.write(0x0, f.read()) @@ -26,18 +32,18 @@ def init_axi_mem(path: os.PathLike, kwargs): class AxiRamReadFromFile(AxiRamRead): - def __init__(self, *args, path: os.PathLike, **kwargs): + def __init__(self, *args, path: os.PathLike[str], **kwargs): init_axi_mem(path, kwargs) super().__init__(*args, **kwargs) class AxiRamFromFile(AxiRam): - def __init__(self, *args, path: os.PathLike, **kwargs): + def __init__(self, *args, path: os.PathLike[str], **kwargs): init_axi_mem(path, kwargs) super().__init__(*args, **kwargs) class AxiRamWriteFromFile(AxiRamWrite): - def __init__(self, *args, path: os.PathLike, **kwargs): + def __init__(self, *args, path: os.PathLike[str], **kwargs): init_axi_mem(path, kwargs) super().__init__(*args, **kwargs) diff --git a/xls/modules/zstd/cocotb/scoreboard.py b/xls/modules/zstd/cocotb/scoreboard.py index b9b64ca6e2..b3dfca1e3a 100644 --- a/xls/modules/zstd/cocotb/scoreboard.py +++ b/xls/modules/zstd/cocotb/scoreboard.py @@ -12,8 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass -from queue import Queue +"""Scoreboard creator for measuring request-response latency. + +Tracks transactions from channel monitors, computes cycle-based latency, +and logs results. +""" + +import dataclasses +import queue from cocotb.clock import Clock from cocotb.log import SimLog @@ -23,20 +29,26 @@ from xls.modules.zstd.cocotb.xlsstruct import XLSStruct -@dataclass +@dataclasses.dataclass class LatencyQueueItem: + """ + Data container for items in a latency queue. + """ + transaction: XLSStruct timestamp: int class LatencyScoreboard: + """Tracks and reports latency between request and response transactions.""" + def __init__(self, dut, clock: Clock, req_monitor: XLSChannelMonitor, resp_monitor: XLSChannelMonitor): self.dut = dut self.log = SimLog(f"zstd.cocotb.scoreboard.{self.dut._name}") self.clock = clock self.req_monitor = req_monitor self.resp_monitor = resp_monitor - self.pending_req = Queue() + self.pending_req = queue.Queue() self.results = [] self.req_monitor.add_callback(self._req_callback) @@ -48,7 +60,7 @@ def _current_cycle(self): def _req_callback(self, transaction: XLSStruct): self.pending_req.put(LatencyQueueItem(transaction, self._current_cycle())) - def _resp_callback(self, transaction: XLSStruct): + def _resp_callback(self, unused_transaction: XLSStruct): latency_item = self.pending_req.get() self.results.append(self._current_cycle() - latency_item.timestamp) @@ -56,11 +68,12 @@ def average_latency(self): return sum(self.results)/len(self.results) def report_result(self): + """Logs a summary of latency measurements and any unfulfilled requests.""" if not self.pending_req.empty(): self.log.warning(f"There are unfulfilled requests from channel {self.req_monitor.name}") while not self.pending_req.empty(): self.log.warning(f"Unfulfilled request: {self.pending_req.get()}") - if len(self.results) > 0: + if self.results: self.log.info(f"Latency report - 1st latency: {self.results[0]}") if len(self.results) > 1: self.log.info(f"Latency report - 2nd latency: {self.results[1]}") diff --git a/xls/modules/zstd/cocotb/utils.py b/xls/modules/zstd/cocotb/utils.py index 0930a92932..7fda01b0b9 100644 --- a/xls/modules/zstd/cocotb/utils.py +++ b/xls/modules/zstd/cocotb/utils.py @@ -12,25 +12,33 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Helpers for setting up and running simulations using Icarus Verilog with Cocotb.""" + import os -from pathlib import Path +import pathlib import cocotb -from cocotb.runner import check_results_file, get_runner +from cocotb.runner import check_results_file +from cocotb.runner import get_runner from cocotb.triggers import ClockCycles from xls.common import runfiles def setup_com_iverilog(): - iverilog_path = Path(runfiles.get_path("iverilog", repository = "com_icarus_iverilog")) - vvp_path = Path(runfiles.get_path("vvp", repository = "com_icarus_iverilog")) + iverilog_path = pathlib.Path( + runfiles.get_path("iverilog", repository = "com_icarus_iverilog") + ) + vvp_path = pathlib.Path( + runfiles.get_path("vvp", repository = "com_icarus_iverilog") + ) os.environ["PATH"] += os.pathsep + str(iverilog_path.parent) os.environ["PATH"] += os.pathsep + str(vvp_path.parent) - build_dir = Path(os.environ['BUILD_WORKING_DIRECTORY'], "sim_build") + build_dir = pathlib.Path(os.environ['BUILD_WORKING_DIRECTORY'], "sim_build") return build_dir def run_test(toplevel, test_module, verilog_sources): + """Builds and runs a Cocotb testbench using Icarus Verilog.""" build_dir = setup_com_iverilog() runner = get_runner("icarus") runner.build( @@ -51,7 +59,7 @@ def run_test(toplevel, test_module, verilog_sources): @cocotb.coroutine async def reset(clk, rst, cycles=1): - """Cocotb coroutine that performs the reset""" + """Cocotb coroutine that performs the reset.""" rst.value = 1 await ClockCycles(clk, cycles) rst.value = 0 diff --git a/xls/modules/zstd/cocotb/xlsstruct.py b/xls/modules/zstd/cocotb/xlsstruct.py index a2d686a8af..131be8e663 100644 --- a/xls/modules/zstd/cocotb/xlsstruct.py +++ b/xls/modules/zstd/cocotb/xlsstruct.py @@ -12,8 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Provides utilities for defining Python representations of XLS structs. +""" + import random -from dataclasses import asdict, astuple, dataclass, fields +import dataclasses from cocotb.binary import BinaryValue @@ -22,29 +26,36 @@ class TruncationError(Exception): pass def xls_dataclass(cls): - """ - Class decorator for XLS structs. + """Class decorator for XLS structs. + Usage: - @xls_dataclass - class MyStruct(XLSStruct): - ... + @xls_dataclass + class MyStruct(XLSStruct): + ... + Args: + cls (type): The class to decorate. + + Returns: + type: The dataclass-decorated class with repr disabled. """ - return dataclass(cls, repr=False) + return dataclasses.dataclass(cls, repr=False) -@dataclass +@dataclasses.dataclass class XLSStruct: - """ - Represents XLS struct on the Python side, allowing serialization/deserialization - to/from common formats and usage with XLS{Driver, Monitor}. + """Represents XLS struct on the Python side. - The intended way to use this class is to inherit from it, specify the fields with - : [= ] syntax and decorate the inheriting class with - @XLSDataclass. Objects of this class can be instantiated and used like usual - dataclass objects, with a few extra methods and properties available. They can also - be passed as arguments to XLSChannelDriver.send and will be serialized to expected - bit vector. Class can be passed to XLSChannelMonitor ``struct`` constructor argument - to automatically deserialize all transfers to the provided struct. + Allows serialization/deserialization to/from common formats and usage with + XLS{Driver, Monitor}. + + The intended way to use this class is to inherit from it, specify the fields + with : [= ] syntax and decorate the inheriting + class with @XLSDataclass. Objects of this class can be instantiated and used + like usual dataclass objects, with a few extra methods and properties + available. They can also be passed as arguments to XLSChannelDriver.send and + will be serialized to expected bit vector. Class can be passed to + XLSChannelMonitor ``struct`` constructor argument to automatically + deserialize all transfers to the provided struct. Example: @@ -70,29 +81,26 @@ class MyStruct(XLSStruct): @classmethod def _masks(cls): - """ - Returns a list of field-sized bitmasks. + """Returns a list of field-sized bitmasks. For example for fields of widths 2, 3, 4 returns [2'b11, 3'b111, 4'b1111]. """ masks = [] - for field in fields(cls): + for field in dataclasses.fields(cls): width = field.type masks += [(1 << width) - 1] return masks @classmethod def _positions(cls): - """ - Returns a list of start positions in a bit vector for - struct's fields. + """Returns a list of start positions in a bit vector for struct's fields. For example for fields of widths 1, 2, 3, 4, 5, 6 returns [20, 18, 15, 11, 6, 0] """ positions = [] - for i, field in enumerate(fields(cls)): + for i, field in enumerate(dataclasses.fields(cls)): width = field.type if i == 0: positions += [cls.total_width - width] @@ -103,66 +111,55 @@ def _positions(cls): @classmethod @property def total_width(cls): - """ - Returns total bit width of the struct - """ - return sum(field.type for field in fields(cls)) + """Returns total bit width of the struct.""" + return sum(field.type for field in dataclasses.fields(cls)) @property def value(self): - """ - Returns struct's value as a Python integer - """ + """Returns struct's value as a Python integer.""" value = 0 masks = self._masks() positions = self._positions() - for field_val, mask, pos in zip(astuple(self), masks, positions): + for field_val, mask, pos in zip( + dataclasses.astuple(self), masks, positions): if field_val > mask: - raise TruncationError(f"Signal value is wider than its bit width") + raise TruncationError("Signal value is wider than its bit width") value |= (field_val & mask) << pos return value @property def binaryvalue(self): - """ - Returns struct's value as a cocotb.binary.BinaryValue - """ + """Returns struct's value as a cocotb.binary.BinaryValue.""" return BinaryValue(self.binstr) @property def binstr(self): - """ - Returns struct's value as a string with its binary representation - """ + """Returns struct's value as a string with its binary representation.""" return f"{self.value:>0{self.total_width}b}" @property def hexstr(self): - """ - Returns struct's value as a string with its hex representation - (without leading "0x") + """Returns struct's value as a string with its hex representation. + + The result does NOT have leading "0x". """ return f"{self.value:>0{self.total_width // 4}x}" @classmethod def from_int(cls, value): - """ - Returns an instance of the struct from Python integer - """ + """Returns an instance of the struct from Python integer.""" instance = {} masks = cls._masks() positions = cls._positions() - for field, mask, pos in zip(fields(cls), masks, positions): + for field, mask, pos in zip(dataclasses.fields(cls), masks, positions): instance[field.name] = (value >> pos) & mask return cls(**instance) @classmethod def randomize(cls): - """ - Returns an instance of the struct with all fields' values randomized - """ + """Returns an instance of the struct with all fields' values randomized.""" instance = {} - for field in fields(cls): + for field in dataclasses.fields(cls): instance[field.name] = random.randrange(0, 2**field.type) return cls(**instance) @@ -171,5 +168,7 @@ def __str__(self): def __repr__(self): classname = self.__class__.__name__ - fields = [f"{name}={hex(value)}" for name, value in asdict(self).items()] - return f"{classname}({', '.join(fields)})" + class_fields = [ + f"{name}={hex(value)}" for name, value in dataclasses.asdict(self).items() + ] + return f"{classname}({', '.join(class_fields)})" diff --git a/xls/modules/zstd/memory/axi_writer_cocotb_test.py b/xls/modules/zstd/memory/axi_writer_cocotb_test.py index d655c6f8e0..2ad396c3c6 100644 --- a/xls/modules/zstd/memory/axi_writer_cocotb_test.py +++ b/xls/modules/zstd/memory/axi_writer_cocotb_test.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright 2024 The XLS Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,44 +15,39 @@ import random import logging -from pathlib import Path +import pathlib import cocotb from cocotb.clock import Clock -from cocotb.triggers import ClockCycles, Event -from cocotb.binary import BinaryValue +from cocotb.triggers import Event from cocotb_bus.scoreboard import Scoreboard -from cocotbext.axi.axis import AxiStreamSource, AxiStreamBus, AxiStreamFrame -from cocotbext.axi.axi_channels import AxiAWBus, AxiWBus, AxiBBus, AxiWriteBus, AxiAWMonitor, AxiWMonitor, AxiBMonitor, AxiBTransaction, AxiBSource, AxiBSink +from cocotbext.axi import axis +from cocotbext.axi import axi_channels from cocotbext.axi.axi_ram import AxiRamWrite from cocotbext.axi.sparse_memory import SparseMemory -from xls.modules.zstd.cocotb.channel import ( - XLSChannel, - XLSChannelDriver, - XLSChannelMonitor, -) -from xls.modules.zstd.cocotb.utils import reset, run_test -from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass +import xls.modules.zstd.cocotb.channel as xlschannel +from xls.modules.zstd.cocotb import utils +from xls.modules.zstd.cocotb import xlsstruct ID_WIDTH = 4 ADDR_WIDTH = 16 # Override default widths of AXI response signals signal_widths = {"bresp": 3} -AxiBBus._signal_widths = signal_widths -AxiBTransaction._signal_widths = signal_widths -AxiBSource._signal_widths = signal_widths -AxiBSink._signal_widths = signal_widths -AxiBMonitor._signal_widths = signal_widths - -@xls_dataclass -class AxiWriterRespStruct(XLSStruct): +axi_channels.AxiBBus._signal_widths = signal_widths +axi_channels.AxiBTransaction._signal_widths = signal_widths +axi_channels.AxiBSource._signal_widths = signal_widths +axi_channels.AxiBSink._signal_widths = signal_widths +axi_channels.AxiBMonitor._signal_widths = signal_widths + +@xlsstruct.xls_dataclass +class AxiWriterRespStruct(xlsstruct.XLSStruct): status: 1 -@xls_dataclass -class WriteRequestStruct(XLSStruct): +@xlsstruct.xls_dataclass +class WriteRequestStruct(xlsstruct.XLSStruct): address: ADDR_WIDTH length: ADDR_WIDTH @@ -65,40 +59,62 @@ def terminate_cb(_): @cocotb.test(timeout_time=20000, timeout_unit="ms") async def ram_test(dut): - GENERIC_ADDR_REQ_CHANNEL = "write_req" - GENERIC_ADDR_RESP_CHANNEL = "write_resp" - AXI_STREAM_CHANNEL = "axi_st_read" - AXI_AW_CHANNEL = "axi_aw" - AXI_W_CHANNEL = "axi_w" - AXI_B_CHANNEL = "axi_b" + generic_addr_req_channel = "write_req" + generic_addr_resp_channel = "write_resp" + axi_stream_channel = "axi_st_read" + axi_aw_channel = "axi_aw" + axi_w_channel = "axi_w" + axi_b_channel = "axi_b" terminate = Event() mem_size = 2**ADDR_WIDTH test_count = 200 - (addr_req_input, axi_st_input, addr_resp_expect, memory_verification, expected_memory) = generate_test_data_random(test_count, mem_size) + ( + addr_req_input, + axi_st_input, + addr_resp_expect, + memory_verification, + expected_memory + ) = generate_test_data_random(test_count, mem_size) dut.rst.setimmediatevalue(0) clock = Clock(dut.clk, 10, units="us") cocotb.start_soon(clock.start()) - resp_bus = XLSChannel(dut, GENERIC_ADDR_RESP_CHANNEL, dut.clk, start_now=True) - - driver_addr_req = XLSChannelDriver(dut, GENERIC_ADDR_REQ_CHANNEL, dut.clk) - driver_axi_st = AxiStreamSource(AxiStreamBus.from_prefix(dut, AXI_STREAM_CHANNEL), dut.clk, dut.rst) - - bus_axi_aw = AxiAWBus.from_prefix(dut, AXI_AW_CHANNEL) - bus_axi_w = AxiWBus.from_prefix(dut, AXI_W_CHANNEL) - bus_axi_b = AxiBBus.from_prefix(dut, AXI_B_CHANNEL) - bus_axi_write = AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) - - monitor_addr_req = XLSChannelMonitor(dut, GENERIC_ADDR_REQ_CHANNEL, dut.clk, WriteRequestStruct) - monitor_addr_resp = XLSChannelMonitor(dut, GENERIC_ADDR_RESP_CHANNEL, dut.clk, AxiWriterRespStruct) - monitor_axi_aw = AxiAWMonitor(bus_axi_aw, dut.clk, dut.rst) - monitor_axi_w = AxiWMonitor(bus_axi_w, dut.clk, dut.rst) - monitor_axi_b = AxiBMonitor(bus_axi_b, dut.clk, dut.rst) + # prefix unused objects with unused_ + # to suppress linter and keep the objects alive + unused_resp_bus = xlschannel.XLSChannel( + dut, generic_addr_resp_channel, dut.clk, start_now=True + ) + + driver_addr_req = xlschannel.XLSChannelDriver( + dut, generic_addr_req_channel, dut.clk + ) + driver_axi_st = axis.AxiStreamSource( + axis.AxiStreamBus.from_prefix(dut, axi_stream_channel), + dut.clk, + dut.rst + ) + + bus_axi_aw = axi_channels.AxiAWBus.from_prefix(dut, axi_aw_channel) + bus_axi_w = axi_channels.AxiWBus.from_prefix(dut, axi_w_channel) + bus_axi_b = axi_channels.AxiBBus.from_prefix(dut, axi_b_channel) + bus_axi_write = axi_channels.AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) + + unused_monitor_addr_req = xlschannel.XLSChannelMonitor( + dut, generic_addr_req_channel, dut.clk, WriteRequestStruct + ) + monitor_addr_resp = xlschannel.XLSChannelMonitor( + dut, generic_addr_resp_channel, dut.clk, AxiWriterRespStruct + ) + unused_monitor_axi_aw = axi_channels.AxiAWMonitor( + bus_axi_aw, dut.clk, dut.rst + ) + unused_monitor_axi_w = axi_channels.AxiWMonitor(bus_axi_w, dut.clk, dut.rst) + unused_monitor_axi_b = axi_channels.AxiBMonitor(bus_axi_b, dut.clk, dut.rst) set_termination_event(monitor_addr_resp, terminate, test_count) @@ -112,15 +128,33 @@ async def ram_test(dut): scoreboard = Scoreboard(dut) scoreboard.add_interface(monitor_addr_resp, addr_resp_expect) - await reset(dut.clk, dut.rst, cycles=10) + await utils.reset(dut.clk, dut.rst, cycles=10) await cocotb.start(driver_addr_req.send(addr_req_input)) await cocotb.start(drive_axi_st(driver_axi_st, axi_st_input)) await terminate.wait() for bundle in memory_verification: - memory_contents = bytearray(memory.read(bundle["base_address"], bundle["length"])) - expected_memory_contents = bytearray(expected_memory.read(bundle["base_address"], bundle["length"])) - assert memory_contents == expected_memory_contents, "{} bytes of memory contents at base address {}:\n{}\nvs\n{}\nHEXDUMP:\n{}\nvs\n{}".format(hex(bundle["length"]), hex(bundle["base_address"]), memory_contents, expected_memory_contents, memory.hexdump(bundle["base_address"], bundle["length"]), expected_memory.hexdump(bundle["base_address"], bundle["length"])) + memory_contents = bytearray( + memory.read(bundle["base_address"], bundle["length"]) + ) + expected_memory_contents = bytearray( + expected_memory.read(bundle["base_address"], bundle["length"]) + ) + assert memory_contents == expected_memory_contents, ( + ( + "{} bytes of memory contents at base address {}:\n" + "{}\nvs\n{}" + "\nHEXDUMP:\n{}" + "\nvs\n{}" + ).format( + hex(bundle["length"]), + hex(bundle["base_address"]), + memory_contents, + expected_memory_contents, + memory.hexdump(bundle["base_address"], bundle["length"]), + expected_memory.hexdump(bundle["base_address"], bundle["length"]) + ) + ) @cocotb.coroutine async def drive_axi_st(driver, inputs): @@ -128,8 +162,6 @@ async def drive_axi_st(driver, inputs): await driver.send(axi_st_input) def generate_test_data_random(test_count, mem_size): - AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x - addr_req_input = [] axi_st_input = [] addr_resp_expect = [] @@ -154,7 +186,12 @@ def generate_test_data_random(test_count, mem_size): addr_req_input.append(transfer_req) data_to_write = random.randbytes(xfer_len) - axi_st_frame = AxiStreamFrame(tdata=data_to_write, tkeep=[15]*xfer_len, tid=(i % (1 << ID_WIDTH)), tdest=(i % (1 << ID_WIDTH))) + axi_st_frame = axis.AxiStreamFrame( + tdata=data_to_write, + tkeep=[15]*xfer_len, + tid=i % (1 << ID_WIDTH), + tdest=(i % (1 << ID_WIDTH)) + ) axi_st_input.append(axi_st_frame) write_expected_memory(transfer_req, axi_st_frame.tdata, memory) @@ -167,36 +204,48 @@ def generate_test_data_random(test_count, mem_size): addr_resp_expect = [AxiWriterRespStruct(status=False)] * test_count - return (addr_req_input, axi_st_input, addr_resp_expect, memory_verification, memory) + return ( + addr_req_input, + axi_st_input, + addr_resp_expect, + memory_verification, + memory + ) def bytes_to_4k_boundary(addr): - AXI_4K_BOUNDARY = 0x1000 - return AXI_4K_BOUNDARY - (addr % AXI_4K_BOUNDARY) + axi_4k_boundary = 0x1000 + return axi_4k_boundary - (addr % axi_4k_boundary) def write_expected_memory(transfer_req, data_to_write, memory): - """ - Write test data to reference memory keeping the AXI 4kb boundary - by spliting the write requests into smaller ones. - """ - prev_id = 0 - address = transfer_req.address - length = transfer_req.length - - BYTES_IN_TRANSFER = 4 - MAX_AXI_BURST_BYTES = 256 * BYTES_IN_TRANSFER - - while (length > 0): - bytes_to_4k = bytes_to_4k_boundary(address) - new_len = min(length, min(bytes_to_4k, MAX_AXI_BURST_BYTES)) - new_data = data_to_write[prev_id:prev_id+new_len] - memory.write(address, new_data) - address = address + new_len - length = length - new_len - prev_id = prev_id + new_len + """Write test data to reference memory keeping the AXI 4kb boundary. + + Split the write requests into smaller ones as needed. + + Args: + transfer_req: The transfer request object containing address and length + information. + data_to_write: A bytes-like object or list of data that needs to be + written. + memory: SparseMemory object simulating memory storage, where the data will + be written. + """ + prev_id = 0 + address = transfer_req.address + length = transfer_req.length + + bytes_in_transfer = 4 + max_axi_burst_bytes = 256 * bytes_in_transfer + + while (length > 0): + bytes_to_4k = bytes_to_4k_boundary(address) + new_len = min(length, min(bytes_to_4k, max_axi_burst_bytes)) + new_data = data_to_write[prev_id:prev_id+new_len] + memory.write(address, new_data) + address = address + new_len + length = length - new_len + prev_id = prev_id + new_len def generate_test_data_arbitrary(mem_size): - AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x - addr_req_input = [] axi_st_input = [] addr_resp_expect = [] @@ -215,12 +264,21 @@ def generate_test_data_arbitrary(mem_size): addr_req_input.append(transfer_req) data_chunks = [] - data_bytes = [[(0xEF + j) & 0xFF, 0xBE, 0xAD, 0xDE] for j in range(xfer_len[i])] - data_words = [int.from_bytes(data_bytes[j]) for j in range(xfer_len[i])] + data_bytes = [ + [(0xEF + j) & 0xFF, 0xBE, 0xAD, 0xDE] for j in range(xfer_len[i]) + ] + unused_data_words = [ + int.from_bytes(data_bytes[j]) for j in range(xfer_len[i]) + ] for j in range(xfer_len[i]): data_chunks += data_bytes[j] data_to_write = bytearray(data_chunks) - axi_st_frame = AxiStreamFrame(tdata=data_to_write, tkeep=[15]*xfer_len[i], tid=i, tdest=i) + axi_st_frame = axis.AxiStreamFrame( + tdata=data_to_write, + tkeep=[15]*xfer_len[i], + tid=i, + tdest=i + ) axi_st_input.append(axi_st_frame) write_expected_memory(transfer_req, axi_st_frame.tdata, memory) @@ -233,7 +291,13 @@ def generate_test_data_arbitrary(mem_size): addr_resp_expect = [AxiWriterRespStruct(status=False)] * testcase_num - return (addr_req_input, axi_st_input, addr_resp_expect, memory_verification, memory) + return ( + addr_req_input, + axi_st_input, + addr_resp_expect, + memory_verification, + memory + ) if __name__ == "__main__": toplevel = "axi_writer_wrapper" @@ -241,5 +305,5 @@ def generate_test_data_arbitrary(mem_size): "xls/modules/zstd/memory/axi_writer.v", "xls/modules/zstd/memory/rtl/axi_writer_wrapper.v", ] - test_module=[Path(__file__).stem] - run_test(toplevel, test_module, verilog_sources) + test_module=[pathlib.Path(__file__).stem] + utils.run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/memory/mem_reader_cocotb_test.py b/xls/modules/zstd/memory/mem_reader_cocotb_test.py index 3e48904f6a..8f862bc5ca 100644 --- a/xls/modules/zstd/memory/mem_reader_cocotb_test.py +++ b/xls/modules/zstd/memory/mem_reader_cocotb_test.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright 2024 The XLS Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,26 +13,22 @@ # limitations under the License. +import pathlib import random import sys import warnings -from pathlib import Path import cocotb from cocotb.clock import Clock -from cocotb.triggers import ClockCycles, Event +from cocotb.triggers import Event from cocotb_bus.scoreboard import Scoreboard -from cocotbext.axi.axi_channels import AxiARBus, AxiRBus, AxiReadBus, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor +from cocotbext.axi import axi_channels from cocotbext.axi.axi_ram import AxiRamRead from cocotbext.axi.sparse_memory import SparseMemory -from xls.modules.zstd.cocotb.channel import ( - XLSChannel, - XLSChannelDriver, - XLSChannelMonitor, -) -from xls.modules.zstd.cocotb.utils import reset, run_test -from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass +import xls.modules.zstd.cocotb.channel as xlschannel +from xls.modules.zstd.cocotb import utils +from xls.modules.zstd.cocotb import xlsstruct # to disable warnings from hexdiff used by cocotb's Scoreboard warnings.filterwarnings("ignore", category=DeprecationWarning) @@ -60,34 +55,34 @@ # Override default widths of AXI response signals signal_widths = {"rresp": 3, "rlast": 1} -AxiRBus._signal_widths = signal_widths -AxiRTransaction._signal_widths = signal_widths -AxiRSource._signal_widths = signal_widths -AxiRSink._signal_widths = signal_widths -AxiRMonitor._signal_widths = signal_widths - -@xls_dataclass -class MemReaderReq(XLSStruct): +axi_channels.AxiRBus._signal_widths = signal_widths +axi_channels.AxiRTransaction._signal_widths = signal_widths +axi_channels.AxiRSource._signal_widths = signal_widths +axi_channels.AxiRSink._signal_widths = signal_widths +axi_channels.AxiRMonitor._signal_widths = signal_widths + +@xlsstruct.xls_dataclass +class MemReaderReq(xlsstruct.XLSStruct): addr: DSLX_ADDR_W length: DSLX_ADDR_W -@xls_dataclass -class MemReaderResp(XLSStruct): +@xlsstruct.xls_dataclass +class MemReaderResp(xlsstruct.XLSStruct): status: STATUS_W data: DSLX_DATA_W length: DSLX_ADDR_W last: LAST_W -@xls_dataclass -class AxiReaderReq(XLSStruct): +@xlsstruct.xls_dataclass +class AxiReaderReq(xlsstruct.XLSStruct): addr: AXI_ADDR_W len: AXI_ADDR_W -@xls_dataclass -class AxiStream(XLSStruct): +@xlsstruct.xls_dataclass +class AxiStream(xlsstruct.XLSStruct): data: AXI_DATA_W str: AXI_DATA_W // 8 keep: AXI_DATA_W // 8 = 0 @@ -96,13 +91,13 @@ class AxiStream(XLSStruct): dest: DEST_W = 0 -@xls_dataclass -class AxiReaderError(XLSStruct): +@xlsstruct.xls_dataclass +class AxiReaderError(xlsstruct.XLSStruct): error: ERROR_W -@xls_dataclass -class AxiAr(XLSStruct): +@xlsstruct.xls_dataclass +class AxiAr(xlsstruct.XLSStruct): id: ID_W addr: AXI_ADDR_W region: 4 @@ -114,8 +109,8 @@ class AxiAr(XLSStruct): qos: 4 -@xls_dataclass -class AxiR(XLSStruct): +@xlsstruct.xls_dataclass +class AxiR(xlsstruct.XLSStruct): id: ID_W data: AXI_DATA_W resp: 3 @@ -161,11 +156,14 @@ def generate_test_data(test_cases, xfer_base=0x0, seed=1234): req += [MemReaderReq(addr=xfer_addr, length=xfer_length)] rem = xfer_length % data_w_div8 - for addr in range(xfer_addr, xfer_max_addr - (data_w_div8 - 1), data_w_div8): + range_end = xfer_max_addr - (data_w_div8 - 1) + for addr in range(xfer_addr, range_end, data_w_div8): last = ((addr + data_w_div8) >= xfer_max_addr) & (rem == 0) data = random.randint(0, 1 << (data_w_div8 * 8)) mem_writes.update({addr: data}) - resp += [MemReaderResp(status=0, data=data, length=data_w_div8, last=last)] + resp += [ + MemReaderResp(status=0, data=data, length=data_w_div8, last=last) + ] if rem > 0: addr = xfer_max_addr - rem @@ -177,16 +175,27 @@ def generate_test_data(test_cases, xfer_base=0x0, seed=1234): return (req, resp, mem_writes) -async def test_mem_reader(dut, req_input, resp_output, mem_contents={}): +async def test_mem_reader(dut, req_input, resp_output, mem_contents=None): + if mem_contents is None: + mem_contents = {} + clock = Clock(dut.clk, 10, units="us") cocotb.start_soon(clock.start()) - mem_reader_resp_bus = XLSChannel( + # prefix unused objects with unused_ + # to suppress linter and keep the objects alive + unused_mem_reader_resp_bus = xlschannel.XLSChannel( dut, MEM_READER_RESP_CHANNEL, dut.clk, start_now=True ) - mem_reader_req_driver = XLSChannelDriver(dut, MEM_READER_REQ_CHANNEL, dut.clk) - mem_reader_resp_monitor = XLSChannelMonitor( - dut, MEM_READER_RESP_CHANNEL, dut.clk, MemReaderResp, callback=print_callback() + mem_reader_req_driver = xlschannel.XLSChannelDriver( + dut, MEM_READER_REQ_CHANNEL, dut.clk + ) + mem_reader_resp_monitor = xlschannel.XLSChannelMonitor( + dut, + MEM_READER_RESP_CHANNEL, + dut.clk, + MemReaderResp, + callback=print_callback() ) terminate = Event() @@ -195,18 +204,20 @@ async def test_mem_reader(dut, req_input, resp_output, mem_contents={}): scoreboard = Scoreboard(dut) scoreboard.add_interface(mem_reader_resp_monitor, resp_output) - ar_bus = AxiARBus.from_prefix(dut, AXI_AR_PREFIX) - r_bus = AxiRBus.from_prefix(dut, AXI_R_PREFIX) - axi_read_bus = AxiReadBus(ar=ar_bus, r=r_bus) + ar_bus = axi_channels.AxiARBus.from_prefix(dut, AXI_AR_PREFIX) + r_bus = axi_channels.AxiRBus.from_prefix(dut, AXI_R_PREFIX) + axi_read_bus = axi_channels.AxiReadBus(ar=ar_bus, r=r_bus) mem_size = 2**AXI_ADDR_W sparse_mem = SparseMemory(mem_size) for addr, data in mem_contents.items(): sparse_mem.write(addr, (data).to_bytes(8, "little")) - memory = AxiRamRead(axi_read_bus, dut.clk, dut.rst, size=mem_size, mem=sparse_mem) + unused_memory = AxiRamRead( + axi_read_bus, dut.clk, dut.rst, size=mem_size, mem=sparse_mem + ) - await reset(dut.clk, dut.rst, cycles=10) + await utils.reset(dut.clk, dut.rst, cycles=10) await mem_reader_req_driver.send(req_input) await terminate.wait() @@ -260,12 +271,12 @@ async def mem_reader_aligned_transfer_shorter_than_bus4(dut): if __name__ == "__main__": - sys.path.append(str(Path(__file__).parent)) + sys.path.append(str(pathlib.Path(__file__).parent)) toplevel = "mem_reader_wrapper" verilog_sources = [ "xls/modules/zstd/memory/mem_reader_adv.v", "xls/modules/zstd/memory/rtl/mem_reader_wrapper.v", ] - test_module = [Path(__file__).stem] - run_test(toplevel, test_module, verilog_sources) + test_module = [pathlib.Path(__file__).stem] + utils.run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/memory/mem_writer_cocotb_test.py b/xls/modules/zstd/memory/mem_writer_cocotb_test.py index 69b39a7894..5767fbba2e 100644 --- a/xls/modules/zstd/memory/mem_writer_cocotb_test.py +++ b/xls/modules/zstd/memory/mem_writer_cocotb_test.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright 2024 The XLS Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,60 +15,54 @@ import random import logging -from enum import Enum -from pathlib import Path +import enum +import pathlib import cocotb from cocotb.clock import Clock -from cocotb.triggers import ClockCycles, Event -from cocotb.binary import BinaryValue +from cocotb.triggers import Event from cocotb_bus.scoreboard import Scoreboard -from cocotbext.axi.axis import AxiStreamSource, AxiStreamBus, AxiStreamFrame -from cocotbext.axi.axi_channels import AxiAWBus, AxiWBus, AxiBBus, AxiWriteBus, AxiAWMonitor, AxiWMonitor, AxiBMonitor, AxiBTransaction, AxiBSource, AxiBSink +from cocotbext.axi import axi_channels from cocotbext.axi.axi_ram import AxiRamWrite from cocotbext.axi.sparse_memory import SparseMemory -from xls.modules.zstd.cocotb.channel import ( - XLSChannel, - XLSChannelDriver, - XLSChannelMonitor, -) -from xls.modules.zstd.cocotb.utils import reset, run_test -from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass +import xls.modules.zstd.cocotb.channel as xlschannel +from xls.modules.zstd.cocotb import utils +from xls.modules.zstd.cocotb import xlsstruct DATA_WIDTH = 32 ADDR_WIDTH = 16 # Override default widths of AXI response signals signal_widths = {"bresp": 3} -AxiBBus._signal_widths = signal_widths -AxiBTransaction._signal_widths = signal_widths -AxiBSource._signal_widths = signal_widths -AxiBSink._signal_widths = signal_widths -AxiBMonitor._signal_widths = signal_widths - -@xls_dataclass -class DataInStruct(XLSStruct): +axi_channels.AxiBBus._signal_widths = signal_widths +axi_channels.AxiBTransaction._signal_widths = signal_widths +axi_channels.AxiBSource._signal_widths = signal_widths +axi_channels.AxiBSink._signal_widths = signal_widths +axi_channels.AxiBMonitor._signal_widths = signal_widths + +@xlsstruct.xls_dataclass +class DataInStruct(xlsstruct.XLSStruct): data: DATA_WIDTH length: ADDR_WIDTH last: 1 -@xls_dataclass -class WriteReqStruct(XLSStruct): +@xlsstruct.xls_dataclass +class WriteReqStruct(xlsstruct.XLSStruct): offset: ADDR_WIDTH length: ADDR_WIDTH -@xls_dataclass -class MemWriterRespStruct(XLSStruct): +@xlsstruct.xls_dataclass +class MemWriterRespStruct(xlsstruct.XLSStruct): status: 1 -class MemWriterRespStatus(Enum): +class MemWriterRespStatus(enum.Enum): OKAY = 0 ERROR = 1 -@xls_dataclass -class WriteRequestStruct(XLSStruct): +@xlsstruct.xls_dataclass +class WriteRequestStruct(xlsstruct.XLSStruct): address: ADDR_WIDTH length: ADDR_WIDTH @@ -79,13 +72,22 @@ def terminate_cb(_): event.set() monitor.add_callback(terminate_cb) -async def test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt): - GENERIC_WRITE_REQ_CHANNEL = "req" - GENERIC_WRITE_RESP_CHANNEL = "resp" - GENERIC_DATA_IN_CHANNEL = "data_in" - AXI_AW_CHANNEL = "axi_aw" - AXI_W_CHANNEL = "axi_w" - AXI_B_CHANNEL = "axi_b" +async def test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt +): + generic_write_req_channel = "req" + generic_write_resp_channel = "resp" + generic_data_in_channel = "data_in" + axi_aw_channel = "axi_aw" + axi_w_channel = "axi_w" + axi_b_channel = "axi_b" terminate = Event() @@ -94,22 +96,38 @@ async def test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_ clock = Clock(dut.clk, 10, units="us") cocotb.start_soon(clock.start()) - resp_bus = XLSChannel(dut, GENERIC_WRITE_RESP_CHANNEL, dut.clk, start_now=True) - - driver_write_req = XLSChannelDriver(dut, GENERIC_WRITE_REQ_CHANNEL, dut.clk) - driver_data_in = XLSChannelDriver(dut, GENERIC_DATA_IN_CHANNEL, dut.clk) - - bus_axi_aw = AxiAWBus.from_prefix(dut, AXI_AW_CHANNEL) - bus_axi_w = AxiWBus.from_prefix(dut, AXI_W_CHANNEL) - bus_axi_b = AxiBBus.from_prefix(dut, AXI_B_CHANNEL) - bus_axi_write = AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) - - monitor_write_req = XLSChannelMonitor(dut, GENERIC_WRITE_REQ_CHANNEL, dut.clk, WriteRequestStruct) - monitor_data_in = XLSChannelMonitor(dut, GENERIC_DATA_IN_CHANNEL, dut.clk, WriteRequestStruct) - monitor_write_resp = XLSChannelMonitor(dut, GENERIC_WRITE_RESP_CHANNEL, dut.clk, MemWriterRespStruct) - monitor_axi_aw = AxiAWMonitor(bus_axi_aw, dut.clk, dut.rst) - monitor_axi_w = AxiWMonitor(bus_axi_w, dut.clk, dut.rst) - monitor_axi_b = AxiBMonitor(bus_axi_b, dut.clk, dut.rst) + # prefix unused objects with unused_ + # to suppress linter and keep the objects alive + unused_resp_bus = xlschannel.XLSChannel( + dut, generic_write_resp_channel, dut.clk, start_now=True + ) + + driver_write_req = xlschannel.XLSChannelDriver( + dut, generic_write_req_channel, dut.clk + ) + driver_data_in = xlschannel.XLSChannelDriver( + dut, generic_data_in_channel, dut.clk + ) + + bus_axi_aw = axi_channels.AxiAWBus.from_prefix(dut, axi_aw_channel) + bus_axi_w = axi_channels.AxiWBus.from_prefix(dut, axi_w_channel) + bus_axi_b = axi_channels.AxiBBus.from_prefix(dut, axi_b_channel) + bus_axi_write = axi_channels.AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) + + unused_monitor_write_req = xlschannel.XLSChannelMonitor( + dut, generic_write_req_channel, dut.clk, WriteRequestStruct + ) + unused_monitor_data_in = xlschannel.XLSChannelMonitor( + dut, generic_data_in_channel, dut.clk, WriteRequestStruct + ) + monitor_write_resp = xlschannel.XLSChannelMonitor( + dut, generic_write_resp_channel, dut.clk, MemWriterRespStruct + ) + unused_monitor_axi_aw = axi_channels.AxiAWMonitor( + bus_axi_aw, dut.clk, dut.rst + ) + unused_monitor_axi_w = axi_channels.AxiWMonitor(bus_axi_w, dut.clk, dut.rst) + unused_monitor_axi_b = axi_channels.AxiBMonitor(bus_axi_b, dut.clk, dut.rst) set_termination_event(monitor_write_resp, terminate, resp_cnt) @@ -122,98 +140,323 @@ async def test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_ scoreboard = Scoreboard(dut) scoreboard.add_interface(monitor_write_resp, write_resp_expect) - await reset(dut.clk, dut.rst, cycles=10) + await utils.reset(dut.clk, dut.rst, cycles=10) await cocotb.start(driver_write_req.send(write_req_input)) await cocotb.start(driver_data_in.send(data_in_input)) await terminate.wait() for bundle in memory_verification: - memory_contents = bytearray(memory.read(bundle["base_address"], bundle["length"])) - expected_memory_contents = bytearray(expected_memory.read(bundle["base_address"], bundle["length"])) - assert memory_contents == expected_memory_contents, "{} bytes of memory contents at base address {}:\n{}\nvs\n{}\nHEXDUMP:\n{}\nvs\n{}".format(hex(bundle["length"]), hex(bundle["base_address"]), memory_contents, expected_memory_contents, memory.hexdump(bundle["base_address"], bundle["length"]), expected_memory.hexdump(bundle["base_address"], bundle["length"])) + memory_contents = bytearray( + memory.read(bundle["base_address"], bundle["length"]) + ) + expected_memory_contents = bytearray( + expected_memory.read(bundle["base_address"], bundle["length"]) + ) + assert memory_contents == expected_memory_contents, ( + ( + "{} bytes of memory contents at base address {}:\n" + "{}\nvs\n{}" + "\nHEXDUMP:\n{}" + "\nvs\n{}" + ).format( + hex(bundle["length"]), + hex(bundle["base_address"]), + memory_contents, + expected_memory_contents, + memory.hexdump(bundle["base_address"], bundle["length"]), + expected_memory.hexdump(bundle["base_address"], bundle["length"]) + ) + ) @cocotb.test(timeout_time=2000, timeout_unit="ms") async def ram_test_single_burst_1_transfer(dut): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_1_transfer) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_1_transfer) + + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=2000, timeout_unit="ms") async def ram_test_single_burst_2_transfers(dut): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_2_transfers) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary( + mem_size, test_cases_single_burst_2_transfers + ) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=2000, timeout_unit="ms") async def ram_test_single_burst_almost_max_burst_transfer(dut): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_almost_max_burst_transfer) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary( + mem_size, test_cases_single_burst_almost_max_burst_transfer + ) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=2000, timeout_unit="ms") async def ram_test_single_burst_max_burst_transfer(dut): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_single_burst_max_burst_transfer) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary( + mem_size, test_cases_single_burst_max_burst_transfer + ) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=2000, timeout_unit="ms") async def ram_test_multiburst_2_full_bursts(dut): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_2_full_bursts) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary( + mem_size, test_cases_multiburst_2_full_bursts + ) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=2000, timeout_unit="ms") async def ram_test_multiburst_1_full_burst_and_single_transfer(dut): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_1_full_burst_and_single_transfer) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary( + mem_size, test_cases_multiburst_1_full_burst_and_single_transfer + ) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=2000, timeout_unit="ms") async def ram_test_multiburst_crossing_4kb_boundary(dut): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_crossing_4kb_boundary) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary( + mem_size, test_cases_multiburst_crossing_4kb_boundary + ) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=2000, timeout_unit="ms") -async def ram_test_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts(dut): +async def ram_test_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts( + dut, +): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary( + mem_size, + test_cases_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts, + ) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=2000, timeout_unit="ms") -async def ram_test_multiburst_crossing_4kb_boundary_with_2_full_bursts_and_1_transfer(dut): +async def ram_test_multiburst_crossing_4kb_boundary_with_2_full_bursts_and_1_transfer( + dut, +): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_arbitrary(mem_size, test_cases_multiburst_crossing_4kb_boundary_with_2_full_bursts_and_1_transfer) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_test_data_arbitrary( + mem_size, + test_cases_multiburst_crossing_4kb_boundary_with_2_full_bursts_and_1_transfer, + ) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=5000, timeout_unit="ms") async def ram_test_not_full_packets(dut): mem_size = 2**ADDR_WIDTH - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_padded_test_data_arbitrary(mem_size, test_cases_not_full_packets) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) = generate_padded_test_data_arbitrary(mem_size, test_cases_not_full_packets) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt, + ) + @cocotb.test(timeout_time=5000, timeout_unit="ms") async def ram_test_random(dut): mem_size = 2**ADDR_WIDTH test_count = 50 - (write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) = generate_test_data_random(test_count, mem_size) - await test_writer(dut, mem_size, write_req_input, data_in_input, write_resp_expect, memory_verification, expected_memory, resp_cnt) + ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt + ) = generate_test_data_random(test_count, mem_size) + await test_writer( + dut, + mem_size, + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + expected_memory, + resp_cnt + ) def generate_test_data_random(test_count, mem_size): - AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x - write_req_input = [] data_in_input = [] write_resp_expect = [] @@ -224,7 +467,7 @@ def generate_test_data_random(test_count, mem_size): xfer_baseaddr = 0 - for i in range(test_count): + for _ in range(test_count): # Generate offset from the absolute address max_xfer_offset = mem_size - xfer_baseaddr xfer_offset = random.randrange(0, max_xfer_offset) @@ -272,37 +515,51 @@ def generate_test_data_random(test_count, mem_size): } memory_verification.append(memory_bundle) - write_resp_expect = [MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value)] * test_count + write_resp_expect = [ + MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value) + ] * test_count - return (write_req_input, data_in_input, write_resp_expect, memory_verification, memory, test_count) + return ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + memory, + test_count + ) def bytes_to_4k_boundary(addr): - AXI_4K_BOUNDARY = 0x1000 - return AXI_4K_BOUNDARY - (addr % AXI_4K_BOUNDARY) + axi_4k_boundary = 0x1000 + return axi_4k_boundary - (addr % axi_4k_boundary) def write_expected_memory(transfer_req, data_to_write, memory): - """ - Write test data to reference memory keeping the AXI 4kb boundary - by spliting the write requests into smaller ones. - """ - prev_id = 0 - address = transfer_req.address - length = transfer_req.length - - BYTES_IN_TRANSFER = 4 - MAX_AXI_BURST_BYTES = 256 * BYTES_IN_TRANSFER - - while (length > 0): - bytes_to_4k = bytes_to_4k_boundary(address) - new_len = min(length, min(bytes_to_4k, MAX_AXI_BURST_BYTES)) - new_data = data_to_write[prev_id:prev_id+new_len] - memory.write(address, new_data) - address = address + new_len - length = length - new_len - prev_id = prev_id + new_len + """ + Write test data to reference memory keeping the AXI 4kb boundary. + + Split the write requests into smaller ones as needed. + + Args: + transfer_req: The transfer request object containing address and length information. + data_to_write: A bytes-like object or list of data that needs to be written. + memory: SparseMemory object simulating memory storage, where the data will be written. + """ + prev_id = 0 + address = transfer_req.address + length = transfer_req.length + + bytes_in_transfer = 4 + max_axi_burst_bytes = 256 * bytes_in_transfer + + while (length > 0): + bytes_to_4k = bytes_to_4k_boundary(address) + new_len = min(length, min(bytes_to_4k, max_axi_burst_bytes)) + new_data = data_to_write[prev_id:prev_id+new_len] + memory.write(address, new_data) + address = address + new_len + length = length - new_len + prev_id = prev_id + new_len def generate_test_data_arbitrary(mem_size, test_cases): - AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x test_count = len(test_cases) random.seed(1234) @@ -364,12 +621,20 @@ def generate_test_data_arbitrary(mem_size, test_cases): } memory_verification.append(memory_bundle) - write_resp_expect = [MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value)] * test_count + write_resp_expect = [ + MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value) + ] * test_count - return (write_req_input, data_in_input, write_resp_expect, memory_verification, memory, test_count) + return ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + memory, + test_count + ) def generate_padded_test_data_arbitrary(mem_size, test_cases): - AXI_AXSIZE_ENCODING_MAX_4B_TRANSFER = 2 # Must be in sync with AXI_AXSIZE_ENCODING enum in axi.x test_count = len(test_cases) random.seed(1234) @@ -412,9 +677,12 @@ def generate_padded_test_data_arbitrary(mem_size, test_cases): last = packet_len == bytes_to_packetize data_in = DataInStruct( - data = int.from_bytes(data_to_write[packetized_bytes:packetized_bytes+packet_len], byteorder='little'), - length = packet_len, - last = last + data=int.from_bytes( + data_to_write[packetized_bytes:packetized_bytes+packet_len], + byteorder='little' + ), + length=packet_len, + last=last ) data_in_input.append(data_in) @@ -435,9 +703,18 @@ def generate_padded_test_data_arbitrary(mem_size, test_cases): } memory_verification.append(memory_bundle) - write_resp_expect = [MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value)] * test_count + write_resp_expect = [ + MemWriterRespStruct(status=MemWriterRespStatus.OKAY.value) + ] * test_count - return (write_req_input, data_in_input, write_resp_expect, memory_verification, memory, test_count) + return ( + write_req_input, + data_in_input, + write_resp_expect, + memory_verification, + memory, + test_count + ) if __name__ == "__main__": toplevel = "mem_writer_wrapper" @@ -445,8 +722,8 @@ def generate_padded_test_data_arbitrary(mem_size, test_cases): "xls/modules/zstd/memory/mem_writer.v", "xls/modules/zstd/memory/rtl/mem_writer_wrapper.v", ] - test_module=[Path(__file__).stem] - run_test(toplevel, test_module, verilog_sources) + test_module=[pathlib.Path(__file__).stem] + utils.run_test(toplevel, test_module, verilog_sources) test_cases_single_burst_1_transfer = [ # Aligned Address; Aligned Length @@ -611,9 +888,11 @@ def generate_padded_test_data_arbitrary(mem_size, test_cases): ] test_cases_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts = [ - # Aligned Address; Aligned Length; Multi-Burst; crossing 4kB boundary with perfectly aligned full bursts + # Aligned Address; Aligned Length; Multi-Burst; + # crossing 4kB boundary with perfectly aligned full bursts (0x0C00, 0x800), - # Unaligned Address; Unaligned Length; Multi-Burst; crossing 4kB boundary with perfectly aligned full bursts + # Unaligned Address; Unaligned Length; Multi-Burst; + # crossing 4kB boundary with perfectly aligned full bursts (0x1C01, 0x7FF), (0x2C02, 0x7FE), (0x3C03, 0x7FD), diff --git a/xls/modules/zstd/memory/rtl/mem_reader_wrapper.v b/xls/modules/zstd/memory/rtl/mem_reader_wrapper.v index 0dfd2dd941..12e1fe4478 100644 --- a/xls/modules/zstd/memory/rtl/mem_reader_wrapper.v +++ b/xls/modules/zstd/memory/rtl/mem_reader_wrapper.v @@ -18,18 +18,18 @@ `default_nettype none module mem_reader_wrapper #( - parameter DSLX_DATA_W = 64, - parameter DSLX_ADDR_W = 16, - parameter AXI_DATA_W = 128, - parameter AXI_ADDR_W = 16, - parameter AXI_DEST_W = 8, - parameter AXI_ID_W = 8, - - parameter CTRL_W = (DSLX_ADDR_W), - parameter REQ_W = (2 * DSLX_ADDR_W), - parameter RESP_W = (1 + DSLX_DATA_W + DSLX_ADDR_W + 1), - parameter AXI_AR_W = (AXI_ID_W + AXI_ADDR_W + 28), - parameter AXI_R_W = (AXI_ID_W + AXI_DATA_W + 4) + parameter int DSLX_DATA_W = 64, + parameter int DSLX_ADDR_W = 16, + parameter int AXI_DATA_W = 128, + parameter int AXI_ADDR_W = 16, + parameter int AXI_DEST_W = 8, + parameter int AXI_ID_W = 8, + + parameter int CTRL_W = (DSLX_ADDR_W), + parameter int REQ_W = (2 * DSLX_ADDR_W), + parameter int RESP_W = (1 + DSLX_DATA_W + DSLX_ADDR_W + 1), + parameter int AXI_AR_W = (AXI_ID_W + AXI_ADDR_W + 28), + parameter int AXI_R_W = (AXI_ID_W + AXI_DATA_W + 4) ) ( input wire clk, input wire rst, diff --git a/xls/modules/zstd/memory/rtl/mem_writer_wrapper.v b/xls/modules/zstd/memory/rtl/mem_writer_wrapper.v index 958383c282..6b53dbd805 100644 --- a/xls/modules/zstd/memory/rtl/mem_writer_wrapper.v +++ b/xls/modules/zstd/memory/rtl/mem_writer_wrapper.v @@ -125,40 +125,70 @@ module mem_writer_wrapper ( wire [ 0:0] axi_stream_padded_tvalid; wire [ 0:0] axi_stream_padded_tready; - assign {axi_writer_write_req_address, axi_writer_write_req_length} = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_writer_req_data; - assign axi_writer_write_req_valid = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_writer_req_vld; - assign axi_writer_write_req_ready = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_writer_req_rdy; - - assign {padding_write_req_address, padding_write_req_length} = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__padding_req_data; - assign padding_write_req_valid = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__padding_req_vld; - assign padding_write_req_ready = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__padding_req_rdy; + assign {axi_writer_write_req_address, axi_writer_write_req_length} = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__axi_writer_req_data; + assign axi_writer_write_req_valid = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__axi_writer_req_vld; + assign axi_writer_write_req_ready = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__axi_writer_req_rdy; + + assign {padding_write_req_address, padding_write_req_length} = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__padding_req_data; + assign padding_write_req_valid = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__padding_req_vld; + assign padding_write_req_ready = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__padding_req_rdy; assign { axi_stream_raw_tdata, axi_stream_raw_tstr, axi_stream_raw_tkeep, axi_stream_raw_tid, axi_stream_raw_tdest, - axi_stream_raw_tlast} = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_st_raw_data; - assign axi_stream_raw_tvalid = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_st_raw_vld; - assign axi_stream_raw_tready = mem_writer.__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0.mem_writer__axi_st_raw_rdy; + axi_stream_raw_tlast} = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__axi_st_raw_data; + assign axi_stream_raw_tvalid = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__axi_st_raw_vld; + assign axi_stream_raw_tready = mem_writer + .__mem_writer__MemWriterInst__MemWriter_0__MemWriterInternal_0__16_32_4_4_4_2_next_inst0 + .mem_writer__axi_st_raw_rdy; assign { axi_stream_clean_tdata, axi_stream_clean_tstr, axi_stream_clean_tkeep, axi_stream_clean_tid, axi_stream_clean_tdest, - axi_stream_clean_tlast} = mem_writer.__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1.mem_writer__axi_st_clean_data; - assign axi_stream_clean_tvalid = mem_writer.__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1.mem_writer__axi_st_clean_vld; - assign axi_stream_clean_tready = mem_writer.__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1.mem_writer__axi_st_clean_rdy; + axi_stream_clean_tlast} = mem_writer + .__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1 // verilog_lint: waive line-length + .mem_writer__axi_st_clean_data; + assign axi_stream_clean_tvalid = mem_writer + .__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1 // verilog_lint: waive line-length + .mem_writer__axi_st_clean_vld; + assign axi_stream_clean_tready = mem_writer + .__xls_modules_zstd_memory_axi_stream_add_empty__MemWriterInst__MemWriter_0__AxiStreamAddEmpty_0__16_32_4_2_4_4_next_inst1 // verilog_lint: waive line-length + .mem_writer__axi_st_clean_rdy; assign { axi_stream_padded_tdata, axi_stream_padded_tstr, axi_stream_padded_tkeep, axi_stream_padded_tid, axi_stream_padded_tdest, - axi_stream_padded_tlast} = mem_writer.__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4.mem_writer__axi_st_padded_data; - assign axi_stream_padded_tvalid = mem_writer.__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4.mem_writer__axi_st_padded_vld; - assign axi_stream_padded_tready = mem_writer.__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4.mem_writer__axi_st_padded_rdy; + axi_stream_padded_tlast} = mem_writer + .__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4 // verilog_lint: waive line-length + .mem_writer__axi_st_padded_data; + assign axi_stream_padded_tvalid = mem_writer + .__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4 // verilog_lint: waive line-length + .mem_writer__axi_st_padded_vld; + assign axi_stream_padded_tready = mem_writer + .__xls_modules_zstd_memory_axi_writer__MemWriterInst__MemWriter_0__AxiWriter_0__16_32_4_4_4_2_next_inst4 // verilog_lint: waive line-length + .mem_writer__axi_st_padded_rdy; mem_writer mem_writer ( .clk(clk), diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index a5ca1daec6..e608e9b6f2 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright 2024 The XLS Authors # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,31 +13,23 @@ # limitations under the License. -from enum import Enum -from pathlib import Path +import enum +import pathlib import tempfile import cocotb +from cocotb import triggers from cocotb.clock import Clock -from cocotb.triggers import ClockCycles, Event -from cocotb.binary import BinaryValue -from cocotb_bus.scoreboard import Scoreboard +from cocotbext.axi import axi_channels from cocotbext.axi.axi_master import AxiMaster -from cocotbext.axi.axi_channels import AxiAWBus, AxiWBus, AxiBBus, AxiWriteBus, AxiARBus, AxiRBus, AxiReadBus, AxiBus, AxiBTransaction, AxiBSource, AxiBSink, AxiBMonitor, AxiRTransaction, AxiRSource, AxiRSink, AxiRMonitor -from cocotbext.axi.axi_ram import AxiRam from cocotbext.axi.sparse_memory import SparseMemory -from xls.common import runfiles -from xls.modules.zstd.cocotb.channel import ( - XLSChannel, - XLSChannelDriver, - XLSChannelMonitor, -) -from xls.modules.zstd.cocotb.data_generator import GenerateFrame, DecompressFrame, BlockType -from xls.modules.zstd.cocotb.memory import init_axi_mem, AxiRamFromFile -from xls.modules.zstd.cocotb.utils import reset, run_test -from xls.modules.zstd.cocotb.xlsstruct import XLSStruct, xls_dataclass +import xls.modules.zstd.cocotb.channel as xlschannel +from xls.modules.zstd.cocotb import data_generator +from xls.modules.zstd.cocotb.memory import AxiRamFromFile +from xls.modules.zstd.cocotb.utils import run_test +from xls.modules.zstd.cocotb import xlsstruct AXI_DATA_W = 64 AXI_DATA_W_BYTES = AXI_DATA_W // 8 @@ -48,34 +39,34 @@ # Override default widths of AXI response signals signal_widths = {"bresp": 3} -AxiBBus._signal_widths = signal_widths -AxiBTransaction._signal_widths = signal_widths -AxiBSource._signal_widths = signal_widths -AxiBSink._signal_widths = signal_widths -AxiBMonitor._signal_widths = signal_widths +axi_channels.AxiBBus._signal_widths = signal_widths +axi_channels.AxiBTransaction._signal_widths = signal_widths +axi_channels.AxiBSource._signal_widths = signal_widths +axi_channels.AxiBSink._signal_widths = signal_widths +axi_channels.AxiBMonitor._signal_widths = signal_widths signal_widths = {"rresp": 3, "rlast": 1} -AxiRBus._signal_widths = signal_widths -AxiRTransaction._signal_widths = signal_widths -AxiRSource._signal_widths = signal_widths -AxiRSink._signal_widths = signal_widths -AxiRMonitor._signal_widths = signal_widths - -@xls_dataclass -class NotifyStruct(XLSStruct): +axi_channels.AxiRBus._signal_widths = signal_widths +axi_channels.AxiRTransaction._signal_widths = signal_widths +axi_channels.AxiRSource._signal_widths = signal_widths +axi_channels.AxiRSink._signal_widths = signal_widths +axi_channels.AxiRMonitor._signal_widths = signal_widths + +@xlsstruct.xls_dataclass +class NotifyStruct(xlsstruct.XLSStruct): pass -class CSR(Enum): +class CSR(enum.Enum): """ - Maps the offsets to the ZSTD Decoder registers + Maps the offsets to the ZSTD Decoder registers. """ - Status = 0 - Start = 1 - InputBuffer = 2 - OutputBuffer = 3 + STATUS = 0 + START = 1 + INPUTBUFFER = 2 + OUTPUTBUFFER = 3 -class Status(Enum): +class Status(enum.Enum): """ - Codes for the Status register + Codes for the Status register. """ IDLE = 0x0 RUNNING = 0x1 @@ -87,39 +78,39 @@ def terminate_cb(_): monitor.add_callback(terminate_cb) def connect_axi_read_bus(dut, name=""): - AXI_AR = "axi_ar" - AXI_R = "axi_r" + axi_ar = "axi_ar" + axi_r = "axi_r" - if name != "": + if name: name += "_" - bus_axi_ar = AxiARBus.from_prefix(dut, name + AXI_AR) - bus_axi_r = AxiRBus.from_prefix(dut, name + AXI_R) + bus_axi_ar = axi_channels.AxiARBus.from_prefix(dut, name + axi_ar) + bus_axi_r = axi_channels.AxiRBus.from_prefix(dut, name + axi_r) - return AxiReadBus(bus_axi_ar, bus_axi_r) + return axi_channels.AxiReadBus(bus_axi_ar, bus_axi_r) def connect_axi_write_bus(dut, name=""): - AXI_AW = "axi_aw" - AXI_W = "axi_w" - AXI_B = "axi_b" + axi_aw = "axi_aw" + axi_w = "axi_w" + axi_b = "axi_b" - if name != "": + if name: name += "_" - bus_axi_aw = AxiAWBus.from_prefix(dut, name + AXI_AW) - bus_axi_w = AxiWBus.from_prefix(dut, name + AXI_W) - bus_axi_b = AxiBBus.from_prefix(dut, name + AXI_B) + bus_axi_aw = axi_channels.AxiAWBus.from_prefix(dut, name + axi_aw) + bus_axi_w = axi_channels.AxiWBus.from_prefix(dut, name + axi_w) + bus_axi_b = axi_channels.AxiBBus.from_prefix(dut, name + axi_b) - return AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) + return axi_channels.AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) def connect_axi_bus(dut, name=""): bus_axi_read = connect_axi_read_bus(dut, name) bus_axi_write = connect_axi_write_bus(dut, name) - return AxiBus(bus_axi_write, bus_axi_read) + return axi_channels.AxiBus(bus_axi_write, bus_axi_read) async def csr_write(cpu, csr, data): - if type(data) is int: + if isinstance(data, int): data = data.to_bytes(AXI_DATA_W_BYTES, byteorder='little') assert len(data) <= AXI_DATA_W_BYTES await cpu.write(csr.value * AXI_DATA_W_BYTES, data) @@ -138,7 +129,7 @@ async def test_csr(dut): cpu = AxiMaster(csr_bus, dut.clk, dut.rst) - await ClockCycles(dut.clk, 10) + await triggers.ClockCycles(dut.clk, 10) i = 0 for reg in CSR: expected_src = bytearray.fromhex("0DF0AD8BEFBEADDE") @@ -147,9 +138,11 @@ async def test_csr(dut): expected[0] += i await csr_write(cpu, reg, expected) read = await csr_read(cpu, reg) - assert read.data == expected, "Expected data doesn't match contents of the {}".format(reg) + assert read.data == expected, ( + "Expected data doesn't match contents of the {}".format(reg) + ) i += 1 - await ClockCycles(dut.clk, 10) + await triggers.ClockCycles(dut.clk, 10) async def test_reset(dut): clock = Clock(dut.clk, 10, units="us") @@ -160,47 +153,49 @@ async def test_reset(dut): csr_bus = connect_axi_bus(dut, "csr") cpu = AxiMaster(csr_bus, dut.clk, dut.rst) - await ClockCycles(dut.clk, 10) + await triggers.ClockCycles(dut.clk, 10) await start_decoder(cpu) timeout = 10 - status = await csr_read(cpu, CSR.Status) - while ((int.from_bytes(status.data, byteorder='little') == Status.IDLE.value) & (timeout != 0)): - status = await csr_read(cpu, CSR.Status) + status = await csr_read(cpu, CSR.STATUS) + while ((int.from_bytes(status.data, byteorder='little') == Status.IDLE.value) + & (timeout != 0)): + status = await csr_read(cpu, CSR.STATUS) timeout -= 1 assert (timeout != 0) await reset_dut(dut, 50) await wait_for_idle(cpu, 10) - await ClockCycles(dut.clk, 10) + await triggers.ClockCycles(dut.clk, 10) -async def configure_decoder(cpu, ibuf_addr, obuf_addr): - status = await csr_read(cpu, CSR.Status) +async def configure_decoder(dut, cpu, ibuf_addr, obuf_addr): + status = await csr_read(cpu, CSR.STATUS) if int.from_bytes(status.data, byteorder='little') != Status.IDLE.value: await reset_dut(dut, 50) - await csr_write(cpu, CSR.InputBuffer, ibuf_addr) - await csr_write(cpu, CSR.OutputBuffer, obuf_addr) + await csr_write(cpu, CSR.INPUTBUFFER, ibuf_addr) + await csr_write(cpu, CSR.OUTPUTBUFFER, obuf_addr) async def start_decoder(cpu): - await csr_write(cpu, CSR.Start, 0x1) + await csr_write(cpu, CSR.START, 0x1) async def wait_for_idle(cpu, timeout=100): - status = await csr_read(cpu, CSR.Status) - while ((int.from_bytes(status.data, byteorder='little') != Status.IDLE.value) & (timeout != 0)): - status = await csr_read(cpu, CSR.Status) + status = await csr_read(cpu, CSR.STATUS) + while ((int.from_bytes(status.data, byteorder='little') != Status.IDLE.value) + & (timeout != 0)): + status = await csr_read(cpu, CSR.STATUS) timeout -= 1 assert (timeout != 0) async def reset_dut(dut, rst_len=10): dut.rst.setimmediatevalue(0) - await ClockCycles(dut.clk, rst_len) + await triggers.ClockCycles(dut.clk, rst_len) dut.rst.setimmediatevalue(1) - await ClockCycles(dut.clk, rst_len) + await triggers.ClockCycles(dut.clk, rst_len) dut.rst.setimmediatevalue(0) def connect_xls_channel(dut, channel_name, xls_struct): - channel = XLSChannel(dut, channel_name, dut.clk, start_now=True) - monitor = XLSChannelMonitor(dut, channel_name, dut.clk, xls_struct) + channel = xlschannel.XLSChannel(dut, channel_name, dut.clk, start_now=True) + monitor = xlschannel.XLSChannelMonitor(dut, channel_name, dut.clk, xls_struct) return (channel, monitor) @@ -221,10 +216,11 @@ def prepare_test_environment(dut): async def test_decoder(dut, seed, block_type, axi_buses, cpu): memory_bus = axi_buses["memory"] - csr_bus = axi_buses["csr"] - (notify_channel, notify_monitor) = connect_xls_channel(dut, NOTIFY_CHANNEL, NotifyStruct) - assert_notify = Event() + (unused_notify_channel, notify_monitor) = connect_xls_channel( + dut, NOTIFY_CHANNEL, NotifyStruct + ) + assert_notify = triggers.Event() set_termination_event(notify_monitor, assert_notify, 1) mem_size = MAX_ENCODED_FRAME_SIZE_B @@ -236,31 +232,55 @@ async def test_decoder(dut, seed, block_type, axi_buses, cpu): await reset_dut(dut, 50) # Generate ZSTD frame to temporary file - GenerateFrame(seed, block_type, encoded.name) + data_generator.GenerateFrame(seed, block_type, encoded.name) - expected_decoded_frame = DecompressFrame(encoded.read()) + expected_decoded_frame = data_generator.DecompressFrame(encoded.read()) encoded.close() reference_memory = SparseMemory(mem_size) reference_memory.write(obuf_addr, expected_decoded_frame) # Initialise testbench memory with generated ZSTD frame - memory = AxiRamFromFile(bus=memory_bus, clock=dut.clk, reset=dut.rst, path=encoded.name, size=mem_size) - - await configure_decoder(cpu, ibuf_addr, obuf_addr) + memory = AxiRamFromFile( + bus=memory_bus, + clock=dut.clk, + reset=dut.rst, + path=encoded.name, + size=mem_size + ) + + await configure_decoder(dut, cpu, ibuf_addr, obuf_addr) await start_decoder(cpu) await assert_notify.wait() await wait_for_idle(cpu) # Read decoded frame in chunks of AXI_DATA_W length # Compare against frame decompressed with the reference library - for read_op in range(0, ((len(expected_decoded_frame) + (AXI_DATA_W_BYTES - 1)) // AXI_DATA_W_BYTES)): + chunks_nb = ( + len(expected_decoded_frame) + (AXI_DATA_W_BYTES - 1) + ) // AXI_DATA_W_BYTES + for read_op in range(0, chunks_nb): addr = obuf_addr + (read_op * AXI_DATA_W_BYTES) mem_contents = memory.read(addr, AXI_DATA_W_BYTES) exp_mem_contents = reference_memory.read(addr, AXI_DATA_W_BYTES) - assert mem_contents == exp_mem_contents, "{} bytes of memory contents at address {} don't match the expected contents:\n{}\nvs\n{}".format(AXI_DATA_W_BYTES, hex(addr), hex(int.from_bytes(mem_contents, byteorder='little')), hex(int.from_bytes(exp_mem_contents, byteorder='little'))) - - await ClockCycles(dut.clk, 20) - -async def testing_routine(dut, test_cases=1, block_type=BlockType.RANDOM): + assert mem_contents == exp_mem_contents, ( + ( + "{} bytes of memory contents at address {} " + "don't match the expected contents:\n" + "{}\nvs\n{}" + ).format( + AXI_DATA_W_BYTES, + hex(addr), + hex(int.from_bytes(mem_contents, byteorder='little')), + hex(int.from_bytes(exp_mem_contents, byteorder='little')) + ) + ) + + await triggers.ClockCycles(dut.clk, 20) + +async def testing_routine( + dut, + test_cases=1, + block_type=data_generator.BlockType.RANDOM +): (axi_buses, cpu) = prepare_test_environment(dut) for test_case in range(test_cases): await test_decoder(dut, test_case, block_type, axi_buses, cpu) @@ -277,13 +297,13 @@ async def zstd_reset_test(dut): @cocotb.test(timeout_time=500, timeout_unit="ms") async def zstd_raw_frames_test(dut): test_cases = 5 - block_type = BlockType.RAW + block_type = data_generator.BlockType.RAW await testing_routine(dut, test_cases, block_type) @cocotb.test(timeout_time=500, timeout_unit="ms") async def zstd_rle_frames_test(dut): test_cases = 5 - block_type = BlockType.RLE + block_type = data_generator.BlockType.RLE await testing_routine(dut, test_cases, block_type) #@cocotb.test(timeout_time=1000, timeout_unit="ms") @@ -314,5 +334,5 @@ async def zstd_rle_frames_test(dut): "xls/modules/zstd/external/arbiter.v", "xls/modules/zstd/external/priority_encoder.v", ] - test_module=[Path(__file__).stem] + test_module=[pathlib.Path(__file__).stem] run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/zstd_frame_dslx.py b/xls/modules/zstd/zstd_frame_dslx.py index 65c5573677..c5ad2e177b 100644 --- a/xls/modules/zstd/zstd_frame_dslx.py +++ b/xls/modules/zstd/zstd_frame_dslx.py @@ -12,28 +12,47 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""ZSTD test frame generator for DSLX tests. + +This module interacts with the data_generator module and underlying +Decodecorpus in order to generate inputs and expected outputs for the ZSTD +Decoder tests in DSLX. + +It generates the ZSTD frame and then decodes it with the reference ZSTD +library. Both the encoded frame and decoded data are written to a DSLX file by +converting raw bytes of the frame and decoded data into DSLX structures. + +Resulting file can be included in the ZSTD Decoder DSLX test and used as the +inputs and expected output for the testbench. +""" import argparse import math import random import tempfile -from pathlib import Path +import pathlib -from xls.modules.zstd.cocotb.data_generator import ( - BlockType, - DecompressFrame, - GenerateFrame, -) +from xls.modules.zstd.cocotb import data_generator def GenerateTestData(seed, btype): with tempfile.NamedTemporaryFile() as tmp: - GenerateFrame(seed, btype, tmp.name) + data_generator.GenerateFrame(seed, btype, tmp.name) tmp.seek(0) return tmp.read() def Bytes2DSLX(frames, bytes_per_word, array_name): + """Converts a list of byte frames to a DSLX constant array declaration. + + Args: + frames (List[bytes]): List of byte sequences representing frames. + bytes_per_word (int): Number of bytes per word in the output format. + array_name (str): Name of the resulting DSLX constant array. + + Returns: + str: A string containing the DSLX constant array declaration. + """ frames_hex = [] maxlen = max(len(frame) for frame in frames) maxlen_size = math.ceil(maxlen / bytes_per_word) @@ -70,49 +89,13 @@ def Bytes2DSLX(frames, bytes_per_word, array_name): def GenerateDataStruct(): return ( - f"pub struct DataArray{{\n" - f" data: uN[BITS_PER_WORD][LENGTH],\n" - f" length: u32,\n" - f" array_length: u32\n" - f"}}\n" + "pub struct DataArray{\n" + " data: uN[BITS_PER_WORD][LENGTH],\n" + " length: u32,\n" + " array_length: u32\n" + "}\n" ) -def main2(): - parser = argparse.ArgumentParser() - parser.add_argument( - "input", - help="Filename of the decodecorpus input", - type=Path, - ) - parser.add_argument( - "output", - help="Filename of the DSLX output file", - type=Path, - ) - parser.add_argument( - "--bytes-per-word", - help="Width of a word in memory, in bytes", - type=int, - default=8, - ) - - args = parser.parse_args() - - with open(args.input, "rb") as fd: - byte_frames = [fd.read()] - - with open(args.output, "w") as dslx_output: - dslx_output.write(GenerateDataStruct()) - - dslx_frames = Bytes2DSLX(byte_frames, args.bytes_per_word, "FRAMES") - dslx_output.write(dslx_frames) - - byte_frames_decompressed = list(map(DecompressFrame, byte_frames)) - dslx_frames_decompressed = Bytes2DSLX( - byte_frames_decompressed, args.bytes_per_word, "DECOMPRESSED_FRAMES" - ) - dslx_output.write(dslx_frames_decompressed) - def main(): parser = argparse.ArgumentParser() @@ -128,17 +111,17 @@ def main(): "Block types allowed in the generated testcases. If multiple block types " "are supplied, generated testcases will cycle through them" ), - type=BlockType.from_string, - choices=list(BlockType), - default=BlockType.RANDOM, + type=data_generator.BlockType.from_string, + choices=list(data_generator.BlockType), + default=data_generator.BlockType.RANDOM, nargs="+", ) parser.add_argument( "-o", "--output", help="Filename of the DSLX output file", - type=Path, - default=Path("frames_test_data.x"), + type=pathlib.Path, + default=pathlib.Path("frames_test_data.x"), ) parser.add_argument( "--bytes-per-word", @@ -148,7 +131,7 @@ def main(): ) args = parser.parse_args() - seed = random.seed(args.seed) + random.seed(args.seed) byte_frames = [ GenerateTestData(random.randrange(2**32), args.btype[i % len(args.btype)]) for i in range(args.n) @@ -159,7 +142,9 @@ def main(): dslx_frames = Bytes2DSLX(byte_frames, args.bytes_per_word, "FRAMES") dslx_output.write(dslx_frames) - byte_frames_decompressed = list(map(DecompressFrame, byte_frames)) + byte_frames_decompressed = list( + map(data_generator.DecompressFrame, byte_frames) + ) dslx_frames_decompressed = Bytes2DSLX( byte_frames_decompressed, args.bytes_per_word, "DECOMPRESSED_FRAMES" ) From 1cfd1448494d0516f62a8f0af67d25104bbb36e5 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 23 Jul 2025 14:33:55 +0200 Subject: [PATCH 017/159] Extend cocotb tests of ZSTD decoder Co-authored-by: Pawel Czarnecki Co-authored-by: Krzysztof Oblonczek Co-authored-by: Wojciech Sipak Co-authored-by: Dominik Lau Co-authored-by: Szymon Gizler Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 183 +- xls/modules/zstd/cocotb/data_generator.py | 24 +- xls/modules/zstd/cocotb/utils.py | 14 +- xls/modules/zstd/data/comp_frame.zst | Bin 0 -> 51 bytes xls/modules/zstd/data/comp_frame_fse_comp.zst | Bin 0 -> 66 bytes .../zstd/data/comp_frame_fse_repeated.zst | Bin 0 -> 92 bytes xls/modules/zstd/data/comp_frame_huffman.zst | Bin 0 -> 93 bytes .../zstd/data/comp_frame_huffman_fse.zst | Bin 0 -> 64 bytes ...erals_predefined_sequences_seed_107958.log | 30 + ...erals_predefined_sequences_seed_107958.zst | Bin 0 -> 61 bytes ...erals_predefined_sequences_seed_204626.log | 25 + ...erals_predefined_sequences_seed_204626.zst | Bin 0 -> 64 bytes ...erals_predefined_sequences_seed_210872.log | 28 + ...erals_predefined_sequences_seed_210872.zst | Bin 0 -> 80 bytes ...erals_predefined_sequences_seed_299289.log | 39 + ...erals_predefined_sequences_seed_299289.zst | Bin 0 -> 60 bytes ...erals_predefined_sequences_seed_319146.log | 26 + ...erals_predefined_sequences_seed_319146.zst | Bin 0 -> 125 bytes ...erals_predefined_sequences_seed_331938.log | 25 + ...erals_predefined_sequences_seed_331938.zst | Bin 0 -> 93 bytes ...erals_predefined_sequences_seed_333824.log | 27 + ...erals_predefined_sequences_seed_333824.zst | Bin 0 -> 56 bytes .../data/pregenerated_compressed_minimal.zst | Bin 0 -> 22 bytes .../data/pregenerated_compressed_random_1.log | 189 ++ .../data/pregenerated_compressed_random_1.zst | Bin 0 -> 505 bytes .../data/pregenerated_compressed_random_2.log | 70 + .../data/pregenerated_compressed_random_2.zst | Bin 0 -> 120 bytes .../data/pregenerated_compressed_raw_1.log | 347 +++ .../data/pregenerated_compressed_raw_1.zst | Bin 0 -> 975 bytes .../data/pregenerated_compressed_raw_2.log | 100 + .../data/pregenerated_compressed_raw_2.zst | Bin 0 -> 193 bytes .../data/pregenerated_compressed_rle_1.log | 179 ++ .../data/pregenerated_compressed_rle_1.zst | Bin 0 -> 281 bytes .../data/pregenerated_compressed_rle_2.log | 103 + .../data/pregenerated_compressed_rle_2.zst | Bin 0 -> 61 bytes .../zstd/data/pregenerated_uncompressed.zst | Bin 0 -> 18 bytes ...erals_compressed_sequences_seed_903062.log | 23 + ...erals_compressed_sequences_seed_903062.zst | Bin 0 -> 37 bytes ...erals_predefined_sequences_seed_422473.log | 25 + ...erals_predefined_sequences_seed_422473.zst | Bin 0 -> 40 bytes ...erals_predefined_sequences_seed_436965.log | 23 + ...erals_predefined_sequences_seed_436965.zst | Bin 0 -> 36 bytes ...erals_predefined_sequences_seed_462302.log | 21 + ...erals_predefined_sequences_seed_462302.zst | Bin 0 -> 31 bytes ...raw_literals_rle_sequences_seed_700216.log | 20 + ...raw_literals_rle_sequences_seed_700216.zst | Bin 0 -> 46 bytes ...erals_compressed_sequences_seed_701326.log | 87 + ...erals_compressed_sequences_seed_701326.zst | Bin 0 -> 75 bytes ...erals_predefined_sequences_seed_406229.log | 37 + ...erals_predefined_sequences_seed_406229.zst | Bin 0 -> 42 bytes ...erals_predefined_sequences_seed_411034.log | 28 + ...erals_predefined_sequences_seed_411034.zst | Bin 0 -> 35 bytes ...erals_predefined_sequences_seed_413015.log | 53 + ...erals_predefined_sequences_seed_413015.zst | Bin 0 -> 64 bytes ...erals_predefined_sequences_seed_436165.log | 34 + ...erals_predefined_sequences_seed_436165.zst | Bin 0 -> 38 bytes ...erals_predefined_sequences_seed_464057.log | 21 + ...erals_predefined_sequences_seed_464057.zst | Bin 0 -> 27 bytes ...erals_predefined_sequences_seed_466803.log | 24 + ...erals_predefined_sequences_seed_466803.zst | Bin 0 -> 32 bytes .../rle_literals_rle_sequences_seed_2.log | 43 + .../rle_literals_rle_sequences_seed_2.zst | Bin 0 -> 121 bytes ...erals_predefined_sequences_seed_408158.log | 111 + ...erals_predefined_sequences_seed_408158.zst | Bin 0 -> 93 bytes ...erals_predefined_sequences_seed_499212.log | 44 + ...erals_predefined_sequences_seed_499212.zst | Bin 0 -> 60 bytes ...erals_compressed_sequences_seed_400077.log | 80 + ...erals_compressed_sequences_seed_400077.zst | Bin 0 -> 142 bytes ...d_rle_compressed_sequences_seed_400025.log | 46 + ...d_rle_compressed_sequences_seed_400025.zst | Bin 0 -> 140 bytes ...d_rle_compressed_sequences_seed_400061.log | 81 + ...d_rle_compressed_sequences_seed_400061.zst | Bin 0 -> 244 bytes ...man_literals_rle_sequences_seed_403927.log | 53 + ...man_literals_rle_sequences_seed_403927.zst | Bin 0 -> 137 bytes .../zstd/external/axi_crossbar_wrapper.v | 788 +++++- xls/modules/zstd/rtl/BUILD | 1 + xls/modules/zstd/rtl/ram_1r1w.v | 88 + xls/modules/zstd/rtl/zstd_dec_wrapper.sv | 2393 ++++++++++++++++- xls/modules/zstd/zstd_dec_cocotb_test.py | 1572 ++++++++++- xls/modules/zstd/zstd_dec_ll_fse_default.mem | 256 ++ xls/modules/zstd/zstd_dec_ml_fse_default.mem | 256 ++ xls/modules/zstd/zstd_dec_of_fse_default.mem | 256 ++ 82 files changed, 7626 insertions(+), 247 deletions(-) create mode 100644 xls/modules/zstd/data/comp_frame.zst create mode 100644 xls/modules/zstd/data/comp_frame_fse_comp.zst create mode 100644 xls/modules/zstd/data/comp_frame_fse_repeated.zst create mode 100644 xls/modules/zstd/data/comp_frame_huffman.zst create mode 100644 xls/modules/zstd/data/comp_frame_huffman_fse.zst create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.log create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.zst create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.log create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.zst create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_210872.log create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_210872.zst create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.log create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.zst create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.log create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.zst create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.log create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.zst create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.log create mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.zst create mode 100644 xls/modules/zstd/data/pregenerated_compressed_minimal.zst create mode 100644 xls/modules/zstd/data/pregenerated_compressed_random_1.log create mode 100644 xls/modules/zstd/data/pregenerated_compressed_random_1.zst create mode 100644 xls/modules/zstd/data/pregenerated_compressed_random_2.log create mode 100644 xls/modules/zstd/data/pregenerated_compressed_random_2.zst create mode 100644 xls/modules/zstd/data/pregenerated_compressed_raw_1.log create mode 100644 xls/modules/zstd/data/pregenerated_compressed_raw_1.zst create mode 100644 xls/modules/zstd/data/pregenerated_compressed_raw_2.log create mode 100644 xls/modules/zstd/data/pregenerated_compressed_raw_2.zst create mode 100644 xls/modules/zstd/data/pregenerated_compressed_rle_1.log create mode 100644 xls/modules/zstd/data/pregenerated_compressed_rle_1.zst create mode 100644 xls/modules/zstd/data/pregenerated_compressed_rle_2.log create mode 100644 xls/modules/zstd/data/pregenerated_compressed_rle_2.zst create mode 100644 xls/modules/zstd/data/pregenerated_uncompressed.zst create mode 100644 xls/modules/zstd/data/raw_literals_compressed_sequences_seed_903062.log create mode 100644 xls/modules/zstd/data/raw_literals_compressed_sequences_seed_903062.zst create mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.log create mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.zst create mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_436965.log create mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_436965.zst create mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.log create mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.zst create mode 100644 xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.log create mode 100644 xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.zst create mode 100644 xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.log create mode 100644 xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.zst create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.log create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.zst create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.log create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.zst create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.log create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.zst create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.log create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.zst create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_464057.log create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_464057.zst create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_466803.log create mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_466803.zst create mode 100644 xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.log create mode 100644 xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.zst create mode 100644 xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.log create mode 100644 xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.zst create mode 100644 xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.log create mode 100644 xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.zst create mode 100644 xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.log create mode 100644 xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.zst create mode 100644 xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.log create mode 100644 xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst create mode 100644 xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.log create mode 100644 xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst create mode 100644 xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.log create mode 100644 xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.zst create mode 100644 xls/modules/zstd/rtl/ram_1r1w.v create mode 100644 xls/modules/zstd/zstd_dec_ll_fse_default.mem create mode 100644 xls/modules/zstd/zstd_dec_ml_fse_default.mem create mode 100644 xls/modules/zstd/zstd_dec_of_fse_default.mem diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f96abb8528..701d34f5f2 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1160,7 +1160,7 @@ xls_dslx_verilog( "delay_model": "asap7", # FIXME: update ram rewrite #"ram_configurations": "ram:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - # latency = 5, + # latency = 1, # rd_req = "fse_proba_freq_dec__rd_req_s", # rd_resp = "fse_proba_freq_dec__rd_resp_r", # wr_req = "fse_proba_freq_dec__wr_req_s", @@ -1186,7 +1186,7 @@ xls_benchmark_ir( "reset": "rst", # FIXME: update ram rewrite #"ram_configurations": "ram:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - # latency = 5, + # latency = 1, # rd_req = "fse_proba_freq_dec__rd_req_s", # rd_resp = "fse_proba_freq_dec__rd_resp_r", # wr_req = "fse_proba_freq_dec__wr_req_s", @@ -1489,14 +1489,14 @@ ZSTD_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { "module_name": "ZstdDecoder", "clock_period_ps": "0", "pipeline_stages": "16", - "flop_inputs_kind": "skid", - "flop_outputs_kind": "skid", "worst_case_throughput": "6", + # "flop_inputs_kind": "skid", + # "flop_outputs_kind": "skid", "materialize_internal_fifos": "false", # TODO: remove once this option works with loopback channels "ram_configurations": ",".join([ ",".join([ "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - latency = 5, + latency = 1, ram_name = "history_buffer_ram{}".format(num), rd_req = "zstd_dec__ram_rd_req_{}_s".format(num), rd_resp = "zstd_dec__ram_rd_resp_{}_r".format(num), @@ -1506,7 +1506,7 @@ ZSTD_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { for num in range(8) ]), "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - latency = 5, + latency = 1, ram_name = "dpd_ram", rd_req = "zstd_dec__dpd_rd_req_s", rd_resp = "zstd_dec__dpd_rd_resp_r", @@ -1514,7 +1514,7 @@ ZSTD_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { wr_resp = "zstd_dec__dpd_wr_resp_r", ), "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - latency = 5, + latency = 1, ram_name = "fse_tmp_ram", rd_req = "zstd_dec__tmp_rd_req_s", rd_resp = "zstd_dec__tmp_rd_resp_r", @@ -1522,44 +1522,112 @@ ZSTD_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { wr_resp = "zstd_dec__tmp_wr_resp_r", ), "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - latency = 5, + latency = 1, ram_name = "fse_tmp2_ram", rd_req = "zstd_dec__tmp2_rd_req_s", rd_resp = "zstd_dec__tmp2_rd_resp_r", wr_req = "zstd_dec__tmp2_wr_req_s", wr_resp = "zstd_dec__tmp2_wr_resp_r", ), - ",".join([ - "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - latency = 5, - ram_name = "fse_lookup_ram{}".format(num), - rd_req = "zstd_dec__fse_rd_req_s__{}".format(num), - rd_resp = "zstd_dec__fse_rd_resp_r__{}".format(num), - wr_req = "zstd_dec__fse_wr_req_s__{}".format(num), - wr_resp = "zstd_dec__fse_wr_resp_r__{}".format(num), - ) - for num in range(6) - ]), "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - latency = 5, + latency = 1, + ram_name = "ll_def_fse_ram", + rd_req = "zstd_dec__ll_def_fse_rd_req_s", + rd_resp = "zstd_dec__ll_def_fse_rd_resp_r", + wr_req = "zstd_dec__ll_def_fse_wr_req_s", + wr_resp = "zstd_dec__ll_def_fse_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "ll_fse_ram", + rd_req = "zstd_dec__ll_fse_rd_req_s", + rd_resp = "zstd_dec__ll_fse_rd_resp_r", + wr_req = "zstd_dec__ll_fse_wr_req_s", + wr_resp = "zstd_dec__ll_fse_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "ml_def_fse_ram", + rd_req = "zstd_dec__ml_def_fse_rd_req_s", + rd_resp = "zstd_dec__ml_def_fse_rd_resp_r", + wr_req = "zstd_dec__ml_def_fse_wr_req_s", + wr_resp = "zstd_dec__ml_def_fse_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "ml_fse_ram", + rd_req = "zstd_dec__ml_fse_rd_req_s", + rd_resp = "zstd_dec__ml_fse_rd_resp_r", + wr_req = "zstd_dec__ml_fse_wr_req_s", + wr_resp = "zstd_dec__ml_fse_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "of_def_fse_ram", + rd_req = "zstd_dec__of_def_fse_rd_req_s", + rd_resp = "zstd_dec__of_def_fse_rd_resp_r", + wr_req = "zstd_dec__of_def_fse_wr_req_s", + wr_resp = "zstd_dec__of_def_fse_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "of_fse_ram", + rd_req = "zstd_dec__of_fse_rd_req_s", + rd_resp = "zstd_dec__of_fse_rd_resp_r", + wr_req = "zstd_dec__of_fse_wr_req_s", + wr_resp = "zstd_dec__of_fse_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, ram_name = "huffman_literals_prescan_ram", rd_req = "zstd_dec__huffman_lit_prescan_mem_rd_req_s", rd_resp = "zstd_dec__huffman_lit_prescan_mem_rd_resp_r", wr_req = "zstd_dec__huffman_lit_prescan_mem_wr_req_s", wr_resp = "zstd_dec__huffman_lit_prescan_mem_wr_resp_r", ), - # FIXME: Enable once HuffmanLiteralsDecoder has states independent of data read from RAM - #"{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - # latency = 5, - # ram_name = "huffman_literals_weights_ram", - # rd_req = "zstd_dec__huffman_lit_weights_mem_rd_req_s", - # rd_resp = "zstd_dec__huffman_lit_weights_mem_rd_resp_r", - # wr_req = "zstd_dec__huffman_lit_weights_mem_wr_req_s", - # wr_resp = "zstd_dec__huffman_lit_weights_mem_wr_resp_r", - #), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "huffman_literals_weights_dpd_ram", + rd_req = "zstd_dec__huffman_lit_weights_dpd_rd_req_s", + rd_resp = "zstd_dec__huffman_lit_weights_dpd_rd_resp_r", + wr_req = "zstd_dec__huffman_lit_weights_dpd_wr_req_s", + wr_resp = "zstd_dec__huffman_lit_weights_dpd_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "huffman_literals_weights_tmp_ram", + rd_req = "zstd_dec__huffman_lit_weights_tmp_rd_req_s", + rd_resp = "zstd_dec__huffman_lit_weights_tmp_rd_resp_r", + wr_req = "zstd_dec__huffman_lit_weights_tmp_wr_req_s", + wr_resp = "zstd_dec__huffman_lit_weights_tmp_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "huffman_literals_weights_tmp2_ram", + rd_req = "zstd_dec__huffman_lit_weights_tmp2_rd_req_s", + rd_resp = "zstd_dec__huffman_lit_weights_tmp2_rd_resp_r", + wr_req = "zstd_dec__huffman_lit_weights_tmp2_wr_req_s", + wr_resp = "zstd_dec__huffman_lit_weights_tmp2_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "huffman_literals_weights_fse_ram", + rd_req = "zstd_dec__huffman_lit_weights_fse_rd_req_s", + rd_resp = "zstd_dec__huffman_lit_weights_fse_rd_resp_r", + wr_req = "zstd_dec__huffman_lit_weights_fse_wr_req_s", + wr_resp = "zstd_dec__huffman_lit_weights_fse_wr_resp_r", + ), + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 1, + ram_name = "huffman_literals_weights_mem_ram", + rd_req = "zstd_dec__huffman_lit_weights_mem_rd_req_s", + rd_resp = "zstd_dec__huffman_lit_weights_mem_rd_resp_r", + wr_req = "zstd_dec__huffman_lit_weights_mem_wr_req_s", + wr_resp = "zstd_dec__huffman_lit_weights_mem_wr_resp_r", + ), ",".join([ "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - latency = 5, + latency = 1, ram_name = "literals_buffer_ram{}".format(num), rd_req = "zstd_dec__litbuf_rd_req_s__{}".format(num), rd_resp = "zstd_dec__litbuf_rd_resp_r__{}".format(num), @@ -1675,11 +1743,60 @@ benchmark_synth( # target_die_utilization_percentage = "1", #) +filegroup( + name = "zstd_dec_xx_fse_default", + srcs = [ + "zstd_dec_ll_fse_default.mem", + "zstd_dec_ml_fse_default.mem", + "zstd_dec_of_fse_default.mem", + ], +) + py_test( name = "zstd_dec_cocotb_test", srcs = ["zstd_dec_cocotb_test.py"], data = [ + "data/comp_frame.zst", + "data/comp_frame_fse_comp.zst", + "data/comp_frame_fse_repeated.zst", + "data/comp_frame_huffman.zst", + "data/comp_frame_huffman_fse.zst", + "data/fse_huffman_literals_predefined_sequences_seed_107958.zst", + "data/fse_huffman_literals_predefined_sequences_seed_204626.zst", + "data/fse_huffman_literals_predefined_sequences_seed_210872.zst", + "data/fse_huffman_literals_predefined_sequences_seed_299289.zst", + "data/fse_huffman_literals_predefined_sequences_seed_319146.zst", + "data/fse_huffman_literals_predefined_sequences_seed_331938.zst", + "data/fse_huffman_literals_predefined_sequences_seed_333824.zst", + "data/pregenerated_compressed_minimal.zst", + "data/pregenerated_compressed_random_1.zst", + "data/pregenerated_compressed_random_2.zst", + "data/pregenerated_compressed_raw_1.zst", + "data/pregenerated_compressed_raw_2.zst", + "data/pregenerated_compressed_rle_1.zst", + "data/pregenerated_compressed_rle_2.zst", + "data/pregenerated_uncompressed.zst", + "data/raw_literals_compressed_sequences_seed_903062.zst", + "data/raw_literals_predefined_sequences_seed_422473.zst", + "data/raw_literals_predefined_sequences_seed_436965.zst", + "data/raw_literals_predefined_sequences_seed_462302.zst", + "data/raw_literals_rle_sequences_seed_700216.zst", + "data/rle_literals_compressed_sequences_seed_701326.zst", + "data/rle_literals_predefined_sequences_seed_406229.zst", + "data/rle_literals_predefined_sequences_seed_411034.zst", + "data/rle_literals_predefined_sequences_seed_413015.zst", + "data/rle_literals_predefined_sequences_seed_436165.zst", + "data/rle_literals_predefined_sequences_seed_464057.zst", + "data/rle_literals_predefined_sequences_seed_466803.zst", + "data/rle_literals_rle_sequences_seed_2.zst", + "data/rle_raw_literals_predefined_sequences_seed_408158.zst", + "data/rle_raw_literals_predefined_sequences_seed_499212.zst", + "data/treeless_huffman_literals_compressed_sequences_seed_400077.zst", + "data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst", + "data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst", + "data/treeless_huffman_literals_rle_sequences_seed_403927.zst", ":zstd_dec.v", + "//xls/modules/zstd:zstd_dec_xx_fse_default", "//xls/modules/zstd/external:arbiter.v", "//xls/modules/zstd/external:axi_crossbar.v", "//xls/modules/zstd/external:axi_crossbar_addr.v", @@ -1689,12 +1806,16 @@ py_test( "//xls/modules/zstd/external:axi_register_rd.v", "//xls/modules/zstd/external:axi_register_wr.v", "//xls/modules/zstd/external:priority_encoder.v", + "//xls/modules/zstd/rtl:ram_1r1w.v", "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", "//xls/modules/zstd/rtl:zstd_dec_wrapper.sv", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], - env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, + env = { + "BUILD_WORKING_DIRECTORY": "sim_build", + "PYTHONUNBUFFERED": "1", + }, imports = ["."], tags = ["manual"], visibility = ["//xls:xls_users"], diff --git a/xls/modules/zstd/cocotb/data_generator.py b/xls/modules/zstd/cocotb/data_generator.py index 46a74acbfc..3c9233f6d6 100644 --- a/xls/modules/zstd/cocotb/data_generator.py +++ b/xls/modules/zstd/cocotb/data_generator.py @@ -39,6 +39,24 @@ def from_string(s): except KeyError as e: raise ValueError(str(e)) from e +class LiteralType(enum.Enum): + """Enum encoding of ZSTD literal types.""" + + RAW = 0 + RLE = 1 + COMPRESSED = 2 + RANDOM = 3 + + def __str__(self): + return self.name + + @staticmethod + def from_string(s): + try: + return BlockType[s] + except KeyError as e: + raise ValueError(str(e)) from e + def CallDecodecorpus(args): decodecorpus = pathlib.Path( runfiles.get_path("decodecorpus", repository = "zstd") @@ -52,11 +70,15 @@ def DecompressFrame(data): dctx = zstandard.ZstdDecompressor() return dctx.decompress(data) -def GenerateFrame(seed, btype, output_path): +def GenerateFrame(seed, btype, output_path, ltype=LiteralType.RANDOM): args = [] args.append("-s" + str(seed)) if (btype != BlockType.RANDOM): args.append("--block-type=" + str(btype.value)) + + if (ltype != LiteralType.RANDOM): + args.append("--literal-type=" + str(ltype.value)) + args.append("--content-size") # Test payloads up to 16KB args.append("--max-content-size-log=14") diff --git a/xls/modules/zstd/cocotb/utils.py b/xls/modules/zstd/cocotb/utils.py index 7fda01b0b9..01688f96a3 100644 --- a/xls/modules/zstd/cocotb/utils.py +++ b/xls/modules/zstd/cocotb/utils.py @@ -50,12 +50,14 @@ def run_test(toplevel, test_module, verilog_sources): waves=True, ) - results_xml = runner.test( - hdl_toplevel=toplevel, - test_module=test_module, - waves=True, - ) - check_results_file(results_xml) + try: + results_xml = runner.test( + hdl_toplevel=toplevel, + test_module=test_module, + waves=True, + ) + finally: + check_results_file(results_xml) @cocotb.coroutine async def reset(clk, rst, cycles=1): diff --git a/xls/modules/zstd/data/comp_frame.zst b/xls/modules/zstd/data/comp_frame.zst new file mode 100644 index 0000000000000000000000000000000000000000..800a7bdca91d4fbddaf33411a2402d7ef59ec7df GIT binary patch literal 51 zcmV-30L=d=wJ-gIM^FF&02KiMaFx%*ldOrkP<`PD=BAYh0Iv5Ma6JW`-kC J;IIYbaD*4377qXb literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/comp_frame_fse_comp.zst b/xls/modules/zstd/data/comp_frame_fse_comp.zst new file mode 100644 index 0000000000000000000000000000000000000000..f9cf4a5d1fee7bfb26a6a87f5549c28e44cf8b21 GIT binary patch literal 66 zcmV-I0KNYxwJ-gIP*eZ_0F40v&^AI31@t#{HJBY6dDqsiwUUi=l>sT&j#E(ss1b(y Y0TFOn(Fbvb|Idp3V+xM51cp>m#Z)F6f&c&j literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/comp_frame_fse_repeated.zst b/xls/modules/zstd/data/comp_frame_fse_repeated.zst new file mode 100644 index 0000000000000000000000000000000000000000..8fed4b3fbe63c83cc93ddfe47fbfd9d14d5d3797 GIT binary patch literal 92 zcmdPcs{gk|#g2i2p@o5=@)}bHqaa(oVB7!yb$5eUTo@P(QyELx7`L&=Ffb@?W&o0m d&%q=wnA{5{t-xeGn9OF71**wp*f!I4CIA*T6FC3? literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/comp_frame_huffman.zst b/xls/modules/zstd/data/comp_frame_huffman.zst new file mode 100644 index 0000000000000000000000000000000000000000..7f21a9f870ae0fefcb79022ea0c563252319b462 GIT binary patch literal 93 zcmdPcs{gmerGSBfA(e^2ig^t~pCAMXurq+5Pdi(f6|0nRi)qZ39FgslOBchq5d8nX-tPbV^Y-um?_FR1KlV2x TGsA`n4`mg3{5V!L*EIkD+e#Sp literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.log new file mode 100644 index 0000000000..74008be9c2 --- /dev/null +++ b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.log @@ -0,0 +1,30 @@ +seed: 107958 +frame seed: 107958 + frame content size: 100 + frame window size: 1536 + content size flag: 1 + single segment flag: 0 + block: + block content size: 100 + last block: yes + compressed block: + compressed literals + distribution weight: 81% + huffman log: 10 + regenerated size: 45 + compressed size: 22 + literals size: 45 + total match lengths: 55 + LL: 13 OF: 9 ML: 4 srcPos: 17 seqNb: 0 + LL: 12 OF: 6 ML: 9 srcPos: 38 seqNb: 1 + LL: 3 OF: 36 ML: 25 srcPos: 66 seqNb: 2 + LL: 0 OF: 42 ML: 5 srcPos: 71 seqNb: 3 + LL: 14 OF: 2 ML: 5 srcPos: 90 seqNb: 4 + LL: 0 OF: 27 ML: 3 srcPos: 93 seqNb: 5 + LL: 1 OF: 67 ML: 4 srcPos: 98 seqNb: 6 + excess literals: 2 srcPos: 100 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 7 + block type: compressed + block size field: 44 + checksum: 61c6e5a0 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.zst new file mode 100644 index 0000000000000000000000000000000000000000..b44e0191ea5b9139965130057d3b415d874e51b2 GIT binary patch literal 61 zcmV-D0K)$$wJ-gI1Y`gJ0A&FH(t-sJ&}BFPumXTV3h1JO#smBR-+%YrsRsZ?2*f7< TxznavaMT(pl`tq@pykG4*5etX literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.log new file mode 100644 index 0000000000..6a11616659 --- /dev/null +++ b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.log @@ -0,0 +1,25 @@ +seed: 204626 +frame seed: 204626 + frame content size: 89 + frame window size: 1152 + content size flag: 1 + single segment flag: 0 + block: + block content size: 89 + last block: yes + compressed block: + compressed literals + distribution weight: 64% + huffman log: 9 + regenerated size: 66 + compressed size: 36 + literals size: 66 + total match lengths: 23 + LL: 42 OF: 16 ML: 9 srcPos: 51 seqNb: 0 + LL: 24 OF: 6 ML: 14 srcPos: 89 seqNb: 1 + excess literals: 0 srcPos: 89 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 2 + block type: compressed + block size field: 47 + checksum: 9834e3da diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.zst new file mode 100644 index 0000000000000000000000000000000000000000..cc5812eb6ae7bf59dca1efd3eb6e4388601a463f GIT binary patch literal 64 zcmV-G0KfkzwJ-gI0a*Y50DS=fA_NH)pe2QL|0Dm?|Fiz?`Z9`A?>> jFa1!@^wT_$t=97~6Dva(rwE^po`i(~3m>;o{=|F$trHuh literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.log new file mode 100644 index 0000000000..f2f01bd94a --- /dev/null +++ b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.log @@ -0,0 +1,39 @@ +seed: 299289 +frame seed: 299289 + frame content size: 68 + frame window size: 294912 + content size flag: 1 + single segment flag: 0 + block: + block content size: 68 + last block: yes + compressed block: + compressed literals + distribution weight: 24% + huffman log: 11 + regenerated size: 60 + compressed size: 74 + trying again + distribution weight: 44% + huffman log: 9 + regenerated size: 41 + compressed size: 45 + trying again + distribution weight: 86% + huffman log: 11 + regenerated size: 46 + compressed size: 26 + literals size: 46 + total match lengths: 22 + LL: 7 OF: 6 ML: 5 srcPos: 12 seqNb: 0 + LL: 5 OF: 8 ML: 4 srcPos: 21 seqNb: 1 + LL: 18 OF: 1 ML: 6 srcPos: 45 seqNb: 2 + repeat offset: 2 + LL: 0 OF: 16 ML: 4 srcPos: 49 seqNb: 3 + LL: 16 OF: 29 ML: 3 srcPos: 68 seqNb: 4 + excess literals: 0 srcPos: 68 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 5 + block type: compressed + block size field: 43 + checksum: 28ab4e28 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.zst new file mode 100644 index 0000000000000000000000000000000000000000..c19b07faf2a3950c60c7f74bab83753da37a2206 GIT binary patch literal 60 zcmdPcs{gme(S?D5A(oNhSrZ%ggG`=P{>(uB(HtfQ5PWa{b^rfc-}zY?8k*)xHk2Ro N+7pw?t>L#?0{~9@6vO}k literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.log new file mode 100644 index 0000000000..dc4eaae5ff --- /dev/null +++ b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.log @@ -0,0 +1,26 @@ +seed: 319146 +frame seed: 319146 + frame content size: 114 + frame window size: 720896 + content size flag: 1 + single segment flag: 0 + block: + block content size: 114 + last block: yes + compressed block: + compressed literals + distribution weight: 23% + huffman log: 10 + regenerated size: 105 + compressed size: 91 + literals size: 105 + total match lengths: 9 + LL: 90 OF: 16 ML: 4 srcPos: 94 seqNb: 0 + LL: 15 OF: 1 ML: 5 srcPos: 114 seqNb: 1 + repeat offset: 1 + excess literals: 0 srcPos: 114 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 2 + block type: compressed + block size field: 104 + checksum: 9a966cac diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.zst new file mode 100644 index 0000000000000000000000000000000000000000..f5b2d6c1575257a12d957e80f41279d3f037b442 GIT binary patch literal 125 zcmdPcs{i+hcM$^wxH2=$V>=+mpx&VBd5N*M&%4!Abb`UsL)L4(4p`eh4N44Jn&7&s zfhCmbUgp(9tGRd>xEXjDo<#KNuVKH|(Q`2@YkIQV?bo+vCVyHjDlP19rf)H+gx5Ti cbxGO3=*gQaBbvFH7}ka4Hc7Is$(c3_0QPJzW&i*H literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.log new file mode 100644 index 0000000000..557c6176f1 --- /dev/null +++ b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.log @@ -0,0 +1,25 @@ +seed: 331938 +frame seed: 331938 + frame content size: 104 + frame window size: 36864 + content size flag: 1 + single segment flag: 0 + block: + block content size: 104 + last block: yes + compressed block: + compressed literals + distribution weight: 37% + huffman log: 9 + regenerated size: 67 + compressed size: 60 + literals size: 67 + total match lengths: 37 + LL: 31 OF: 31 ML: 23 srcPos: 54 seqNb: 0 + LL: 19 OF: 13 ML: 14 srcPos: 87 seqNb: 1 + excess literals: 17 srcPos: 104 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 2 + block type: compressed + block size field: 72 + checksum: d76a8c9e diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.zst new file mode 100644 index 0000000000000000000000000000000000000000..943fafbf00fffabb1fa2d2784f50c5cfb70ceba8 GIT binary patch literal 93 zcmdPcs{i+hW(ETUxH2(Vv3y{VnV{-v^|}1Zgt>J$_OH3BU;6cF_^+w*D~+EPeG_J5 vU}s=s(7L))x}52;xSZD~N$*wq>iHq8tCng!n#|9{ka#LFaJ9|6o~-KtQv4(n literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.log new file mode 100644 index 0000000000..025dfe3f63 --- /dev/null +++ b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.log @@ -0,0 +1,27 @@ +seed: 333824 +frame seed: 333824 + frame content size: 109 + frame window size: 109 + content size flag: 1 + single segment flag: 1 + block: + block content size: 109 + last block: yes + compressed block: + compressed literals + distribution weight: 74% + huffman log: 9 + regenerated size: 86 + compressed size: 30 + literals size: 86 + total match lengths: 23 + LL: 10 OF: 5 ML: 3 srcPos: 13 seqNb: 0 + LL: 45 OF: 40 ML: 10 srcPos: 68 seqNb: 1 + LL: 21 OF: 5 ML: 10 srcPos: 99 seqNb: 2 + repeat offset: 1 + excess literals: 10 srcPos: 109 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 3 + block type: compressed + block size field: 43 + checksum: 5905f5bb diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.zst new file mode 100644 index 0000000000000000000000000000000000000000..51b18d62b87ad7cfbad0b4f5e96e4cccda62bc75 GIT binary patch literal 56 zcmdPcs{dCdHZg`6ou9S z5JU*~5J;8PI8mhQEFGL?0Nh8g z4m2p9q(C15-jpEd0N`!#j#oYixCUef0Hc8$0VoX2|kOt6L zkPomvu%rNiO5lnD3<$uwhTkK=IHVu!04+dF0N@Bn&j166X8?!uzbLO0x}3Ppn%bWE^@Ud$Q%Yiz?mQboC^>)^sTYxgG_2nAe#AqD^v0Qw~? vEl4>)P9^5f~RykOuNWH4MB5uq}Ww-~kj7zL$y8fFai2 literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/pregenerated_compressed_random_2.log b/xls/modules/zstd/data/pregenerated_compressed_random_2.log new file mode 100644 index 0000000000..e120ce7b47 --- /dev/null +++ b/xls/modules/zstd/data/pregenerated_compressed_random_2.log @@ -0,0 +1,70 @@ +seed: 2 +frame seed: 2 + frame content size: 152 + frame window size: 114688 + content size flag: 1 + single segment flag: 0 + block: + block content size: 91 + last block: no + compressed block: + compressed literals + distribution weight: 33% + huffman log: 10 + regenerated size: 44 + compressed size: 49 + trying again + distribution weight: 64% + huffman log: 8 + regenerated size: 35 + compressed size: 30 + literals size: 35 + total match lengths: 56 + LL: 14 OF: 12 ML: 6 srcPos: 20 seqNb: 0 + LL: 1 OF: 9 ML: 9 srcPos: 30 seqNb: 1 + LL: 5 OF: 12 ML: 10 srcPos: 45 seqNb: 2 + LL: 0 OF: 39 ML: 3 srcPos: 48 seqNb: 3 + LL: 5 OF: 3 ML: 13 srcPos: 66 seqNb: 4 + LL: 10 OF: 24 ML: 15 srcPos: 91 seqNb: 5 + excess literals: 0 srcPos: 91 + LL type: 2 OF type: 2 ML type: 2 + number of sequences: 6 + block type: compressed + block size field: 61 + block: + block content size: 53 + last block: no + compressed block: + rle literals: 0xf8 + literals size: 19 + total match lengths: 34 + LL: 2 OF: 56 ML: 3 srcPos: 96 seqNb: 0 + LL: 1 OF: 56 ML: 3 srcPos: 100 seqNb: 1 + repeat offset: 0 + LL: 0 OF: 40 ML: 4 srcPos: 104 seqNb: 2 + LL: 0 OF: 48 ML: 4 srcPos: 108 seqNb: 3 + LL: 6 OF: 53 ML: 3 srcPos: 117 seqNb: 4 + LL: 5 OF: 81 ML: 3 srcPos: 125 seqNb: 5 + LL: 2 OF: 111 ML: 4 srcPos: 131 seqNb: 6 + LL: 3 OF: 79 ML: 3 srcPos: 137 seqNb: 7 + LL: 0 OF: 78 ML: 3 srcPos: 140 seqNb: 8 + LL: 0 OF: 87 ML: 4 srcPos: 144 seqNb: 9 + excess literals: 0 srcPos: 144 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 10 + block type: compressed + block size field: 29 + block: + block content size: 8 + last block: yes + compressed block: + raw literals + literals size: 0 + total match lengths: 8 + LL: 0 OF: 58 ML: 8 srcPos: 152 seqNb: 0 + excess literals: 0 srcPos: 152 + LL type: 3 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 7 + checksum: e7ab4be9 diff --git a/xls/modules/zstd/data/pregenerated_compressed_random_2.zst b/xls/modules/zstd/data/pregenerated_compressed_random_2.zst new file mode 100644 index 0000000000000000000000000000000000000000..014359109f9ab7dcc6a82d125f26aad14fcef42c GIT binary patch literal 120 zcmV-;0Eho5wJ-gIHkbea0PFz(GJ*#cpe0EMXu$vfze!Z7s;VGo5C8zf2W|GT<=Toz z9tNmjOxQ9O4-hid4uB#PnI#Jc3&^YR3!WYh3G4s>nfM9-S_BXU0HlB+M}VmSXsn>7 a3Ro%t0Nf2IEIj}K00Gnm1sw?KORMKncPS_U literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/pregenerated_compressed_raw_1.log b/xls/modules/zstd/data/pregenerated_compressed_raw_1.log new file mode 100644 index 0000000000..15e8ef895b --- /dev/null +++ b/xls/modules/zstd/data/pregenerated_compressed_raw_1.log @@ -0,0 +1,347 @@ +seed: 1 +frame seed: 1 + frame content size: 1019 + frame window size: 294912 + content size flag: 1 + single segment flag: 0 + block: + block content size: 181 + last block: no + compressed block: + raw literals + literals size: 139 + total match lengths: 42 + LL: 37 OF: 31 ML: 13 srcPos: 50 seqNb: 0 + LL: 25 OF: 16 ML: 29 srcPos: 104 seqNb: 1 + excess literals: 77 srcPos: 181 + LL type: 2 OF type: 2 ML type: 2 + number of sequences: 2 + block type: compressed + block size field: 168 + block: + block content size: 0 + last block: no + compressed block: + raw literals + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 181 + number of sequences: 0 + block type: compressed + block size field: 2 + block: + block content size: 63 + last block: no + compressed block: + raw literals + literals size: 7 + total match lengths: 56 + LL: 0 OF: 70 ML: 3 srcPos: 184 seqNb: 0 + LL: 0 OF: 56 ML: 3 srcPos: 187 seqNb: 1 + LL: 0 OF: 55 ML: 3 srcPos: 190 seqNb: 2 + repeat offset: 2 + LL: 0 OF: 23 ML: 3 srcPos: 193 seqNb: 3 + LL: 0 OF: 4 ML: 3 srcPos: 196 seqNb: 4 + LL: 0 OF: 136 ML: 3 srcPos: 199 seqNb: 5 + LL: 0 OF: 23 ML: 3 srcPos: 202 seqNb: 6 + LL: 0 OF: 28 ML: 3 srcPos: 205 seqNb: 7 + LL: 0 OF: 2 ML: 3 srcPos: 208 seqNb: 8 + LL: 1 OF: 5 ML: 3 srcPos: 212 seqNb: 9 + LL: 0 OF: 4 ML: 3 srcPos: 215 seqNb: 10 + repeat offset: 2 + LL: 0 OF: 2 ML: 3 srcPos: 218 seqNb: 11 + repeat offset: 2 + LL: 0 OF: 56 ML: 3 srcPos: 221 seqNb: 12 + LL: 2 OF: 209 ML: 3 srcPos: 226 seqNb: 13 + LL: 0 OF: 24 ML: 4 srcPos: 230 seqNb: 14 + LL: 0 OF: 14 ML: 3 srcPos: 233 seqNb: 15 + LL: 0 OF: 112 ML: 4 srcPos: 237 seqNb: 16 + LL: 0 OF: 233 ML: 3 srcPos: 240 seqNb: 17 + excess literals: 4 srcPos: 244 + LL type: 2 OF type: 2 ML type: 2 + number of sequences: 18 + block type: compressed + block size field: 39 + block: + block content size: 59 + last block: no + compressed block: + raw literals + literals size: 19 + total match lengths: 40 + LL: 2 OF: 90 ML: 6 srcPos: 252 seqNb: 0 + LL: 2 OF: 153 ML: 5 srcPos: 259 seqNb: 1 + LL: 10 OF: 76 ML: 10 srcPos: 279 seqNb: 2 + LL: 1 OF: 226 ML: 5 srcPos: 285 seqNb: 3 + LL: 2 OF: 5 ML: 4 srcPos: 291 seqNb: 4 + LL: 2 OF: 33 ML: 7 srcPos: 300 seqNb: 5 + LL: 0 OF: 262 ML: 3 srcPos: 303 seqNb: 6 + excess literals: 0 srcPos: 303 + LL type: 2 OF type: 2 ML type: 0 + number of sequences: 7 + block type: compressed + block size field: 45 + block: + block content size: 614 + last block: no + compressed block: + raw literals + literals size: 159 + total match lengths: 455 + LL: 2 OF: 128 ML: 3 srcPos: 308 seqNb: 0 + LL: 3 OF: 96 ML: 3 srcPos: 314 seqNb: 1 + LL: 0 OF: 123 ML: 3 srcPos: 317 seqNb: 2 + LL: 0 OF: 225 ML: 3 srcPos: 320 seqNb: 3 + LL: 1 OF: 172 ML: 3 srcPos: 324 seqNb: 4 + LL: 2 OF: 56 ML: 3 srcPos: 329 seqNb: 5 + LL: 2 OF: 13 ML: 3 srcPos: 334 seqNb: 6 + LL: 1 OF: 327 ML: 3 srcPos: 338 seqNb: 7 + LL: 0 OF: 264 ML: 3 srcPos: 341 seqNb: 8 + LL: 0 OF: 13 ML: 3 srcPos: 344 seqNb: 9 + repeat offset: 2 + LL: 0 OF: 340 ML: 3 srcPos: 347 seqNb: 10 + LL: 0 OF: 95 ML: 3 srcPos: 350 seqNb: 11 + LL: 0 OF: 221 ML: 3 srcPos: 353 seqNb: 12 + LL: 0 OF: 95 ML: 3 srcPos: 356 seqNb: 13 + repeat offset: 1 + LL: 0 OF: 94 ML: 3 srcPos: 359 seqNb: 14 + repeat offset: 2 + LL: 0 OF: 95 ML: 3 srcPos: 362 seqNb: 15 + repeat offset: 1 + LL: 0 OF: 294 ML: 3 srcPos: 365 seqNb: 16 + LL: 0 OF: 250 ML: 3 srcPos: 368 seqNb: 17 + LL: 3 OF: 310 ML: 3 srcPos: 374 seqNb: 18 + LL: 0 OF: 59 ML: 3 srcPos: 377 seqNb: 19 + LL: 0 OF: 17 ML: 3 srcPos: 380 seqNb: 20 + LL: 1 OF: 59 ML: 3 srcPos: 384 seqNb: 21 + repeat offset: 1 + LL: 0 OF: 372 ML: 3 srcPos: 387 seqNb: 22 + LL: 1 OF: 203 ML: 3 srcPos: 391 seqNb: 23 + LL: 1 OF: 251 ML: 3 srcPos: 395 seqNb: 24 + LL: 0 OF: 203 ML: 3 srcPos: 398 seqNb: 25 + repeat offset: 1 + LL: 1 OF: 363 ML: 3 srcPos: 402 seqNb: 26 + LL: 0 OF: 321 ML: 3 srcPos: 405 seqNb: 27 + LL: 1 OF: 395 ML: 3 srcPos: 409 seqNb: 28 + LL: 1 OF: 381 ML: 3 srcPos: 413 seqNb: 29 + LL: 3 OF: 99 ML: 3 srcPos: 419 seqNb: 30 + LL: 1 OF: 116 ML: 3 srcPos: 423 seqNb: 31 + LL: 0 OF: 293 ML: 3 srcPos: 426 seqNb: 32 + LL: 0 OF: 292 ML: 3 srcPos: 429 seqNb: 33 + repeat offset: 2 + LL: 1 OF: 127 ML: 3 srcPos: 433 seqNb: 34 + LL: 0 OF: 219 ML: 3 srcPos: 436 seqNb: 35 + LL: 0 OF: 236 ML: 3 srcPos: 439 seqNb: 36 + LL: 1 OF: 353 ML: 3 srcPos: 443 seqNb: 37 + LL: 1 OF: 46 ML: 3 srcPos: 447 seqNb: 38 + LL: 3 OF: 404 ML: 3 srcPos: 453 seqNb: 39 + LL: 0 OF: 267 ML: 3 srcPos: 456 seqNb: 40 + LL: 0 OF: 421 ML: 3 srcPos: 459 seqNb: 41 + LL: 1 OF: 201 ML: 3 srcPos: 463 seqNb: 42 + LL: 1 OF: 12 ML: 3 srcPos: 467 seqNb: 43 + LL: 0 OF: 438 ML: 3 srcPos: 470 seqNb: 44 + LL: 1 OF: 136 ML: 3 srcPos: 474 seqNb: 45 + LL: 2 OF: 126 ML: 3 srcPos: 479 seqNb: 46 + LL: 0 OF: 400 ML: 3 srcPos: 482 seqNb: 47 + LL: 0 OF: 130 ML: 3 srcPos: 485 seqNb: 48 + LL: 2 OF: 206 ML: 3 srcPos: 490 seqNb: 49 + LL: 0 OF: 302 ML: 3 srcPos: 493 seqNb: 50 + LL: 0 OF: 206 ML: 3 srcPos: 496 seqNb: 51 + LL: 1 OF: 420 ML: 3 srcPos: 500 seqNb: 52 + LL: 1 OF: 131 ML: 3 srcPos: 504 seqNb: 53 + LL: 1 OF: 172 ML: 3 srcPos: 508 seqNb: 54 + LL: 0 OF: 131 ML: 3 srcPos: 511 seqNb: 55 + repeat offset: 1 + LL: 2 OF: 438 ML: 3 srcPos: 516 seqNb: 56 + LL: 0 OF: 453 ML: 3 srcPos: 519 seqNb: 57 + LL: 0 OF: 248 ML: 3 srcPos: 522 seqNb: 58 + LL: 0 OF: 438 ML: 3 srcPos: 525 seqNb: 59 + repeat offset: 2 + LL: 1 OF: 326 ML: 3 srcPos: 529 seqNb: 60 + LL: 0 OF: 248 ML: 3 srcPos: 532 seqNb: 61 + repeat offset: 2 + LL: 1 OF: 46 ML: 3 srcPos: 536 seqNb: 62 + LL: 2 OF: 139 ML: 3 srcPos: 541 seqNb: 63 + LL: 1 OF: 139 ML: 3 srcPos: 545 seqNb: 64 + repeat offset: 0 + LL: 0 OF: 508 ML: 4 srcPos: 549 seqNb: 65 + LL: 2 OF: 482 ML: 3 srcPos: 554 seqNb: 66 + LL: 1 OF: 127 ML: 3 srcPos: 558 seqNb: 67 + LL: 3 OF: 485 ML: 3 srcPos: 564 seqNb: 68 + LL: 0 OF: 300 ML: 3 srcPos: 567 seqNb: 69 + LL: 2 OF: 338 ML: 3 srcPos: 572 seqNb: 70 + LL: 1 OF: 285 ML: 3 srcPos: 576 seqNb: 71 + LL: 0 OF: 300 ML: 3 srcPos: 579 seqNb: 72 + repeat offset: 2 + LL: 1 OF: 60 ML: 3 srcPos: 583 seqNb: 73 + LL: 2 OF: 304 ML: 3 srcPos: 588 seqNb: 74 + LL: 2 OF: 106 ML: 3 srcPos: 593 seqNb: 75 + LL: 4 OF: 106 ML: 3 srcPos: 600 seqNb: 76 + repeat offset: 0 + LL: 2 OF: 106 ML: 3 srcPos: 605 seqNb: 77 + repeat offset: 0 + LL: 5 OF: 60 ML: 3 srcPos: 613 seqNb: 78 + repeat offset: 2 + LL: 2 OF: 594 ML: 3 srcPos: 618 seqNb: 79 + LL: 1 OF: 234 ML: 3 srcPos: 622 seqNb: 80 + LL: 1 OF: 227 ML: 3 srcPos: 626 seqNb: 81 + LL: 0 OF: 235 ML: 3 srcPos: 629 seqNb: 82 + LL: 1 OF: 311 ML: 3 srcPos: 633 seqNb: 83 + LL: 0 OF: 253 ML: 3 srcPos: 636 seqNb: 84 + LL: 1 OF: 286 ML: 3 srcPos: 640 seqNb: 85 + LL: 0 OF: 175 ML: 3 srcPos: 643 seqNb: 86 + LL: 1 OF: 395 ML: 3 srcPos: 647 seqNb: 87 + LL: 2 OF: 544 ML: 3 srcPos: 652 seqNb: 88 + LL: 0 OF: 278 ML: 3 srcPos: 655 seqNb: 89 + LL: 1 OF: 450 ML: 3 srcPos: 659 seqNb: 90 + LL: 4 OF: 469 ML: 3 srcPos: 666 seqNb: 91 + LL: 5 OF: 192 ML: 3 srcPos: 674 seqNb: 92 + LL: 0 OF: 492 ML: 3 srcPos: 677 seqNb: 93 + LL: 2 OF: 192 ML: 3 srcPos: 682 seqNb: 94 + repeat offset: 1 + LL: 2 OF: 597 ML: 3 srcPos: 687 seqNb: 95 + LL: 1 OF: 501 ML: 3 srcPos: 691 seqNb: 96 + LL: 0 OF: 148 ML: 3 srcPos: 694 seqNb: 97 + LL: 1 OF: 513 ML: 3 srcPos: 698 seqNb: 98 + LL: 0 OF: 143 ML: 3 srcPos: 701 seqNb: 99 + LL: 2 OF: 89 ML: 3 srcPos: 706 seqNb: 100 + LL: 1 OF: 513 ML: 3 srcPos: 710 seqNb: 101 + repeat offset: 2 + LL: 0 OF: 35 ML: 3 srcPos: 713 seqNb: 102 + LL: 0 OF: 218 ML: 3 srcPos: 716 seqNb: 103 + LL: 1 OF: 140 ML: 3 srcPos: 720 seqNb: 104 + LL: 2 OF: 465 ML: 3 srcPos: 725 seqNb: 105 + LL: 0 OF: 183 ML: 3 srcPos: 728 seqNb: 106 + LL: 0 OF: 704 ML: 4 srcPos: 732 seqNb: 107 + LL: 2 OF: 361 ML: 3 srcPos: 737 seqNb: 108 + LL: 1 OF: 460 ML: 3 srcPos: 741 seqNb: 109 + LL: 3 OF: 554 ML: 3 srcPos: 747 seqNb: 110 + LL: 3 OF: 544 ML: 3 srcPos: 753 seqNb: 111 + LL: 0 OF: 19 ML: 3 srcPos: 756 seqNb: 112 + LL: 2 OF: 539 ML: 3 srcPos: 761 seqNb: 113 + LL: 0 OF: 557 ML: 3 srcPos: 764 seqNb: 114 + LL: 3 OF: 501 ML: 3 srcPos: 770 seqNb: 115 + LL: 2 OF: 557 ML: 3 srcPos: 775 seqNb: 116 + repeat offset: 1 + LL: 0 OF: 683 ML: 3 srcPos: 778 seqNb: 117 + LL: 0 OF: 362 ML: 3 srcPos: 781 seqNb: 118 + LL: 1 OF: 723 ML: 4 srcPos: 786 seqNb: 119 + LL: 1 OF: 526 ML: 4 srcPos: 791 seqNb: 120 + LL: 0 OF: 653 ML: 3 srcPos: 794 seqNb: 121 + LL: 0 OF: 572 ML: 3 srcPos: 797 seqNb: 122 + LL: 1 OF: 572 ML: 3 srcPos: 801 seqNb: 123 + repeat offset: 0 + LL: 0 OF: 383 ML: 4 srcPos: 805 seqNb: 124 + LL: 4 OF: 383 ML: 4 srcPos: 813 seqNb: 125 + repeat offset: 0 + LL: 1 OF: 46 ML: 4 srcPos: 818 seqNb: 126 + LL: 0 OF: 742 ML: 3 srcPos: 821 seqNb: 127 + LL: 2 OF: 84 ML: 4 srcPos: 827 seqNb: 128 + LL: 1 OF: 793 ML: 3 srcPos: 831 seqNb: 129 + LL: 4 OF: 277 ML: 3 srcPos: 838 seqNb: 130 + LL: 0 OF: 312 ML: 3 srcPos: 841 seqNb: 131 + LL: 1 OF: 306 ML: 3 srcPos: 845 seqNb: 132 + LL: 0 OF: 706 ML: 3 srcPos: 848 seqNb: 133 + LL: 0 OF: 244 ML: 3 srcPos: 851 seqNb: 134 + LL: 4 OF: 301 ML: 3 srcPos: 858 seqNb: 135 + LL: 1 OF: 53 ML: 3 srcPos: 862 seqNb: 136 + LL: 1 OF: 313 ML: 3 srcPos: 866 seqNb: 137 + LL: 1 OF: 53 ML: 3 srcPos: 870 seqNb: 138 + repeat offset: 1 + LL: 1 OF: 301 ML: 4 srcPos: 875 seqNb: 139 + repeat offset: 2 + LL: 6 OF: 53 ML: 3 srcPos: 884 seqNb: 140 + repeat offset: 1 + LL: 3 OF: 295 ML: 3 srcPos: 890 seqNb: 141 + LL: 0 OF: 363 ML: 3 srcPos: 893 seqNb: 142 + LL: 1 OF: 742 ML: 4 srcPos: 898 seqNb: 143 + LL: 2 OF: 742 ML: 4 srcPos: 904 seqNb: 144 + repeat offset: 0 + LL: 2 OF: 728 ML: 3 srcPos: 909 seqNb: 145 + LL: 1 OF: 728 ML: 3 srcPos: 913 seqNb: 146 + repeat offset: 0 + LL: 0 OF: 597 ML: 3 srcPos: 916 seqNb: 147 + excess literals: 1 srcPos: 917 + LL type: 0 OF type: 0 ML type: 3 + number of sequences: 148 + block type: compressed + block size field: 550 + block: + block content size: 24 + last block: no + compressed block: + raw literals + literals size: 21 + total match lengths: 3 + LL: 9 OF: 905 ML: 3 srcPos: 929 seqNb: 0 + excess literals: 12 srcPos: 941 + LL type: 1 OF type: 1 ML type: 3 + number of sequences: 1 + block type: compressed + block size field: 28 + block: + block content size: 0 + last block: no + compressed block: + raw literals + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 941 + number of sequences: 0 + block type: compressed + block size field: 2 + block: + block content size: 24 + last block: no + compressed block: + raw literals + literals size: 14 + total match lengths: 10 + LL: 11 OF: 506 ML: 10 srcPos: 962 seqNb: 0 + excess literals: 3 srcPos: 965 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 22 + block: + block content size: 47 + last block: no + compressed block: + raw literals + literals size: 40 + total match lengths: 7 + LL: 40 OF: 833 ML: 4 srcPos: 1009 seqNb: 0 + LL: 0 OF: 832 ML: 3 srcPos: 1012 seqNb: 1 + repeat offset: 2 + excess literals: 0 srcPos: 1012 + LL type: 0 OF type: 0 ML type: 2 + number of sequences: 2 + block type: compressed + block size field: 53 + block: + block content size: 4 + last block: no + compressed block: + raw literals + literals size: 0 + total match lengths: 4 + LL: 0 OF: 92 ML: 4 srcPos: 1016 seqNb: 0 + excess literals: 0 srcPos: 1016 + LL type: 3 OF type: 1 ML type: 3 + number of sequences: 1 + block type: compressed + block size field: 7 + block: + block content size: 3 + last block: yes + compressed block: + raw literals + literals size: 0 + total match lengths: 3 + LL: 0 OF: 280 ML: 3 srcPos: 1019 seqNb: 0 + excess literals: 0 srcPos: 1019 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 8 + checksum: 20a07047 diff --git a/xls/modules/zstd/data/pregenerated_compressed_raw_1.zst b/xls/modules/zstd/data/pregenerated_compressed_raw_1.zst new file mode 100644 index 0000000000000000000000000000000000000000..bea909ee30ae7aae91212d16fe49be9a48dd2ede GIT binary patch literal 975 zcmV;=12Ft3wJ-g|LHh#$0000007L}U1+0te4+(PM}5`4S%4#K~0e4b!> z6np>HcPmBA)MmhAm7o|)L^$3P119i$P3cL5u^8T}Cea-+RuWxK)_f4aTK^$2r!+1h$=1w39bMDa6beP zkOV*k5ob*TZd-u45EUo0hdDnB@YM!0^G%bz+b?{A^_|Fd?z3bAJEtfQ1Jqnv4Ai_0CZe{Wd>Nt0t~Q# z=`6r*1I%myYa2jR5deMx4FC)PX#>DB3&5`cc7TBpz^Vx77XmCg0V8of@L`rwEnV{I-$Jl#*-- xp(j4Orn?{>H6Q{A6!1SU0HOgz89V?000G{M>pXq=MiB#L6R)2XmS(&lAC#4x{_}C3XNx`tMGFeX&8w1c+zUu*076B9%Bmn?;Q_lcm z93$)3o$+ygq1Dv`h!ikj{{z%2fK>ti0W2LgJ#K*f002N=!^){%+&j1gpwudWRsRz} vP@tgTAGAOh?9mp(*gOCL00C401pzk{000008~^|aVgMZg00=7pcn0~a;H*lJ literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/pregenerated_compressed_rle_1.log b/xls/modules/zstd/data/pregenerated_compressed_rle_1.log new file mode 100644 index 0000000000..6758c747f2 --- /dev/null +++ b/xls/modules/zstd/data/pregenerated_compressed_rle_1.log @@ -0,0 +1,179 @@ +seed: 1 +frame seed: 1 + frame content size: 1019 + frame window size: 294912 + content size flag: 1 + single segment flag: 0 + block: + block content size: 181 + last block: no + compressed block: + rle literals: 0xed + literals size: 139 + total match lengths: 42 + LL: 139 OF: 97 ML: 42 srcPos: 181 seqNb: 0 + excess literals: 0 srcPos: 181 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 10 + block: + block content size: 178 + last block: no + compressed block: + rle literals: 0xc6 + literals size: 21 + total match lengths: 157 + LL: 0 OF: 53 ML: 4 srcPos: 185 seqNb: 0 + LL: 0 OF: 155 ML: 3 srcPos: 188 seqNb: 1 + LL: 0 OF: 7 ML: 3 srcPos: 191 seqNb: 2 + LL: 2 OF: 124 ML: 4 srcPos: 197 seqNb: 3 + LL: 0 OF: 138 ML: 4 srcPos: 201 seqNb: 4 + LL: 0 OF: 92 ML: 4 srcPos: 205 seqNb: 5 + LL: 0 OF: 1 ML: 5 srcPos: 210 seqNb: 6 + LL: 0 OF: 93 ML: 3 srcPos: 213 seqNb: 7 + LL: 1 OF: 144 ML: 5 srcPos: 219 seqNb: 8 + LL: 1 OF: 183 ML: 4 srcPos: 224 seqNb: 9 + LL: 0 OF: 140 ML: 3 srcPos: 227 seqNb: 10 + LL: 1 OF: 42 ML: 5 srcPos: 233 seqNb: 11 + LL: 0 OF: 118 ML: 4 srcPos: 237 seqNb: 12 + LL: 1 OF: 47 ML: 4 srcPos: 242 seqNb: 13 + LL: 0 OF: 232 ML: 5 srcPos: 247 seqNb: 14 + LL: 0 OF: 24 ML: 4 srcPos: 251 seqNb: 15 + LL: 1 OF: 24 ML: 5 srcPos: 257 seqNb: 16 + repeat offset: 0 + LL: 0 OF: 2 ML: 3 srcPos: 260 seqNb: 17 + LL: 0 OF: 212 ML: 4 srcPos: 264 seqNb: 18 + LL: 1 OF: 66 ML: 4 srcPos: 269 seqNb: 19 + LL: 0 OF: 65 ML: 5 srcPos: 274 seqNb: 20 + repeat offset: 2 + LL: 0 OF: 212 ML: 5 srcPos: 279 seqNb: 21 + repeat offset: 2 + LL: 0 OF: 119 ML: 4 srcPos: 283 seqNb: 22 + LL: 1 OF: 245 ML: 4 srcPos: 288 seqNb: 23 + LL: 0 OF: 152 ML: 5 srcPos: 293 seqNb: 24 + LL: 0 OF: 219 ML: 3 srcPos: 296 seqNb: 25 + LL: 0 OF: 107 ML: 5 srcPos: 301 seqNb: 26 + LL: 0 OF: 157 ML: 4 srcPos: 305 seqNb: 27 + LL: 1 OF: 30 ML: 6 srcPos: 312 seqNb: 28 + LL: 1 OF: 44 ML: 5 srcPos: 318 seqNb: 29 + LL: 0 OF: 151 ML: 4 srcPos: 322 seqNb: 30 + LL: 1 OF: 30 ML: 4 srcPos: 327 seqNb: 31 + repeat offset: 2 + LL: 1 OF: 8 ML: 4 srcPos: 332 seqNb: 32 + LL: 1 OF: 238 ML: 4 srcPos: 337 seqNb: 33 + LL: 6 OF: 244 ML: 5 srcPos: 348 seqNb: 34 + LL: 0 OF: 290 ML: 3 srcPos: 351 seqNb: 35 + LL: 1 OF: 271 ML: 3 srcPos: 355 seqNb: 36 + LL: 0 OF: 111 ML: 4 srcPos: 359 seqNb: 37 + excess literals: 0 srcPos: 359 + LL type: 0 OF type: 2 ML type: 2 + number of sequences: 38 + block type: compressed + block size field: 80 + block: + block content size: 419 + last block: no + compressed block: + rle literals: 0xc1 + literals size: 282 + total match lengths: 137 + LL: 0 OF: 353 ML: 5 srcPos: 364 seqNb: 0 + LL: 1 OF: 111 ML: 3 srcPos: 368 seqNb: 1 + repeat offset: 1 + LL: 18 OF: 65 ML: 4 srcPos: 390 seqNb: 2 + LL: 3 OF: 213 ML: 6 srcPos: 399 seqNb: 3 + LL: 0 OF: 249 ML: 6 srcPos: 405 seqNb: 4 + LL: 11 OF: 285 ML: 11 srcPos: 427 seqNb: 5 + LL: 4 OF: 297 ML: 5 srcPos: 436 seqNb: 6 + LL: 4 OF: 61 ML: 5 srcPos: 445 seqNb: 7 + LL: 11 OF: 204 ML: 4 srcPos: 460 seqNb: 8 + LL: 7 OF: 394 ML: 3 srcPos: 470 seqNb: 9 + LL: 7 OF: 30 ML: 5 srcPos: 482 seqNb: 10 + LL: 4 OF: 346 ML: 6 srcPos: 492 seqNb: 11 + LL: 1 OF: 394 ML: 3 srcPos: 496 seqNb: 12 + repeat offset: 2 + LL: 53 OF: 501 ML: 5 srcPos: 554 seqNb: 13 + LL: 15 OF: 116 ML: 3 srcPos: 572 seqNb: 14 + LL: 5 OF: 116 ML: 3 srcPos: 580 seqNb: 15 + repeat offset: 0 + LL: 4 OF: 80 ML: 5 srcPos: 589 seqNb: 16 + LL: 32 OF: 434 ML: 3 srcPos: 624 seqNb: 17 + LL: 6 OF: 80 ML: 4 srcPos: 634 seqNb: 18 + repeat offset: 1 + LL: 3 OF: 387 ML: 4 srcPos: 641 seqNb: 19 + LL: 0 OF: 639 ML: 6 srcPos: 647 seqNb: 20 + LL: 4 OF: 319 ML: 7 srcPos: 658 seqNb: 21 + LL: 0 OF: 639 ML: 4 srcPos: 662 seqNb: 22 + repeat offset: 1 + LL: 5 OF: 387 ML: 3 srcPos: 670 seqNb: 23 + repeat offset: 2 + LL: 0 OF: 639 ML: 4 srcPos: 674 seqNb: 24 + repeat offset: 1 + LL: 5 OF: 166 ML: 3 srcPos: 682 seqNb: 25 + LL: 0 OF: 135 ML: 3 srcPos: 685 seqNb: 26 + LL: 60 OF: 91 ML: 5 srcPos: 750 seqNb: 27 + LL: 1 OF: 141 ML: 4 srcPos: 755 seqNb: 28 + LL: 0 OF: 667 ML: 5 srcPos: 760 seqNb: 29 + excess literals: 18 srcPos: 778 + LL type: 2 OF type: 0 ML type: 2 + number of sequences: 30 + block type: compressed + block size field: 80 + block: + block content size: 7 + last block: no + compressed block: + rle literals: 0x3a + literals size: 1 + total match lengths: 6 + LL: 1 OF: 282 ML: 6 srcPos: 785 seqNb: 0 + excess literals: 0 srcPos: 785 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 9 + block: + block content size: 129 + last block: no + compressed block: + rle literals: 0xcf + literals size: 60 + total match lengths: 69 + LL: 0 OF: 455 ML: 69 srcPos: 854 seqNb: 0 + excess literals: 60 srcPos: 914 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 10 + block: + block content size: 85 + last block: no + compressed block: + rle literals: 0xf1 + literals size: 76 + total match lengths: 9 + LL: 44 OF: 616 ML: 4 srcPos: 962 seqNb: 0 + LL: 11 OF: 455 ML: 5 srcPos: 978 seqNb: 1 + repeat offset: 1 + excess literals: 21 srcPos: 999 + LL type: 2 OF type: 2 ML type: 2 + number of sequences: 2 + block type: compressed + block size field: 29 + block: + block content size: 20 + last block: yes + compressed block: + rle literals: 0xf5 + literals size: 11 + total match lengths: 9 + LL: 1 OF: 118 ML: 3 srcPos: 1003 seqNb: 0 + LL: 2 OF: 709 ML: 3 srcPos: 1008 seqNb: 1 + LL: 8 OF: 168 ML: 3 srcPos: 1019 seqNb: 2 + excess literals: 0 srcPos: 1019 + LL type: 2 OF type: 2 ML type: 1 + number of sequences: 3 + block type: compressed + block size field: 24 + checksum: 043d84eb diff --git a/xls/modules/zstd/data/pregenerated_compressed_rle_1.zst b/xls/modules/zstd/data/pregenerated_compressed_rle_1.zst new file mode 100644 index 0000000000000000000000000000000000000000..ded729fd6a2d15d0e6aebe18e8623fcb8cb9c1af GIT binary patch literal 281 zcmV+!0p|WFwJ-g|LHh#$0000008{_~wFvD2R2l{&i)4fX0I9|%C?Lofsp|oNst>XR z64{kB+*fga3)Z3;@EkA5AZ>2KtFR&9fBFa)EEKpaA({ZZ#aI)_z1*X7fQ~&_Q1J40e00=0e9P9u9#RTyJs1)EYV8Gvh6F^|#`3Dq$fd2u+H2@d_1jPUV fS@i>?6o6K%0{0ISuvbx4Z7K+|R+ literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/pregenerated_compressed_rle_2.log b/xls/modules/zstd/data/pregenerated_compressed_rle_2.log new file mode 100644 index 0000000000..ed2a1dbb00 --- /dev/null +++ b/xls/modules/zstd/data/pregenerated_compressed_rle_2.log @@ -0,0 +1,103 @@ +seed: 2 +frame seed: 2 + frame content size: 152 + frame window size: 114688 + content size flag: 1 + single segment flag: 0 + block: + block content size: 91 + last block: no + compressed block: + rle literals: 0xda + literals size: 68 + total match lengths: 23 + LL: 2 OF: 1 ML: 3 srcPos: 5 seqNb: 0 + LL: 8 OF: 2 ML: 3 srcPos: 16 seqNb: 1 + LL: 5 OF: 10 ML: 4 srcPos: 25 seqNb: 2 + LL: 8 OF: 10 ML: 3 srcPos: 36 seqNb: 3 + repeat offset: 0 + LL: 4 OF: 7 ML: 3 srcPos: 43 seqNb: 4 + LL: 38 OF: 27 ML: 4 srcPos: 85 seqNb: 5 + LL: 2 OF: 68 ML: 3 srcPos: 90 seqNb: 6 + excess literals: 1 srcPos: 91 + LL type: 2 OF type: 0 ML type: 2 + number of sequences: 7 + block type: compressed + block size field: 26 + block: + block content size: 0 + last block: no + compressed block: + rle literals: 0x3c + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 91 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 61 + last block: yes + compressed block: + rle literals: 0xe1 + literals size: 56 + total match lengths: 5 + LL: 0 OF: 26 ML: 5 srcPos: 96 seqNb: 0 + excess literals: 56 srcPos: 152 + LL type: 1 OF type: 3 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 9 + checksum: 57673f5c + +seed: 2 +frame seed: 2 + frame content size: 152 + frame window size: 114688 + content size flag: 1 + single segment flag: 0 + block: + block content size: 91 + last block: no + compressed block: + rle literals: 0xda + literals size: 68 + total match lengths: 23 + LL: 2 OF: 1 ML: 3 srcPos: 5 seqNb: 0 + LL: 8 OF: 2 ML: 3 srcPos: 16 seqNb: 1 + LL: 5 OF: 10 ML: 4 srcPos: 25 seqNb: 2 + LL: 8 OF: 10 ML: 3 srcPos: 36 seqNb: 3 + repeat offset: 0 + LL: 4 OF: 7 ML: 3 srcPos: 43 seqNb: 4 + LL: 38 OF: 27 ML: 4 srcPos: 85 seqNb: 5 + LL: 2 OF: 68 ML: 3 srcPos: 90 seqNb: 6 + excess literals: 1 srcPos: 91 + LL type: 2 OF type: 0 ML type: 2 + number of sequences: 7 + block type: compressed + block size field: 26 + block: + block content size: 0 + last block: no + compressed block: + rle literals: 0x3c + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 91 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 61 + last block: yes + compressed block: + rle literals: 0xe1 + literals size: 56 + total match lengths: 5 + LL: 0 OF: 26 ML: 5 srcPos: 96 seqNb: 0 + excess literals: 56 srcPos: 152 + LL type: 1 OF type: 3 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 9 + checksum: 57673f5c diff --git a/xls/modules/zstd/data/pregenerated_compressed_rle_2.zst b/xls/modules/zstd/data/pregenerated_compressed_rle_2.zst new file mode 100644 index 0000000000000000000000000000000000000000..d9d78e4a9a66fa237bdf8a75d701f7c4d4840907 GIT binary patch literal 61 zcmdPcs{gmeYz6}Z!xaVwSC(7s9Rfy;8&^D?&s-qy-cUPaq7IAX}0Nr&ASO5S3 literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.log b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.log new file mode 100644 index 0000000000..a785b43a08 --- /dev/null +++ b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.log @@ -0,0 +1,25 @@ +seed: 422473 +frame seed: 422473 + frame content size: 25 + frame window size: 5632 + content size flag: 1 + single segment flag: 0 + block: + block content size: 25 + last block: yes + compressed block: + raw literals + literals size: 11 + total match lengths: 14 + LL: 1 OF: 1 ML: 3 srcPos: 4 seqNb: 0 + LL: 0 OF: 3 ML: 4 srcPos: 8 seqNb: 1 + LL: 10 OF: 3 ML: 4 srcPos: 22 seqNb: 2 + repeat offset: 0 + LL: 0 OF: 1 ML: 3 srcPos: 25 seqNb: 3 + repeat offset: 2 + excess literals: 0 srcPos: 25 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 4 + block type: compressed + block size field: 23 + checksum: 335a35eb diff --git a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.zst b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.zst new file mode 100644 index 0000000000000000000000000000000000000000..07ac1493f58207b329213e3034f212774fa3a689 GIT binary patch literal 40 wcmdPcs{gk|SdxK(VJ`ziM1|SyHl6ksf765cEDU-K0xP%~4#YCPHjOd{00iRQU(Tx?_c=7UlTpn&%$8bUcj#XO+rT6+Pi_@% literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.log b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.log new file mode 100644 index 0000000000..672a98a312 --- /dev/null +++ b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.log @@ -0,0 +1,21 @@ +seed: 462302 +frame seed: 462302 + frame content size: 27 + frame window size: 11264 + content size flag: 1 + single segment flag: 0 + block: + block content size: 27 + last block: yes + compressed block: + raw literals + literals size: 6 + total match lengths: 21 + LL: 1 OF: 1 ML: 18 srcPos: 19 seqNb: 0 + LL: 5 OF: 16 ML: 3 srcPos: 27 seqNb: 1 + excess literals: 0 srcPos: 27 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 2 + block type: compressed + block size field: 14 + checksum: 8eb0243a diff --git a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.zst b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.zst new file mode 100644 index 0000000000000000000000000000000000000000..1f38ecd40da94b5837533ca2424f9f15528ab8df GIT binary patch literal 31 mcmdPcs{gk|TAG1@p_GBapd~6~ZcsZDgE8aL;v6fL4SfKSG6{zO literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.log b/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.log new file mode 100644 index 0000000000..769fcf02cd --- /dev/null +++ b/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.log @@ -0,0 +1,20 @@ +seed: 700216 +frame seed: 700216 + frame content size: 21 + frame window size: 2097152 + content size flag: 1 + single segment flag: 0 + block: + block content size: 21 + last block: yes + compressed block: + raw literals + literals size: 18 + total match lengths: 3 + LL: 18 OF: 1 ML: 3 srcPos: 21 seqNb: 0 + excess literals: 0 srcPos: 21 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 25 + checksum: 46d2ac93 diff --git a/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.zst b/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.zst new file mode 100644 index 0000000000000000000000000000000000000000..541f7860abfbb436f4ad70a20656426405511957 GIT binary patch literal 46 ycmdPcs{i*$geU_9oMm8`Q2#~IR#~s&Qp!c2KMrZ9k6d(N3=w2v;F!GTk{bYQNf43% literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.log b/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.log new file mode 100644 index 0000000000..ddd13d1a38 --- /dev/null +++ b/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.log @@ -0,0 +1,87 @@ +seed: 701326 +frame seed: 701326 + frame content size: 28 + frame window size: 11264 + content size flag: 1 + single segment flag: 0 + block: + block content size: 1 + last block: no + compressed block: + rle literals: 0xf3 + literals size: 1 + total match lengths: 0 + excess literals: 1 srcPos: 1 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 3 + last block: no + compressed block: + rle literals: 0x08 + literals size: 3 + total match lengths: 0 + excess literals: 3 srcPos: 4 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 18 + last block: no + compressed block: + rle literals: 0x62 + literals size: 7 + total match lengths: 11 + LL: 0 OF: 1 ML: 3 srcPos: 7 seqNb: 0 + LL: 4 OF: 11 ML: 8 srcPos: 19 seqNb: 1 + excess literals: 3 srcPos: 22 + LL type: 2 OF type: 2 ML type: 2 + number of sequences: 2 + block type: compressed + block size field: 22 + block: + block content size: 2 + last block: no + compressed block: + rle literals: 0x90 + literals size: 2 + total match lengths: 0 + excess literals: 2 srcPos: 24 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 0 + last block: no + compressed block: + rle literals: 0xfd + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 24 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 1 + last block: no + compressed block: + rle literals: 0xab + literals size: 1 + total match lengths: 0 + excess literals: 1 srcPos: 25 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 3 + last block: yes + compressed block: + rle literals: 0x2d + literals size: 3 + total match lengths: 0 + excess literals: 3 srcPos: 28 + number of sequences: 0 + block type: compressed + block size field: 3 + checksum: 08dc0268 diff --git a/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.zst b/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.zst new file mode 100644 index 0000000000000000000000000000000000000000..23f21c7ce7ad473eba27d42293af286ab54c0485 GIT binary patch literal 75 zcmdPcs{gk|T84pv0f;$2gGfmZhAj*XmPt%2L<|o67Y=w}FJh4JkJ*fYhk*qsEjR(B Ui19C2!)gXupaxxr45m9A0E&kX+W-In literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.log b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.log new file mode 100644 index 0000000000..ac01d5ea38 --- /dev/null +++ b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.log @@ -0,0 +1,37 @@ +seed: 406229 +frame seed: 406229 + frame content size: 83 + frame window size: 57344 + content size flag: 1 + single segment flag: 0 + block: + block content size: 76 + last block: no + compressed block: + rle literals: 0xba + literals size: 49 + total match lengths: 27 + LL: 1 OF: 1 ML: 6 srcPos: 7 seqNb: 0 + LL: 14 OF: 16 ML: 4 srcPos: 25 seqNb: 1 + LL: 15 OF: 27 ML: 4 srcPos: 44 seqNb: 2 + LL: 5 OF: 16 ML: 4 srcPos: 53 seqNb: 3 + repeat offset: 1 + LL: 14 OF: 23 ML: 6 srcPos: 73 seqNb: 4 + LL: 0 OF: 20 ML: 3 srcPos: 76 seqNb: 5 + excess literals: 0 srcPos: 76 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 6 + block type: compressed + block size field: 19 + block: + block content size: 7 + last block: yes + compressed block: + rle literals: 0x27 + literals size: 7 + total match lengths: 0 + excess literals: 7 srcPos: 83 + number of sequences: 0 + block type: compressed + block size field: 3 + checksum: e20e662f diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.zst b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.zst new file mode 100644 index 0000000000000000000000000000000000000000..5886d2796cec029257ee5564a2c1505a295d5ec8 GIT binary patch literal 42 ycmdPcs{gk|FPMRWVGaX>DDy5h26M&*#Z#nbonMj8mRb-l%fMi%&Y+*h_Xq$A77Qr> literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.log b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.log new file mode 100644 index 0000000000..8bc1fedfa2 --- /dev/null +++ b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.log @@ -0,0 +1,28 @@ +seed: 411034 +frame seed: 411034 + frame content size: 31 + frame window size: 31 + content size flag: 1 + single segment flag: 1 + block: + block content size: 31 + last block: yes + compressed block: + rle literals: 0x25 + literals size: 1 + total match lengths: 30 + LL: 1 OF: 1 ML: 8 srcPos: 9 seqNb: 0 + LL: 0 OF: 1 ML: 3 srcPos: 12 seqNb: 1 + LL: 0 OF: 5 ML: 6 srcPos: 18 seqNb: 2 + LL: 0 OF: 10 ML: 3 srcPos: 21 seqNb: 3 + LL: 0 OF: 5 ML: 4 srcPos: 25 seqNb: 4 + repeat offset: 1 + LL: 0 OF: 10 ML: 3 srcPos: 28 seqNb: 5 + repeat offset: 1 + LL: 0 OF: 1 ML: 3 srcPos: 31 seqNb: 6 + excess literals: 0 srcPos: 31 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 7 + block type: compressed + block size field: 19 + checksum: 1cc9a33c diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.zst b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.zst new file mode 100644 index 0000000000000000000000000000000000000000..08d368a59b26c379cd54fe05b890118dd0956afe GIT binary patch literal 35 ocmdPcs{eP1JOcy6Tm}YCRdxmoAd_POE5j)uOW+-s&Ek_X0Fqb;QUCw| literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.log b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.log new file mode 100644 index 0000000000..562e10ee40 --- /dev/null +++ b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.log @@ -0,0 +1,53 @@ +seed: 413015 +frame seed: 413015 + frame content size: 86 + frame window size: 86 + content size flag: 1 + single segment flag: 1 + block: + block content size: 48 + last block: no + compressed block: + rle literals: 0x89 + literals size: 35 + total match lengths: 13 + LL: 35 OF: 22 ML: 10 srcPos: 45 seqNb: 0 + LL: 0 OF: 37 ML: 3 srcPos: 48 seqNb: 1 + excess literals: 0 srcPos: 48 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 2 + block type: compressed + block size field: 11 + block: + block content size: 0 + last block: no + compressed block: + rle literals: 0xa8 + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 48 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 38 + last block: yes + compressed block: + rle literals: 0x25 + literals size: 12 + total match lengths: 26 + LL: 0 OF: 24 ML: 3 srcPos: 51 seqNb: 0 + LL: 1 OF: 40 ML: 3 srcPos: 55 seqNb: 1 + LL: 11 OF: 37 ML: 4 srcPos: 70 seqNb: 2 + LL: 0 OF: 5 ML: 3 srcPos: 73 seqNb: 3 + LL: 0 OF: 40 ML: 3 srcPos: 76 seqNb: 4 + LL: 0 OF: 70 ML: 3 srcPos: 79 seqNb: 5 + LL: 0 OF: 74 ML: 3 srcPos: 82 seqNb: 6 + LL: 0 OF: 40 ML: 4 srcPos: 86 seqNb: 7 + repeat offset: 2 + excess literals: 0 srcPos: 86 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 8 + block type: compressed + block size field: 24 + checksum: 9f8a30e7 diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.zst b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.zst new file mode 100644 index 0000000000000000000000000000000000000000..209ef6617839bac20f9102a4ec76721e9caeed48 GIT binary patch literal 64 zcmdPcs{i*%7y|^vFff=hbuuw{FmAZ>Ojw42fpG=HQ3i%YRSpKd1~x_y0|sx#1}27; Q2c|PsG-k3sH|UxV0Q)Qu>;M1& literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.log b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.log new file mode 100644 index 0000000000..adcc599156 --- /dev/null +++ b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.log @@ -0,0 +1,34 @@ +seed: 436165 +frame seed: 436165 + frame content size: 90 + frame window size: 589824 + content size flag: 1 + single segment flag: 0 + block: + block content size: 1 + last block: no + compressed block: + rle literals: 0x88 + literals size: 1 + total match lengths: 0 + excess literals: 1 srcPos: 1 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 89 + last block: yes + compressed block: + rle literals: 0x65 + literals size: 51 + total match lengths: 38 + LL: 4 OF: 1 ML: 5 srcPos: 10 seqNb: 0 + LL: 23 OF: 3 ML: 5 srcPos: 38 seqNb: 1 + LL: 2 OF: 4 ML: 6 srcPos: 46 seqNb: 2 + LL: 9 OF: 33 ML: 22 srcPos: 77 seqNb: 3 + excess literals: 13 srcPos: 90 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 4 + block type: compressed + block size field: 15 + checksum: 391cc8a2 diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.zst b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.zst new file mode 100644 index 0000000000000000000000000000000000000000..1eb90bcec4c7709120731752e030407228765130 GIT binary patch literal 38 ucmdPcs{gmeGm3$ML56{WvxA|Qfx(nHm4(4&cZvF;>AzjJge0Zvp56 BI`IGi literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.log b/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.log new file mode 100644 index 0000000000..d80c261dc5 --- /dev/null +++ b/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.log @@ -0,0 +1,111 @@ +seed: 408158 +frame seed: 408158 + frame content size: 44 + frame window size: 90112 + content size flag: 1 + single segment flag: 0 + block: + block content size: 43 + last block: no + compressed block: + raw literals + literals size: 15 + total match lengths: 28 + LL: 7 OF: 3 ML: 9 srcPos: 16 seqNb: 0 + LL: 0 OF: 2 ML: 4 srcPos: 20 seqNb: 1 + LL: 0 OF: 12 ML: 3 srcPos: 23 seqNb: 2 + LL: 8 OF: 6 ML: 12 srcPos: 43 seqNb: 3 + excess literals: 0 srcPos: 43 + LL type: 0 OF type: 0 ML type: 0 + number of sequences: 4 + block type: compressed + block size field: 27 + block: + block content size: 1 + last block: no + compressed block: + raw literals + literals size: 1 + total match lengths: 0 + excess literals: 1 srcPos: 44 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 0 + last block: no + compressed block: + raw literals + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 44 + number of sequences: 0 + block type: compressed + block size field: 2 + block: + block content size: 0 + last block: no + compressed block: + raw literals + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 44 + number of sequences: 0 + block type: compressed + block size field: 2 + block: + block content size: 0 + last block: no + compressed block: + raw literals + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 44 + number of sequences: 0 + block type: compressed + block size field: 2 + block: + block content size: 0 + last block: no + compressed block: + rle literals: 0x57 + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 44 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 0 + last block: no + compressed block: + rle literals: 0x02 + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 44 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 0 + last block: no + compressed block: + rle literals: 0x96 + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 44 + number of sequences: 0 + block type: compressed + block size field: 3 + block: + block content size: 0 + last block: yes + compressed block: + rle literals: 0x40 + literals size: 0 + total match lengths: 0 + excess literals: 0 srcPos: 44 + number of sequences: 0 + block type: compressed + block size field: 3 + checksum: 975ba214 diff --git a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.zst b/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.zst new file mode 100644 index 0000000000000000000000000000000000000000..ddb555d1ec10bedcdae94e207d616c656badb7e6 GIT binary patch literal 93 zcmdPcs{i+hu?_=t^LcVPI%9WSGLp@SE9#ff)?ne`o*ypYiW+zWX4( q6Ap>Y{#h$vrC#xHLX*h!kBoc_0_QezI~g&`GB7yaVCcHpI}ZR{058%2 literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.log b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.log new file mode 100644 index 0000000000..2e7d1110a7 --- /dev/null +++ b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.log @@ -0,0 +1,46 @@ +seed: 400025 +frame seed: 400025 + frame content size: 142 + frame window size: 40960 + content size flag: 1 + single segment flag: 0 + block: + block content size: 78 + last block: no + compressed block: + compressed literals + small range literals + huffman log: 4 + regenerated size: 66 + compressed size: 50 + literals size: 66 + total match lengths: 12 + LL: 66 OF: 45 ML: 12 srcPos: 78 seqNb: 0 + excess literals: 0 srcPos: 78 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 60 + block: + block content size: 64 + last block: yes + compressed block: + compressed literals + huffman repeat stats + regenerated size: 43 + compressed size: 30 + literals size: 43 + total match lengths: 21 + LL: 12 OF: 17 ML: 3 srcPos: 93 seqNb: 0 + LL: 0 OF: 65 ML: 5 srcPos: 98 seqNb: 1 + LL: 23 OF: 7 ML: 3 srcPos: 124 seqNb: 2 + LL: 4 OF: 65 ML: 4 srcPos: 132 seqNb: 3 + repeat offset: 1 + LL: 0 OF: 69 ML: 3 srcPos: 135 seqNb: 4 + LL: 1 OF: 75 ML: 3 srcPos: 139 seqNb: 5 + excess literals: 3 srcPos: 142 + LL type: 2 OF type: 2 ML type: 0 + number of sequences: 6 + block type: compressed + block size field: 60 + checksum: 29af0a62 diff --git a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst new file mode 100644 index 0000000000000000000000000000000000000000..884f540c77e2e215f227606c3a7c765a36e17064 GIT binary patch literal 140 zcmdPcs{gk|tB-+!;Rz#yS_@B~AOr|-GJxRI1Jl!v9SCQT3|0SaF)!<*wzSPpf%m${ zRwM`y literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.log b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.log new file mode 100644 index 0000000000..6f5e6bf708 --- /dev/null +++ b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.log @@ -0,0 +1,81 @@ +seed: 400061 +frame seed: 400061 + frame content size: 241 + frame window size: 2048 + content size flag: 1 + single segment flag: 0 + block: + block content size: 60 + last block: no + compressed block: + raw literals + literals size: 60 + total match lengths: 0 + excess literals: 60 srcPos: 60 + number of sequences: 0 + block type: compressed + block size field: 63 + block: + block content size: 91 + last block: no + compressed block: + compressed literals + small range literals + huffman log: 10 + regenerated size: 44 + compressed size: 39 + literals size: 44 + total match lengths: 47 + LL: 1 OF: 40 ML: 3 srcPos: 64 seqNb: 0 + LL: 0 OF: 12 ML: 3 srcPos: 67 seqNb: 1 + LL: 2 OF: 13 ML: 3 srcPos: 72 seqNb: 2 + LL: 0 OF: 70 ML: 3 srcPos: 75 seqNb: 3 + LL: 8 OF: 71 ML: 3 srcPos: 86 seqNb: 4 + LL: 17 OF: 17 ML: 3 srcPos: 106 seqNb: 5 + LL: 0 OF: 39 ML: 3 srcPos: 109 seqNb: 6 + LL: 0 OF: 56 ML: 3 srcPos: 112 seqNb: 7 + LL: 1 OF: 78 ML: 5 srcPos: 118 seqNb: 8 + LL: 1 OF: 48 ML: 3 srcPos: 122 seqNb: 9 + LL: 0 OF: 122 ML: 3 srcPos: 125 seqNb: 10 + LL: 1 OF: 1 ML: 3 srcPos: 129 seqNb: 11 + LL: 7 OF: 31 ML: 4 srcPos: 140 seqNb: 12 + LL: 3 OF: 19 ML: 5 srcPos: 148 seqNb: 13 + excess literals: 3 srcPos: 151 + LL type: 2 OF type: 0 ML type: 2 + number of sequences: 14 + block type: compressed + block size field: 74 + block: + block content size: 11 + last block: no + compressed block: + rle literals: 0x9c + literals size: 8 + total match lengths: 3 + LL: 4 OF: 1 ML: 3 srcPos: 158 seqNb: 0 + repeat offset: 2 + excess literals: 4 srcPos: 162 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 8 + block: + block content size: 79 + last block: yes + compressed block: + compressed literals + huffman repeat stats + regenerated size: 57 + compressed size: 37 + literals size: 57 + total match lengths: 22 + LL: 22 OF: 18 ML: 3 srcPos: 187 seqNb: 0 + LL: 7 OF: 168 ML: 5 srcPos: 199 seqNb: 1 + LL: 28 OF: 48 ML: 5 srcPos: 232 seqNb: 2 + LL: 0 OF: 176 ML: 9 srcPos: 241 seqNb: 3 + excess literals: 0 srcPos: 241 + LL type: 2 OF type: 0 ML type: 2 + number of sequences: 4 + block type: compressed + block size field: 69 + checksum: 1d05f549 diff --git a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst new file mode 100644 index 0000000000000000000000000000000000000000..b0fe4849b33e414c3f433a607ba487e6d9d3ac45 GIT binary patch literal 244 zcmdPcs{i*0$43ST_`}F>g!xjNTiEi28&i!Jzum35@8Qy_?Cp09l1{rE`u}(4%Uj!S zZE;c#b$fVoj%U<YWcpoa_xNa^A+Eeg1;EnQ$xD{r2Ve0yl(u1xf{eU}5)(k8r)} z!oc7-hcSeOk%3v4iD9}kCkF!vF8m&;v7ckT)PjG7UK?B{$xXI-7P5ElKJma!6AC6S oXJqLR(MVus{lm)kgIz?UVL#)4Mq}n>X{kZHGgz5Czp}~#0A!C~PXGV_ literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.log b/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.log new file mode 100644 index 0000000000..9962123a00 --- /dev/null +++ b/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.log @@ -0,0 +1,53 @@ +seed: 403927 +frame seed: 403927 + frame content size: 224 + frame window size: 229376 + content size flag: 1 + single segment flag: 0 + block: + block content size: 100 + last block: no + compressed block: + compressed literals + distribution weight: 46% + huffman log: 8 + regenerated size: 67 + compressed size: 56 + literals size: 67 + total match lengths: 33 + LL: 67 OF: 46 ML: 33 srcPos: 100 seqNb: 0 + excess literals: 0 srcPos: 100 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 68 + block: + block content size: 23 + last block: no + compressed block: + rle literals: 0x46 + literals size: 14 + total match lengths: 9 + LL: 14 OF: 27 ML: 9 srcPos: 123 seqNb: 0 + excess literals: 0 srcPos: 123 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 8 + block: + block content size: 101 + last block: yes + compressed block: + compressed literals + huffman repeat stats + regenerated size: 96 + compressed size: 29 + literals size: 96 + total match lengths: 5 + LL: 0 OF: 4 ML: 5 srcPos: 128 seqNb: 0 + excess literals: 96 srcPos: 224 + LL type: 1 OF type: 1 ML type: 1 + number of sequences: 1 + block type: compressed + block size field: 38 + checksum: d06e892b diff --git a/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.zst b/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.zst new file mode 100644 index 0000000000000000000000000000000000000000..d5cfe6c706ba9b984dbb80eb547409ab0c657773 GIT binary patch literal 137 zcmV;40CxW;~PIEbq8P;~|)RcEED@K9BF`Vj^I z2LJ{DQ+IGAWC_bi4TLIG11E0-sbK*P2Vt`%1OZeT1s+2TL;wJBMgdd~1O^^80RRI= r2UU_#uF#0^gp^g{SLC4)DJ6zfw|q|#!&$3=5 Date: Wed, 23 Jul 2025 14:36:26 +0200 Subject: [PATCH 018/159] Collect P&R benchmarks Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 45 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 701d34f5f2..5e33488b98 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -15,7 +15,7 @@ # Build rules for XLS ZSTD codec implementation. # pytype binary, library -load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") +load("@rules_hdl//place_and_route:build_defs.bzl", "collect_benchmark_reports", "place_and_route") load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") load("@rules_hdl//verilog:providers.bzl", "verilog_library") load("@xls_pip_deps//:requirements.bzl", "requirement") @@ -3777,3 +3777,46 @@ xls_dslx_test( name = "refilling_shift_buffer_mux_dslx_test", library = ":refilling_shift_buffer_mux_dslx", ) + +collect_benchmark_reports( + name = "all_banchmark_reports", + srcs = [ + ":window_buffer_place_and_route", + ":shift_buffer_aligner_place_and_route", + ":shift_buffer_storage_place_and_route", + ":shift_buffer_place_and_route", + ":frame_header_dec_place_and_route", + ":block_header_dec_place_and_route", + ":raw_block_dec_place_and_route", + ":rle_block_dec_place_and_route", + ":dec_mux_place_and_route", + ":sequence_executor_place_and_route", + ":axi_csr_accessor_place_and_route", + ":csr_config_place_and_route", + ":ram_rw_handler_place_and_route", + ":fse_proba_freq_dec_place_and_route", + ":refilling_shift_buffer_internal_place_and_route", + ":zstd_dec_internal_place_and_route", + ":fse_table_iterator_place_and_route", + ":fse_table_creator_place_and_route", + ":command_constructor_place_and_route", + ":ram_demux_place_and_route", + ":ram_demux_naive_place_and_route", + ":fse_dec_place_and_route", + ":ram_demux3_place_and_route", + ":rle_literals_dec_place_and_route", + ":raw_literals_dec_place_and_route", + ":literals_buffer_place_and_route", + ":literals_decoder_ctrl_place_and_route", + # ":literals_decoder_place_and_route", + ":huffman_prescan_place_and_route", + # ":huffman_code_builder_place_and_route", + ":huffman_axi_reader_place_and_route", + # ":huffman_data_preprocessor_place_and_route", + # ":huffman_decoder_place_and_route", + ":huffman_ctrl_place_and_route", + ":huffman_weights_dec_place_and_route", + # ":huffman_literals_dec_place_and_route", + # ":zstd_dec_place_and_route", + ] +) From 6810b5d17cca001585d41fec5f4764ddb0db3c99 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 17 Jun 2025 10:41:35 +0200 Subject: [PATCH 019/159] Add fixed version of RamDemux together with a test Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 34 ++++- xls/modules/zstd/ram_demux_cocotb_test.py | 173 ++++++++++++++++++++++ xls/modules/zstd/rtl/BUILD | 3 +- xls/modules/zstd/rtl/ram_demux_wrapper.v | 155 +++++++++++++++++++ 4 files changed, 363 insertions(+), 2 deletions(-) create mode 100644 xls/modules/zstd/ram_demux_cocotb_test.py create mode 100644 xls/modules/zstd/rtl/ram_demux_wrapper.v diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 5e33488b98..b33d2c6cad 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -2171,6 +2171,7 @@ RAM_DEMUX_CODEGEN_ARGS = { "clock_period_ps": "750", "reset": "rst", "use_system_verilog": "false", + "streaming_channel_data_suffix": "_data", "materialize_internal_fifos": "true", } @@ -2247,6 +2248,37 @@ ram_demux_naive_codegen_args = { "materialize_internal_fifos": "true", } +py_test( + name = "ram_demux_cocotb_test", + srcs = ["ram_demux_cocotb_test.py"], + data = [ + "//xls/modules/zstd:ram_demux.v", + "//xls/modules/zstd/rtl:ram_1r1w.v", + "//xls/modules/zstd/rtl:ram_demux_wrapper.v", + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + env = { + "BUILD_WORKING_DIRECTORY": "sim_build", + "PYTHONUNBUFFERED": "1", + }, + imports = ["."], + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("cocotb"), + requirement("cocotbext-axi"), + requirement("pytest"), + requirement("zstandard"), + "//xls/common:runfiles", + "//xls/modules/zstd/cocotb:channel", + "//xls/modules/zstd/cocotb:data_generator", + "//xls/modules/zstd/cocotb:memory", + "//xls/modules/zstd/cocotb:utils", + "//xls/modules/zstd/cocotb:xlsstruct", + ], +) + xls_dslx_verilog( name = "ram_demux_naive_verilog", codegen_args = ram_demux_naive_codegen_args, @@ -3818,5 +3850,5 @@ collect_benchmark_reports( ":huffman_weights_dec_place_and_route", # ":huffman_literals_dec_place_and_route", # ":zstd_dec_place_and_route", - ] + ], ) diff --git a/xls/modules/zstd/ram_demux_cocotb_test.py b/xls/modules/zstd/ram_demux_cocotb_test.py new file mode 100644 index 0000000000..5e75c6abce --- /dev/null +++ b/xls/modules/zstd/ram_demux_cocotb_test.py @@ -0,0 +1,173 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import pathlib +import random +import sys +import warnings + +import cocotb +from cocotb.binary import BinaryValue +from cocotb.clock import Clock +from cocotb.triggers import Event, ClockCycles, RisingEdge +from cocotb_bus.scoreboard import Scoreboard +from cocotbext.axi import axi_channels +from cocotbext.axi.axi_ram import AxiRamRead +from cocotbext.axi.sparse_memory import SparseMemory + +import xls.modules.zstd.cocotb.channel as xlschannel +from xls.modules.zstd.cocotb import utils +from xls.modules.zstd.cocotb import xlsstruct + +warnings.filterwarnings("ignore", category=DeprecationWarning) + +ADDR_W = 10 +DATA_W = 64 +NUM_PARTITIONS = 64 +SEL_W = 1 + + +@xlsstruct.xls_dataclass +class SelReq(xlsstruct.XLSStruct): + sel: SEL_W + + +@xlsstruct.xls_dataclass +class ReadReq(xlsstruct.XLSStruct): + addr: ADDR_W + mask: NUM_PARTITIONS + + +@xlsstruct.xls_dataclass +class ReadResp(xlsstruct.XLSStruct): + data: DATA_W + + +@xlsstruct.xls_dataclass +class WriteReq(xlsstruct.XLSStruct): + addr: ADDR_W + data: DATA_W + mask: NUM_PARTITIONS + + +@xlsstruct.xls_dataclass +class WriteResp(xlsstruct.XLSStruct): + pass + + +def print_callback(name: str = "monitor"): + def _print_callback(transaction): + print(f" [{name}]: {transaction}") + + return _print_callback + + +def set_termination_event(monitor, event, transactions): + def terminate_cb(_): + if monitor.stats.received_transactions == transactions: + print("all transactions received") + event.set() + + monitor.add_callback(terminate_cb) + + +@cocotb.test(timeout_time=500, timeout_unit="ms") +async def test_mem_reader(dut): + + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + sel_resp_channel = xlschannel.XLSChannel(dut, "ram_demux__sel_resp_s", dut.clk) + rd_resp_channel = xlschannel.XLSChannel(dut, "ram_demux__rd_resp_s", dut.clk) + wr_resp_channel = xlschannel.XLSChannel( + dut, "ram_demux__wr_resp_s", dut.clk, start_now=True + ) + + sel_driver = xlschannel.XLSChannelDriver(dut, "ram_demux__sel_req_r", dut.clk) + rd_req_driver = xlschannel.XLSChannelDriver(dut, "ram_demux__rd_req_r", dut.clk) + wr_req_driver = xlschannel.XLSChannelDriver(dut, "ram_demux__wr_req_r", dut.clk) + + dut.rst.setimmediatevalue(0) + await ClockCycles(dut.clk, 10) + dut.rst.setimmediatevalue(1) + await ClockCycles(dut.clk, 10) + dut.rst.setimmediatevalue(0) + + sel_resp_channel.rdy.setimmediatevalue(1) + rd_resp_channel.rdy.setimmediatevalue(1) + wr_resp_channel.rdy.setimmediatevalue(1) + + await sel_driver.send(SelReq(0)) + while True: + await RisingEdge(dut.clk) + if sel_resp_channel.rdy.value and sel_resp_channel.vld.value: + break + + await wr_req_driver.send(WriteReq(addr=123, data=0x10, mask=0xFFFF_FFFF_FFFF_FFFF)) + while True: + await RisingEdge(dut.clk) + if wr_resp_channel.rdy.value and wr_resp_channel.vld.value: + break + + await sel_driver.send(SelReq(1)) + while True: + await RisingEdge(dut.clk) + if sel_resp_channel.rdy.value and sel_resp_channel.vld.value: + break + + await wr_req_driver.send(WriteReq(addr=256, data=0x3, mask=0xFFFF_FFFF_FFFF_FFFF)) + while True: + await RisingEdge(dut.clk) + if wr_resp_channel.rdy.value and wr_resp_channel.vld.value: + break + + await sel_driver.send(SelReq(0)) + while True: + await RisingEdge(dut.clk) + if sel_resp_channel.rdy.value and sel_resp_channel.vld.value: + break + + await rd_req_driver.send(ReadReq(addr=123, mask=0xFFFF_FFFF_FFFF_FFFF)) + while True: + await RisingEdge(dut.clk) + if rd_resp_channel.rdy.value and rd_resp_channel.vld.value: + assert rd_resp_channel.data.value == 0x10 + break + + await sel_driver.send(SelReq(1)) + while True: + await RisingEdge(dut.clk) + if sel_resp_channel.rdy.value and sel_resp_channel.vld.value: + break + + await rd_req_driver.send(ReadReq(addr=256, mask=0xFFFF_FFFF_FFFF_FFFF)) + while True: + await RisingEdge(dut.clk) + if rd_resp_channel.rdy.value and rd_resp_channel.vld.value: + assert rd_resp_channel.data.value == 0x3 + break + + +if __name__ == "__main__": + sys.path.append(str(pathlib.Path(__file__).parent)) + + toplevel = "ram_demux_wrapper" + verilog_sources = [ + "xls/modules/zstd/ram_demux.v", + "xls/modules/zstd/rtl/ram_1r1w.v", + "xls/modules/zstd/rtl/ram_demux_wrapper.v", + ] + test_module = [pathlib.Path(__file__).stem] + utils.run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/rtl/BUILD b/xls/modules/zstd/rtl/BUILD index 3816de3b42..1a6c5f1592 100644 --- a/xls/modules/zstd/rtl/BUILD +++ b/xls/modules/zstd/rtl/BUILD @@ -36,7 +36,8 @@ exports_files( [ "zstd_dec_wrapper.sv", "xls_fifo_wrapper.sv", - "ram_1r1w.v" + "ram_demux_wrapper.v", + "ram_1r1w.v", ], ) diff --git a/xls/modules/zstd/rtl/ram_demux_wrapper.v b/xls/modules/zstd/rtl/ram_demux_wrapper.v new file mode 100644 index 0000000000..973b0b18a4 --- /dev/null +++ b/xls/modules/zstd/rtl/ram_demux_wrapper.v @@ -0,0 +1,155 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module ram_demux_wrapper #( + parameter DATA_WIDTH = 64, + parameter SIZE = 1024, + parameter ADDR_WIDTH = $clog2(SIZE), + parameter NUM_PARTITIONS = 64 +) ( + input wire clk, + input wire rst, + + input wire ram_demux__sel_req_r_data, + input wire ram_demux__sel_req_r_vld, + output wire ram_demux__sel_req_r_rdy, + + input wire ram_demux__sel_resp_s_rdy, + output wire ram_demux__sel_resp_s_vld, + + input wire [ NUM_PARTITIONS + ADDR_WIDTH - 1:0 ] ram_demux__rd_req_r_data, + input wire ram_demux__rd_req_r_vld, + output wire ram_demux__rd_req_r_rdy, + + output wire [ DATA_WIDTH -1:0 ] ram_demux__rd_resp_s_data, + output wire ram_demux__rd_resp_s_vld, + input wire ram_demux__rd_resp_s_rdy, + + input wire [ DATA_WIDTH + NUM_PARTITIONS + ADDR_WIDTH -1:0 ] ram_demux__wr_req_r_data, + input wire ram_demux__wr_req_r_vld, + output wire ram_demux__wr_req_r_rdy, + + input wire ram_demux__wr_resp_s_rdy, + output wire ram_demux__wr_resp_s_vld +); + +wire [ DATA_WIDTH -1:0 ] ram0_wr_data; +wire [ ADDR_WIDTH -1:0 ] ram0_wr_addr; +wire ram0_wr_en; +wire [ NUM_PARTITIONS -1:0 ] ram0_wr_mask; + +wire [ DATA_WIDTH -1:0 ] ram0_rd_data; +wire [ ADDR_WIDTH -1:0 ] ram0_rd_addr; +wire ram0_rd_en; +wire [ NUM_PARTITIONS -1:0 ] ram0_rd_mask; + +ram_1r1w # ( + .DATA_WIDTH(DATA_WIDTH), + .SIZE(SIZE), + .NUM_PARTITIONS(NUM_PARTITIONS), + .ADDR_WIDTH(ADDR_WIDTH) +) ram0 ( + .clk (clk), + .rst (rst), + + .wr_data (ram0_wr_data), + .wr_addr (ram0_wr_addr), + .wr_en (ram0_wr_en), + .wr_mask (ram0_wr_mask), + + .rd_data (ram0_rd_data), + .rd_addr (ram0_rd_addr), + .rd_en (ram0_rd_en), + .rd_mask (ram0_rd_mask) +); + +wire [ DATA_WIDTH -1:0 ] ram1_wr_data; +wire [ ADDR_WIDTH -1:0 ] ram1_wr_addr; +wire ram1_wr_en; +wire [ NUM_PARTITIONS -1:0 ] ram1_wr_mask; + +wire [ DATA_WIDTH -1:0 ] ram1_rd_data; +wire [ ADDR_WIDTH -1:0 ] ram1_rd_addr; +wire ram1_rd_en; +wire [ NUM_PARTITIONS -1:0 ] ram1_rd_mask; + + +ram_1r1w # ( + .DATA_WIDTH(DATA_WIDTH), + .SIZE(SIZE), + .NUM_PARTITIONS(NUM_PARTITIONS), + .ADDR_WIDTH(ADDR_WIDTH) +) ram1 ( + .clk (clk), + .rst (rst), + + .wr_data (ram1_wr_data), + .wr_addr (ram1_wr_addr), + .wr_en (ram1_wr_en), + .wr_mask (ram1_wr_mask), + + .rd_data (ram1_rd_data), + .rd_addr (ram1_rd_addr), + .rd_en (ram1_rd_en), + .rd_mask (ram1_rd_mask) +); + +RamDemux demux ( + .clk(clk), + .rst(rst), + + .ram_demux__rd_req_r_data(ram_demux__rd_req_r_data), + .ram_demux__rd_req_r_vld(ram_demux__rd_req_r_vld), + .ram_demux__rd_req_r_rdy(ram_demux__rd_req_r_rdy), + + .ram_demux__sel_req_r_data(ram_demux__sel_req_r_data), + .ram_demux__sel_req_r_rdy(ram_demux__sel_req_r_rdy), + .ram_demux__sel_req_r_vld(ram_demux__sel_req_r_vld), + + .ram_demux__sel_resp_s_rdy(ram_demux__sel_resp_s_rdy), + .ram_demux__sel_resp_s_vld(ram_demux__sel_resp_s_vld), + + .ram_demux__wr_req_r_vld(ram_demux__wr_req_r_vld), + .ram_demux__wr_req_r_rdy(ram_demux__wr_req_r_rdy), + .ram_demux__wr_req_r_data(ram_demux__wr_req_r_data), + + .ram_demux__wr_resp_s_rdy(ram_demux__wr_resp_s_rdy), + .ram_demux__wr_resp_s_vld(ram_demux__wr_resp_s_vld), + + .ram_demux__rd_resp_s_rdy(ram_demux__rd_resp_s_rdy), + .ram_demux__rd_resp_s_data(ram_demux__rd_resp_s_data), + .ram_demux__rd_resp_s_vld(ram_demux__rd_resp_s_vld), + + .ram0_rd_data (ram0_rd_data), + .ram0_rd_addr (ram0_rd_addr), + .ram0_rd_mask (ram0_rd_mask), + .ram0_rd_en (ram0_rd_en), + + .ram0_wr_addr (ram0_wr_addr), + .ram0_wr_data (ram0_wr_data), + .ram0_wr_mask (ram0_wr_mask), + .ram0_wr_en (ram0_wr_en), + + .ram1_rd_data (ram1_rd_data), + .ram1_rd_addr (ram1_rd_addr), + .ram1_rd_mask (ram1_rd_mask), + .ram1_rd_en (ram1_rd_en), + + .ram1_wr_addr (ram1_wr_addr), + .ram1_wr_data (ram1_wr_data), + .ram1_wr_mask (ram1_wr_mask), + .ram1_wr_en (ram1_wr_en) +); + +endmodule From 5e0701ae02cfcef59307e0a534d53f3e8bf8e92b Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Mon, 21 Jul 2025 17:53:10 +0200 Subject: [PATCH 020/159] Document decoder testing --- xls/modules/zstd/README.md | 83 +++++++++++++++++++++++++++++++ xls/modules/zstd/memory/README.md | 41 +++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index fb079592e3..aae9a03c8c 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -452,6 +452,9 @@ results against the decoding of the reference library. Currently, due to the restrictions from the ZSTD frame generator, it is possible to test only the positive cases (decoding valid ZSTD frames). +Verilog tests are written in Python as +[cocotb](https://github.com/cocotb/cocotb) testbench. + ZstdDecoder's main communication interfaces are the AXI buses. Due to the way XLS handles the codegen of DSLX channels that model the AXI channels, the particular ports of the AXI channels are not represented correctly. This @@ -465,6 +468,31 @@ Crossbar](https://github.com/alexforencich/verilog-axi). ![diagram of interfaces of decoder and its wrapper](img/ZSTD_decoder_wrapper.png) +**Figure: Zstd decoder wrapper connection diagram.** + +Cocotb testbench interacts with the decoder with the help of a +[cocotbext-axi](https://github.com/alexforencich/cocotbext-axi) extension that +provides AXI bus models, drivers, monitors and RAM model accessible through AXI +interface. Cocotb AXI Manager is connected to the decoder's `CSR Interface` and +is used to simulate the software's interaction with the decoder. + +The Basic test case for the ZstdDecoder is composed of the following steps: + +1. The testbench generates a ZSTD frame using the + [decodecorpus](https://github.com/facebook/zstd/blob/dev/tests/decodecorpus.c) + utility from the [zstd reference library](https://github.com/facebook/zstd). +2. The encoded frame is placed in an AXI RAM model that is connected to the + decoder's `Memory Interface`. +3. The encoded frame is decoded with the zstd reference library and the results + are represented in the decoder's output format as the expected data from the + simulation. +4. AXI Manager performs a series of writes to the ZstdDecoder CSRs to configure + it and start the decoding process. +5. Testbench waits for the signal on the `Notify` channel and checks the output + of the decoder stored in the memory against the expected output data. +6. Test case succeeds once `Notify` is asserted, all expected data is received + and the decoder lands in `IDLE` state with status `OKAY` in the `Status` CSR. + ### Failure points #### User-facing decoder errors @@ -537,3 +565,58 @@ This is done for example in: * Frame header decoder * SequenceExecutor + +### Testing against [libzstd](https://github.com/facebook/zstd) + +Design is verified by comparing decoding results to the reference library +`libzstd`. ZSTD frames used for testing are generated with +[decodecorpus](https://github.com/facebook/zstd/blob/dev/tests/decodecorpus.c) +utility. The generated frame is then decoded with `libzstd` and with simulated +`ZstdDecoder`. + +#### Positive test cases + +If the results of decoding with `libzstd` are valid, the test runs the same +encoded frame through the simulation of DSLX design. The output of the +simulation is gathered and compared with the results of `libzstd` in terms of +its size and contents. + +Encoded ZSTD frame is generated with the function `GenerateFrame(seed, btype, +output_path)` from +[data_generator](https://github.com/antmicro/xls/blob/main/xls/modules/zstd/cocotb/data_generator.py) +library. This function takes as arguments the seed for the generator, an enum +that codes the type of blocks that should be generated in a given frame and the +output path to write the generated frame into a file. The available block types +are: + +* RAW +* RLE +* COMPRESSED +* RANDOM + +The function returns a vector of bytes representing a valid encoded ZSTD frame. +Such generated frame can be passed to DSLX and cocotb testbenches to be decoded +in the simulation and compared against the results from the reference library. + +Verilog tests are available in the `zstd_dec_cocotb_test.py` file and can be +launched with the following Bazel command: + +```shell +bazel run -c opt -- //xls/modules/zstd:zstd_dec_cocotb_test --logtostderr +``` + +#### Negative test cases + +Currently, `decodecorpus` does not support generating ZSTD frames with subtle +errors that trigger failure points provided in the ZSTD Decoder. Because of +that, it is not possible to efficiently provide valuable negative tests for the +integrated ZSTD Decoder. + +The alternatives for writing negative tests include: + +* Generating a well-known valid ZSTD frame from a specific generator seed and +then tweaking the raw bits in this frame to trigger the error response from the +decoder + +[^1]: `CompressedBlockDecoder` is to be added in follow-up PRs. +[^2]: Checksum verification is currently unsupported. diff --git a/xls/modules/zstd/memory/README.md b/xls/modules/zstd/memory/README.md index 37c20c45a3..ad7d874218 100644 --- a/xls/modules/zstd/memory/README.md +++ b/xls/modules/zstd/memory/README.md @@ -89,3 +89,44 @@ The list below shows the usage of the `MemWriter` proc: 3. Wait for the response submitted on the `resp_s` channel, which indicates if the write operation was successful or an error occurred. + +## Cocotb Simulation + +This directory also contains Verilog simulations of the created modules, +which test their interaction with RAM attached to the AXI bus. These Verilog +simulations provide insight into the design's latency and achievable throughput. + +The simulation interacts with verilog file generated from the particular DSLX +proc through a verilog wrapper. The wrapper is used to create an interface that +is compliant with the AXI specification so that the cocotb testbench can +interact with the DUT with the help of an extension tailored for handling the +AXI bus. + +### Usage + +1. Run the simulation with the following command: + +``` +bazel run -c opt //xls/modules/zstd/memory:_cocotb_test -- --logtostderr +``` + +2. Observe simulation results, e.g. for `mem_writer_cocotb_test`: + +``` +************************************************************************************************************************************************************* +** TEST STATUS SIM TIME (ns) REAL TIME (s) RATIO (ns/s) ** +************************************************************************************************************************************************************* +** mem_writer_cocotb_test.ram_test_single_burst_1_transfer PASS 1970000.00 0.05 40004933.01 ** +** mem_writer_cocotb_test.ram_test_single_burst_2_transfers PASS 2140000.00 0.04 52208013.80 ** +** mem_writer_cocotb_test.ram_test_single_burst_almost_max_burst_transfer PASS 42620000.00 1.00 42734572.11 ** +** mem_writer_cocotb_test.ram_test_single_burst_max_burst_transfer PASS 43380000.00 1.03 42245987.95 ** +** mem_writer_cocotb_test.ram_test_multiburst_2_full_bursts PASS 85940000.00 2.00 42978720.13 ** +** mem_writer_cocotb_test.ram_test_multiburst_1_full_burst_and_single_transfer PASS 44510000.00 1.02 43487911.16 ** +** mem_writer_cocotb_test.ram_test_multiburst_crossing_4kb_boundary PASS 3740000.00 0.06 60190612.91 ** +** mem_writer_cocotb_test.ram_test_multiburst_crossing_4kb_boundary_with_perfectly_aligned_full_bursts PASS 21440000.00 0.50 42469371.00 ** +** mem_writer_cocotb_test.ram_test_multiburst_crossing_4kb_boundary_with_2_full_bursts_and_1_transfer PASS 87070000.00 2.01 43348812.05 ** +** mem_writer_cocotb_test.ram_test_random PASS 4491230000.00 109.05 41184670.96 ** +************************************************************************************************************************************************************* +** TESTS=10 PASS=10 FAIL=0 SKIP=0 4824040000.01 116.82 41296261.92 ** +************************************************************************************************************************************************************* +``` From 4687a490eef5d713419d28ab97a9ec1280a8cb57 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 23 Apr 2025 22:30:39 +0200 Subject: [PATCH 021/159] dependency_support: Add verilog_axi package Signed-off-by: Robert Winkler --- .../BUILD.bazel | 15 +++++++ .../bundled.BUILD.bazel | 45 +++++++++++++++++++ dependency_support/load_external.bzl | 8 ++++ 3 files changed, 68 insertions(+) create mode 100644 dependency_support/com_github_alexforencich_verilog_axi/BUILD.bazel create mode 100644 dependency_support/com_github_alexforencich_verilog_axi/bundled.BUILD.bazel diff --git a/dependency_support/com_github_alexforencich_verilog_axi/BUILD.bazel b/dependency_support/com_github_alexforencich_verilog_axi/BUILD.bazel new file mode 100644 index 0000000000..4a24c1372f --- /dev/null +++ b/dependency_support/com_github_alexforencich_verilog_axi/BUILD.bazel @@ -0,0 +1,15 @@ +# Copyright 2025 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Needed to make this a package. diff --git a/dependency_support/com_github_alexforencich_verilog_axi/bundled.BUILD.bazel b/dependency_support/com_github_alexforencich_verilog_axi/bundled.BUILD.bazel new file mode 100644 index 0000000000..296e2bc0f6 --- /dev/null +++ b/dependency_support/com_github_alexforencich_verilog_axi/bundled.BUILD.bazel @@ -0,0 +1,45 @@ +# Copyright 2025 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@xls_pip_deps//:requirements.bzl", "requirement") + +package(default_visibility = ["//visibility:public"]) + +exports_files( + glob(["rtl/*.v"]), +) + +py_binary( + name = "axi_crossbar_wrap", + srcs = ["rtl/axi_crossbar_wrap.py"], + deps = [requirement("Jinja2")], +) + +py_binary( + name = "axi_interconnect_wrap", + srcs = ["rtl/axi_interconnect_wrap.py"], + deps = [requirement("Jinja2")], +) + +py_binary( + name = "axil_crossbar_wrap", + srcs = ["rtl/axi_crossbar_wrap.py"], + deps = [requirement("Jinja2")], +) + +py_binary( + name = "axil_interconnect_wrap", + srcs = ["rtl/axil_interconnect_wrap.py"], + deps = [requirement("Jinja2")], +) diff --git a/dependency_support/load_external.bzl b/dependency_support/load_external.bzl index cdc9deebc8..91467b3d9a 100644 --- a/dependency_support/load_external.bzl +++ b/dependency_support/load_external.bzl @@ -80,3 +80,11 @@ def load_external_repositories(): urls = ["https://github.com/facebook/zstd/archive/fdfb2aff39dc498372d8c9e5f2330b692fea9794.zip"], build_file = Label("//dependency_support/com_github_facebook_zstd:bundled.BUILD.bazel"), ) + + http_archive( + name = "com_github_alexforencich_verilog_axi", + sha256 = "f3b58406b51950584cc7b0c67b0710cef10cb14e1f5576e97a2f0b1c0b12fcbe", + strip_prefix = "verilog-axi-516bd5dadc3365b7f9e225d2af8fe0b8d804fe53", + urls = ["https://github.com/alexforencich/verilog-axi/archive/516bd5dadc3365b7f9e225d2af8fe0b8d804fe53.zip"], + build_file = "//dependency_support/com_github_alexforencich_verilog_axi:bundled.BUILD.bazel", + ) From 112d0899c4aad55cd9ff3bd7cd829534fe5d96fe Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 23 Jul 2025 15:07:45 +0200 Subject: [PATCH 022/159] Use bazel package with verilog_axi Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 29 +- xls/modules/zstd/external/BUILD | 48 - xls/modules/zstd/external/LICENSE | 19 - xls/modules/zstd/external/arbiter.v | 159 --- xls/modules/zstd/external/axi_crossbar.v | 391 ------ xls/modules/zstd/external/axi_crossbar_addr.v | 418 ------ xls/modules/zstd/external/axi_crossbar_rd.v | 569 -------- xls/modules/zstd/external/axi_crossbar_wr.v | 678 --------- .../zstd/external/axi_crossbar_wrapper.v | 1246 ----------------- xls/modules/zstd/external/axi_register_rd.v | 530 ------- xls/modules/zstd/external/axi_register_wr.v | 691 --------- xls/modules/zstd/external/priority_encoder.v | 92 -- xls/modules/zstd/zstd_dec_cocotb_test.py | 18 +- 13 files changed, 28 insertions(+), 4860 deletions(-) delete mode 100644 xls/modules/zstd/external/BUILD delete mode 100644 xls/modules/zstd/external/LICENSE delete mode 100644 xls/modules/zstd/external/arbiter.v delete mode 100644 xls/modules/zstd/external/axi_crossbar.v delete mode 100644 xls/modules/zstd/external/axi_crossbar_addr.v delete mode 100644 xls/modules/zstd/external/axi_crossbar_rd.v delete mode 100644 xls/modules/zstd/external/axi_crossbar_wr.v delete mode 100644 xls/modules/zstd/external/axi_crossbar_wrapper.v delete mode 100644 xls/modules/zstd/external/axi_register_rd.v delete mode 100644 xls/modules/zstd/external/axi_register_wr.v delete mode 100644 xls/modules/zstd/external/priority_encoder.v diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index b33d2c6cad..546579e119 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1752,6 +1752,15 @@ filegroup( ], ) +genrule( + name = "axi_crossbar_wrapper", + outs = ["axi_crossbar_wrapper.v"], + cmd = "$(location @com_github_alexforencich_verilog_axi//:axi_crossbar_wrap) -p 15 1 -n axi_crossbar_wrapper -o $(OUTS)", + tools = [ + "@com_github_alexforencich_verilog_axi//:axi_crossbar_wrap", + ], +) + py_test( name = "zstd_dec_cocotb_test", srcs = ["zstd_dec_cocotb_test.py"], @@ -1795,20 +1804,20 @@ py_test( "data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst", "data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst", "data/treeless_huffman_literals_rle_sequences_seed_403927.zst", + ":axi_crossbar_wrapper.v", ":zstd_dec.v", - "//xls/modules/zstd:zstd_dec_xx_fse_default", - "//xls/modules/zstd/external:arbiter.v", - "//xls/modules/zstd/external:axi_crossbar.v", - "//xls/modules/zstd/external:axi_crossbar_addr.v", - "//xls/modules/zstd/external:axi_crossbar_rd.v", - "//xls/modules/zstd/external:axi_crossbar_wr.v", - "//xls/modules/zstd/external:axi_crossbar_wrapper.v", - "//xls/modules/zstd/external:axi_register_rd.v", - "//xls/modules/zstd/external:axi_register_wr.v", - "//xls/modules/zstd/external:priority_encoder.v", + ":zstd_dec_xx_fse_default", "//xls/modules/zstd/rtl:ram_1r1w.v", "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", "//xls/modules/zstd/rtl:zstd_dec_wrapper.sv", + "@com_github_alexforencich_verilog_axi//:rtl/arbiter.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_addr.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_rd.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_wr.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_register_rd.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_register_wr.v", + "@com_github_alexforencich_verilog_axi//:rtl/priority_encoder.v", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], diff --git a/xls/modules/zstd/external/BUILD b/xls/modules/zstd/external/BUILD deleted file mode 100644 index f0e3c97626..0000000000 --- a/xls/modules/zstd/external/BUILD +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2024 The XLS Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Collection of external RTL sources - -This package exports external verilog sources required by the ZSTD Decoder -in the verilog tests. - -The exported files come from the Open Source library of the AXI4 and AXI4 lite -bus components released under MIT license. - -The sources contain an implementation of the AXI4 crossbar which is used in the -verilog tests of the ZSTD Decoder to connect multiple AXI interfaces into a single -AXI interface that forms the IO of the ZSTD Decoder verilog wrapper used in the tests. - -Source: https://github.com/alexforencich/verilog-axi -""" - -package( - default_applicable_licenses = ["//:license"], - default_visibility = ["//xls:xls_users"], - licenses = ["notice"], -) - -exports_files( - [ - "arbiter.v", - "axi_crossbar.v", - "axi_crossbar_addr.v", - "axi_crossbar_rd.v", - "axi_crossbar_wr.v", - "axi_crossbar_wrapper.v", - "axi_register_rd.v", - "axi_register_wr.v", - "priority_encoder.v", - ], -) diff --git a/xls/modules/zstd/external/LICENSE b/xls/modules/zstd/external/LICENSE deleted file mode 100644 index 6923387c75..0000000000 --- a/xls/modules/zstd/external/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2018 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/xls/modules/zstd/external/arbiter.v b/xls/modules/zstd/external/arbiter.v deleted file mode 100644 index cfac70d1c6..0000000000 --- a/xls/modules/zstd/external/arbiter.v +++ /dev/null @@ -1,159 +0,0 @@ -/* - -Copyright (c) 2014-2021 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * Arbiter module - */ -module arbiter # -( - parameter PORTS = 4, - // select round robin arbitration - parameter ARB_TYPE_ROUND_ROBIN = 0, - // blocking arbiter enable - parameter ARB_BLOCK = 0, - // block on acknowledge assert when nonzero, request deassert when 0 - parameter ARB_BLOCK_ACK = 1, - // LSB priority selection - parameter ARB_LSB_HIGH_PRIORITY = 0 -) -( - input wire clk, - input wire rst, - - input wire [PORTS-1:0] request, - input wire [PORTS-1:0] acknowledge, - - output wire [PORTS-1:0] grant, - output wire grant_valid, - output wire [$clog2(PORTS)-1:0] grant_encoded -); - -reg [PORTS-1:0] grant_reg = 0, grant_next; -reg grant_valid_reg = 0, grant_valid_next; -reg [$clog2(PORTS)-1:0] grant_encoded_reg = 0, grant_encoded_next; - -assign grant_valid = grant_valid_reg; -assign grant = grant_reg; -assign grant_encoded = grant_encoded_reg; - -wire request_valid; -wire [$clog2(PORTS)-1:0] request_index; -wire [PORTS-1:0] request_mask; - -priority_encoder #( - .WIDTH(PORTS), - .LSB_HIGH_PRIORITY(ARB_LSB_HIGH_PRIORITY) -) -priority_encoder_inst ( - .input_unencoded(request), - .output_valid(request_valid), - .output_encoded(request_index), - .output_unencoded(request_mask) -); - -reg [PORTS-1:0] mask_reg = 0, mask_next; - -wire masked_request_valid; -wire [$clog2(PORTS)-1:0] masked_request_index; -wire [PORTS-1:0] masked_request_mask; - -priority_encoder #( - .WIDTH(PORTS), - .LSB_HIGH_PRIORITY(ARB_LSB_HIGH_PRIORITY) -) -priority_encoder_masked ( - .input_unencoded(request & mask_reg), - .output_valid(masked_request_valid), - .output_encoded(masked_request_index), - .output_unencoded(masked_request_mask) -); - -always @* begin - grant_next = 0; - grant_valid_next = 0; - grant_encoded_next = 0; - mask_next = mask_reg; - - if (ARB_BLOCK && !ARB_BLOCK_ACK && grant_reg & request) begin - // granted request still asserted; hold it - grant_valid_next = grant_valid_reg; - grant_next = grant_reg; - grant_encoded_next = grant_encoded_reg; - end else if (ARB_BLOCK && ARB_BLOCK_ACK && grant_valid && !(grant_reg & acknowledge)) begin - // granted request not yet acknowledged; hold it - grant_valid_next = grant_valid_reg; - grant_next = grant_reg; - grant_encoded_next = grant_encoded_reg; - end else if (request_valid) begin - if (ARB_TYPE_ROUND_ROBIN) begin - if (masked_request_valid) begin - grant_valid_next = 1; - grant_next = masked_request_mask; - grant_encoded_next = masked_request_index; - if (ARB_LSB_HIGH_PRIORITY) begin - mask_next = {PORTS{1'b1}} << (masked_request_index + 1); - end else begin - mask_next = {PORTS{1'b1}} >> (PORTS - masked_request_index); - end - end else begin - grant_valid_next = 1; - grant_next = request_mask; - grant_encoded_next = request_index; - if (ARB_LSB_HIGH_PRIORITY) begin - mask_next = {PORTS{1'b1}} << (request_index + 1); - end else begin - mask_next = {PORTS{1'b1}} >> (PORTS - request_index); - end - end - end else begin - grant_valid_next = 1; - grant_next = request_mask; - grant_encoded_next = request_index; - end - end -end - -always @(posedge clk) begin - if (rst) begin - grant_reg <= 0; - grant_valid_reg <= 0; - grant_encoded_reg <= 0; - mask_reg <= 0; - end else begin - grant_reg <= grant_next; - grant_valid_reg <= grant_valid_next; - grant_encoded_reg <= grant_encoded_next; - mask_reg <= mask_next; - end -end - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/axi_crossbar.v b/xls/modules/zstd/external/axi_crossbar.v deleted file mode 100644 index 991d45403a..0000000000 --- a/xls/modules/zstd/external/axi_crossbar.v +++ /dev/null @@ -1,391 +0,0 @@ -/* - -Copyright (c) 2018 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * AXI4 crossbar - */ -module axi_crossbar # -( - // Number of AXI inputs (slave interfaces) - parameter S_COUNT = 4, - // Number of AXI outputs (master interfaces) - parameter M_COUNT = 4, - // Width of data bus in bits - parameter DATA_WIDTH = 32, - // Width of address bus in bits - parameter ADDR_WIDTH = 32, - // Width of wstrb (width of data bus in words) - parameter STRB_WIDTH = (DATA_WIDTH/8), - // Input ID field width (from AXI masters) - parameter S_ID_WIDTH = 8, - // Output ID field width (towards AXI slaves) - // Additional bits required for response routing - parameter M_ID_WIDTH = S_ID_WIDTH+$clog2(S_COUNT), - // Propagate awuser signal - parameter AWUSER_ENABLE = 0, - // Width of awuser signal - parameter AWUSER_WIDTH = 1, - // Propagate wuser signal - parameter WUSER_ENABLE = 0, - // Width of wuser signal - parameter WUSER_WIDTH = 1, - // Propagate buser signal - parameter BUSER_ENABLE = 0, - // Width of buser signal - parameter BUSER_WIDTH = 1, - // Propagate aruser signal - parameter ARUSER_ENABLE = 0, - // Width of aruser signal - parameter ARUSER_WIDTH = 1, - // Propagate ruser signal - parameter RUSER_ENABLE = 0, - // Width of ruser signal - parameter RUSER_WIDTH = 1, - // Number of concurrent unique IDs for each slave interface - // S_COUNT concatenated fields of 32 bits - parameter S_THREADS = {S_COUNT{32'd2}}, - // Number of concurrent operations for each slave interface - // S_COUNT concatenated fields of 32 bits - parameter S_ACCEPT = {S_COUNT{32'd16}}, - // Number of regions per master interface - parameter M_REGIONS = 1, - // Master interface base addresses - // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits - // set to zero for default addressing based on M_ADDR_WIDTH - parameter M_BASE_ADDR = 0, - // Master interface address widths - // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits - parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, - // Read connections between interfaces - // M_COUNT concatenated fields of S_COUNT bits - parameter M_CONNECT_READ = {M_COUNT{{S_COUNT{1'b1}}}}, - // Write connections between interfaces - // M_COUNT concatenated fields of S_COUNT bits - parameter M_CONNECT_WRITE = {M_COUNT{{S_COUNT{1'b1}}}}, - // Number of concurrent operations for each master interface - // M_COUNT concatenated fields of 32 bits - parameter M_ISSUE = {M_COUNT{32'd4}}, - // Secure master (fail operations based on awprot/arprot) - // M_COUNT bits - parameter M_SECURE = {M_COUNT{1'b0}}, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_AW_REG_TYPE = {S_COUNT{2'd0}}, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_W_REG_TYPE = {S_COUNT{2'd0}}, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_B_REG_TYPE = {S_COUNT{2'd1}}, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_AR_REG_TYPE = {S_COUNT{2'd0}}, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_R_REG_TYPE = {S_COUNT{2'd2}}, - // Master interface AW channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_AW_REG_TYPE = {M_COUNT{2'd1}}, - // Master interface W channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_W_REG_TYPE = {M_COUNT{2'd2}}, - // Master interface B channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_B_REG_TYPE = {M_COUNT{2'd0}}, - // Master interface AR channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_AR_REG_TYPE = {M_COUNT{2'd1}}, - // Master interface R channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_R_REG_TYPE = {M_COUNT{2'd0}} -) -( - input wire clk, - input wire rst, - - /* - * AXI slave interfaces - */ - input wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_awid, - input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_awaddr, - input wire [S_COUNT*8-1:0] s_axi_awlen, - input wire [S_COUNT*3-1:0] s_axi_awsize, - input wire [S_COUNT*2-1:0] s_axi_awburst, - input wire [S_COUNT-1:0] s_axi_awlock, - input wire [S_COUNT*4-1:0] s_axi_awcache, - input wire [S_COUNT*3-1:0] s_axi_awprot, - input wire [S_COUNT*4-1:0] s_axi_awqos, - input wire [S_COUNT*AWUSER_WIDTH-1:0] s_axi_awuser, - input wire [S_COUNT-1:0] s_axi_awvalid, - output wire [S_COUNT-1:0] s_axi_awready, - input wire [S_COUNT*DATA_WIDTH-1:0] s_axi_wdata, - input wire [S_COUNT*STRB_WIDTH-1:0] s_axi_wstrb, - input wire [S_COUNT-1:0] s_axi_wlast, - input wire [S_COUNT*WUSER_WIDTH-1:0] s_axi_wuser, - input wire [S_COUNT-1:0] s_axi_wvalid, - output wire [S_COUNT-1:0] s_axi_wready, - output wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_bid, - output wire [S_COUNT*2-1:0] s_axi_bresp, - output wire [S_COUNT*BUSER_WIDTH-1:0] s_axi_buser, - output wire [S_COUNT-1:0] s_axi_bvalid, - input wire [S_COUNT-1:0] s_axi_bready, - input wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_arid, - input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_araddr, - input wire [S_COUNT*8-1:0] s_axi_arlen, - input wire [S_COUNT*3-1:0] s_axi_arsize, - input wire [S_COUNT*2-1:0] s_axi_arburst, - input wire [S_COUNT-1:0] s_axi_arlock, - input wire [S_COUNT*4-1:0] s_axi_arcache, - input wire [S_COUNT*3-1:0] s_axi_arprot, - input wire [S_COUNT*4-1:0] s_axi_arqos, - input wire [S_COUNT*ARUSER_WIDTH-1:0] s_axi_aruser, - input wire [S_COUNT-1:0] s_axi_arvalid, - output wire [S_COUNT-1:0] s_axi_arready, - output wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_rid, - output wire [S_COUNT*DATA_WIDTH-1:0] s_axi_rdata, - output wire [S_COUNT*2-1:0] s_axi_rresp, - output wire [S_COUNT-1:0] s_axi_rlast, - output wire [S_COUNT*RUSER_WIDTH-1:0] s_axi_ruser, - output wire [S_COUNT-1:0] s_axi_rvalid, - input wire [S_COUNT-1:0] s_axi_rready, - - /* - * AXI master interfaces - */ - output wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_awid, - output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_awaddr, - output wire [M_COUNT*8-1:0] m_axi_awlen, - output wire [M_COUNT*3-1:0] m_axi_awsize, - output wire [M_COUNT*2-1:0] m_axi_awburst, - output wire [M_COUNT-1:0] m_axi_awlock, - output wire [M_COUNT*4-1:0] m_axi_awcache, - output wire [M_COUNT*3-1:0] m_axi_awprot, - output wire [M_COUNT*4-1:0] m_axi_awqos, - output wire [M_COUNT*4-1:0] m_axi_awregion, - output wire [M_COUNT*AWUSER_WIDTH-1:0] m_axi_awuser, - output wire [M_COUNT-1:0] m_axi_awvalid, - input wire [M_COUNT-1:0] m_axi_awready, - output wire [M_COUNT*DATA_WIDTH-1:0] m_axi_wdata, - output wire [M_COUNT*STRB_WIDTH-1:0] m_axi_wstrb, - output wire [M_COUNT-1:0] m_axi_wlast, - output wire [M_COUNT*WUSER_WIDTH-1:0] m_axi_wuser, - output wire [M_COUNT-1:0] m_axi_wvalid, - input wire [M_COUNT-1:0] m_axi_wready, - input wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_bid, - input wire [M_COUNT*2-1:0] m_axi_bresp, - input wire [M_COUNT*BUSER_WIDTH-1:0] m_axi_buser, - input wire [M_COUNT-1:0] m_axi_bvalid, - output wire [M_COUNT-1:0] m_axi_bready, - output wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_arid, - output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_araddr, - output wire [M_COUNT*8-1:0] m_axi_arlen, - output wire [M_COUNT*3-1:0] m_axi_arsize, - output wire [M_COUNT*2-1:0] m_axi_arburst, - output wire [M_COUNT-1:0] m_axi_arlock, - output wire [M_COUNT*4-1:0] m_axi_arcache, - output wire [M_COUNT*3-1:0] m_axi_arprot, - output wire [M_COUNT*4-1:0] m_axi_arqos, - output wire [M_COUNT*4-1:0] m_axi_arregion, - output wire [M_COUNT*ARUSER_WIDTH-1:0] m_axi_aruser, - output wire [M_COUNT-1:0] m_axi_arvalid, - input wire [M_COUNT-1:0] m_axi_arready, - input wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_rid, - input wire [M_COUNT*DATA_WIDTH-1:0] m_axi_rdata, - input wire [M_COUNT*2-1:0] m_axi_rresp, - input wire [M_COUNT-1:0] m_axi_rlast, - input wire [M_COUNT*RUSER_WIDTH-1:0] m_axi_ruser, - input wire [M_COUNT-1:0] m_axi_rvalid, - output wire [M_COUNT-1:0] m_axi_rready -); - -axi_crossbar_wr #( - .S_COUNT(S_COUNT), - .M_COUNT(M_COUNT), - .DATA_WIDTH(DATA_WIDTH), - .ADDR_WIDTH(ADDR_WIDTH), - .STRB_WIDTH(STRB_WIDTH), - .S_ID_WIDTH(S_ID_WIDTH), - .M_ID_WIDTH(M_ID_WIDTH), - .AWUSER_ENABLE(AWUSER_ENABLE), - .AWUSER_WIDTH(AWUSER_WIDTH), - .WUSER_ENABLE(WUSER_ENABLE), - .WUSER_WIDTH(WUSER_WIDTH), - .BUSER_ENABLE(BUSER_ENABLE), - .BUSER_WIDTH(BUSER_WIDTH), - .S_THREADS(S_THREADS), - .S_ACCEPT(S_ACCEPT), - .M_REGIONS(M_REGIONS), - .M_BASE_ADDR(M_BASE_ADDR), - .M_ADDR_WIDTH(M_ADDR_WIDTH), - .M_CONNECT(M_CONNECT_WRITE), - .M_ISSUE(M_ISSUE), - .M_SECURE(M_SECURE), - .S_AW_REG_TYPE(S_AW_REG_TYPE), - .S_W_REG_TYPE (S_W_REG_TYPE), - .S_B_REG_TYPE (S_B_REG_TYPE) -) -axi_crossbar_wr_inst ( - .clk(clk), - .rst(rst), - - /* - * AXI slave interfaces - */ - .s_axi_awid(s_axi_awid), - .s_axi_awaddr(s_axi_awaddr), - .s_axi_awlen(s_axi_awlen), - .s_axi_awsize(s_axi_awsize), - .s_axi_awburst(s_axi_awburst), - .s_axi_awlock(s_axi_awlock), - .s_axi_awcache(s_axi_awcache), - .s_axi_awprot(s_axi_awprot), - .s_axi_awqos(s_axi_awqos), - .s_axi_awuser(s_axi_awuser), - .s_axi_awvalid(s_axi_awvalid), - .s_axi_awready(s_axi_awready), - .s_axi_wdata(s_axi_wdata), - .s_axi_wstrb(s_axi_wstrb), - .s_axi_wlast(s_axi_wlast), - .s_axi_wuser(s_axi_wuser), - .s_axi_wvalid(s_axi_wvalid), - .s_axi_wready(s_axi_wready), - .s_axi_bid(s_axi_bid), - .s_axi_bresp(s_axi_bresp), - .s_axi_buser(s_axi_buser), - .s_axi_bvalid(s_axi_bvalid), - .s_axi_bready(s_axi_bready), - - /* - * AXI master interfaces - */ - .m_axi_awid(m_axi_awid), - .m_axi_awaddr(m_axi_awaddr), - .m_axi_awlen(m_axi_awlen), - .m_axi_awsize(m_axi_awsize), - .m_axi_awburst(m_axi_awburst), - .m_axi_awlock(m_axi_awlock), - .m_axi_awcache(m_axi_awcache), - .m_axi_awprot(m_axi_awprot), - .m_axi_awqos(m_axi_awqos), - .m_axi_awregion(m_axi_awregion), - .m_axi_awuser(m_axi_awuser), - .m_axi_awvalid(m_axi_awvalid), - .m_axi_awready(m_axi_awready), - .m_axi_wdata(m_axi_wdata), - .m_axi_wstrb(m_axi_wstrb), - .m_axi_wlast(m_axi_wlast), - .m_axi_wuser(m_axi_wuser), - .m_axi_wvalid(m_axi_wvalid), - .m_axi_wready(m_axi_wready), - .m_axi_bid(m_axi_bid), - .m_axi_bresp(m_axi_bresp), - .m_axi_buser(m_axi_buser), - .m_axi_bvalid(m_axi_bvalid), - .m_axi_bready(m_axi_bready) -); - -axi_crossbar_rd #( - .S_COUNT(S_COUNT), - .M_COUNT(M_COUNT), - .DATA_WIDTH(DATA_WIDTH), - .ADDR_WIDTH(ADDR_WIDTH), - .STRB_WIDTH(STRB_WIDTH), - .S_ID_WIDTH(S_ID_WIDTH), - .M_ID_WIDTH(M_ID_WIDTH), - .ARUSER_ENABLE(ARUSER_ENABLE), - .ARUSER_WIDTH(ARUSER_WIDTH), - .RUSER_ENABLE(RUSER_ENABLE), - .RUSER_WIDTH(RUSER_WIDTH), - .S_THREADS(S_THREADS), - .S_ACCEPT(S_ACCEPT), - .M_REGIONS(M_REGIONS), - .M_BASE_ADDR(M_BASE_ADDR), - .M_ADDR_WIDTH(M_ADDR_WIDTH), - .M_CONNECT(M_CONNECT_READ), - .M_ISSUE(M_ISSUE), - .M_SECURE(M_SECURE), - .S_AR_REG_TYPE(S_AR_REG_TYPE), - .S_R_REG_TYPE (S_R_REG_TYPE) -) -axi_crossbar_rd_inst ( - .clk(clk), - .rst(rst), - - /* - * AXI slave interfaces - */ - .s_axi_arid(s_axi_arid), - .s_axi_araddr(s_axi_araddr), - .s_axi_arlen(s_axi_arlen), - .s_axi_arsize(s_axi_arsize), - .s_axi_arburst(s_axi_arburst), - .s_axi_arlock(s_axi_arlock), - .s_axi_arcache(s_axi_arcache), - .s_axi_arprot(s_axi_arprot), - .s_axi_arqos(s_axi_arqos), - .s_axi_aruser(s_axi_aruser), - .s_axi_arvalid(s_axi_arvalid), - .s_axi_arready(s_axi_arready), - .s_axi_rid(s_axi_rid), - .s_axi_rdata(s_axi_rdata), - .s_axi_rresp(s_axi_rresp), - .s_axi_rlast(s_axi_rlast), - .s_axi_ruser(s_axi_ruser), - .s_axi_rvalid(s_axi_rvalid), - .s_axi_rready(s_axi_rready), - - /* - * AXI master interfaces - */ - .m_axi_arid(m_axi_arid), - .m_axi_araddr(m_axi_araddr), - .m_axi_arlen(m_axi_arlen), - .m_axi_arsize(m_axi_arsize), - .m_axi_arburst(m_axi_arburst), - .m_axi_arlock(m_axi_arlock), - .m_axi_arcache(m_axi_arcache), - .m_axi_arprot(m_axi_arprot), - .m_axi_arqos(m_axi_arqos), - .m_axi_arregion(m_axi_arregion), - .m_axi_aruser(m_axi_aruser), - .m_axi_arvalid(m_axi_arvalid), - .m_axi_arready(m_axi_arready), - .m_axi_rid(m_axi_rid), - .m_axi_rdata(m_axi_rdata), - .m_axi_rresp(m_axi_rresp), - .m_axi_rlast(m_axi_rlast), - .m_axi_ruser(m_axi_ruser), - .m_axi_rvalid(m_axi_rvalid), - .m_axi_rready(m_axi_rready) -); - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/axi_crossbar_addr.v b/xls/modules/zstd/external/axi_crossbar_addr.v deleted file mode 100644 index 7b7846526b..0000000000 --- a/xls/modules/zstd/external/axi_crossbar_addr.v +++ /dev/null @@ -1,418 +0,0 @@ -/* - -Copyright (c) 2018 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * AXI4 crossbar address decode and admission control - */ -module axi_crossbar_addr # -( - // Slave interface index - parameter S = 0, - // Number of AXI inputs (slave interfaces) - parameter S_COUNT = 4, - // Number of AXI outputs (master interfaces) - parameter M_COUNT = 4, - // Width of address bus in bits - parameter ADDR_WIDTH = 32, - // ID field width - parameter ID_WIDTH = 8, - // Number of concurrent unique IDs - parameter S_THREADS = 32'd2, - // Number of concurrent operations - parameter S_ACCEPT = 32'd16, - // Number of regions per master interface - parameter M_REGIONS = 1, - // Master interface base addresses - // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits - // set to zero for default addressing based on M_ADDR_WIDTH - parameter M_BASE_ADDR = 0, - // Master interface address widths - // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits - parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, - // Connections between interfaces - // M_COUNT concatenated fields of S_COUNT bits - parameter M_CONNECT = {M_COUNT{{S_COUNT{1'b1}}}}, - // Secure master (fail operations based on awprot/arprot) - // M_COUNT bits - parameter M_SECURE = {M_COUNT{1'b0}}, - // Enable write command output - parameter WC_OUTPUT = 0 -) -( - input wire clk, - input wire rst, - - /* - * Address input - */ - input wire [ID_WIDTH-1:0] s_axi_aid, - input wire [ADDR_WIDTH-1:0] s_axi_aaddr, - input wire [2:0] s_axi_aprot, - input wire [3:0] s_axi_aqos, - input wire s_axi_avalid, - output wire s_axi_aready, - - /* - * Address output - */ - output wire [3:0] m_axi_aregion, - output wire [$clog2(M_COUNT)-1:0] m_select, - output wire m_axi_avalid, - input wire m_axi_aready, - - /* - * Write command output - */ - output wire [$clog2(M_COUNT)-1:0] m_wc_select, - output wire m_wc_decerr, - output wire m_wc_valid, - input wire m_wc_ready, - - /* - * Reply command output - */ - output wire m_rc_decerr, - output wire m_rc_valid, - input wire m_rc_ready, - - /* - * Completion input - */ - input wire [ID_WIDTH-1:0] s_cpl_id, - input wire s_cpl_valid -); - -parameter CL_S_COUNT = $clog2(S_COUNT); -parameter CL_M_COUNT = $clog2(M_COUNT); - -parameter S_INT_THREADS = S_THREADS > S_ACCEPT ? S_ACCEPT : S_THREADS; -parameter CL_S_INT_THREADS = $clog2(S_INT_THREADS); -parameter CL_S_ACCEPT = $clog2(S_ACCEPT); - -// default address computation -function [M_COUNT*M_REGIONS*ADDR_WIDTH-1:0] calcBaseAddrs(input [31:0] dummy); - integer i; - reg [ADDR_WIDTH-1:0] base; - reg [ADDR_WIDTH-1:0] width; - reg [ADDR_WIDTH-1:0] size; - reg [ADDR_WIDTH-1:0] mask; - begin - calcBaseAddrs = {M_COUNT*M_REGIONS*ADDR_WIDTH{1'b0}}; - base = 0; - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - width = M_ADDR_WIDTH[i*32 +: 32]; - mask = {ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - width); - size = mask + 1; - if (width > 0) begin - if ((base & mask) != 0) begin - base = base + size - (base & mask); // align - end - calcBaseAddrs[i * ADDR_WIDTH +: ADDR_WIDTH] = base; - base = base + size; // increment - end - end - end -endfunction - -parameter M_BASE_ADDR_INT = M_BASE_ADDR ? M_BASE_ADDR : calcBaseAddrs(0); - -integer i, j; - -// check configuration -initial begin - if (S_ACCEPT < 1) begin - $error("Error: need at least 1 accept (instance %m)"); - $finish; - end - - if (S_THREADS < 1) begin - $error("Error: need at least 1 thread (instance %m)"); - $finish; - end - - if (S_THREADS > S_ACCEPT) begin - $warning("Warning: requested thread count larger than accept count; limiting thread count to accept count (instance %m)"); - end - - if (M_REGIONS < 1) begin - $error("Error: need at least 1 region (instance %m)"); - $finish; - end - - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - if (M_ADDR_WIDTH[i*32 +: 32] && (M_ADDR_WIDTH[i*32 +: 32] < 12 || M_ADDR_WIDTH[i*32 +: 32] > ADDR_WIDTH)) begin - $error("Error: address width out of range (instance %m)"); - $finish; - end - end - - $display("Addressing configuration for axi_crossbar_addr instance %m"); - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - if (M_ADDR_WIDTH[i*32 +: 32]) begin - $display("%2d (%2d): %x / %02d -- %x-%x", - i/M_REGIONS, i%M_REGIONS, - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], - M_ADDR_WIDTH[i*32 +: 32], - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) - ); - end - end - - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - if ((M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & (2**M_ADDR_WIDTH[i*32 +: 32]-1)) != 0) begin - $display("Region not aligned:"); - $display("%2d (%2d): %x / %2d -- %x-%x", - i/M_REGIONS, i%M_REGIONS, - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], - M_ADDR_WIDTH[i*32 +: 32], - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) - ); - $error("Error: address range not aligned (instance %m)"); - $finish; - end - end - - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - for (j = i+1; j < M_COUNT*M_REGIONS; j = j + 1) begin - if (M_ADDR_WIDTH[i*32 +: 32] && M_ADDR_WIDTH[j*32 +: 32]) begin - if (((M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32])) <= (M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[j*32 +: 32])))) - && ((M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[j*32 +: 32])) <= (M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32]))))) begin - $display("Overlapping regions:"); - $display("%2d (%2d): %x / %2d -- %x-%x", - i/M_REGIONS, i%M_REGIONS, - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH], - M_ADDR_WIDTH[i*32 +: 32], - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[i*32 +: 32]), - M_BASE_ADDR_INT[i*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[i*32 +: 32])) - ); - $display("%2d (%2d): %x / %2d -- %x-%x", - j/M_REGIONS, j%M_REGIONS, - M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH], - M_ADDR_WIDTH[j*32 +: 32], - M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] & ({ADDR_WIDTH{1'b1}} << M_ADDR_WIDTH[j*32 +: 32]), - M_BASE_ADDR_INT[j*ADDR_WIDTH +: ADDR_WIDTH] | ({ADDR_WIDTH{1'b1}} >> (ADDR_WIDTH - M_ADDR_WIDTH[j*32 +: 32])) - ); - $error("Error: address ranges overlap (instance %m)"); - $finish; - end - end - end - end -end - -localparam [2:0] - STATE_IDLE = 3'd0, - STATE_DECODE = 3'd1; - -reg [2:0] state_reg = STATE_IDLE, state_next; - -reg s_axi_aready_reg = 0, s_axi_aready_next; - -reg [3:0] m_axi_aregion_reg = 4'd0, m_axi_aregion_next; -reg [CL_M_COUNT-1:0] m_select_reg = 0, m_select_next; -reg m_axi_avalid_reg = 1'b0, m_axi_avalid_next; -reg m_decerr_reg = 1'b0, m_decerr_next; -reg m_wc_valid_reg = 1'b0, m_wc_valid_next; -reg m_rc_valid_reg = 1'b0, m_rc_valid_next; - -assign s_axi_aready = s_axi_aready_reg; - -assign m_axi_aregion = m_axi_aregion_reg; -assign m_select = m_select_reg; -assign m_axi_avalid = m_axi_avalid_reg; - -assign m_wc_select = m_select_reg; -assign m_wc_decerr = m_decerr_reg; -assign m_wc_valid = m_wc_valid_reg; - -assign m_rc_decerr = m_decerr_reg; -assign m_rc_valid = m_rc_valid_reg; - -reg match; -reg trans_start; -reg trans_complete; - -reg [$clog2(S_ACCEPT+1)-1:0] trans_count_reg = 0; -wire trans_limit = trans_count_reg >= S_ACCEPT && !trans_complete; - -// transfer ID thread tracking -reg [ID_WIDTH-1:0] thread_id_reg[S_INT_THREADS-1:0]; -reg [CL_M_COUNT-1:0] thread_m_reg[S_INT_THREADS-1:0]; -reg [3:0] thread_region_reg[S_INT_THREADS-1:0]; -reg [$clog2(S_ACCEPT+1)-1:0] thread_count_reg[S_INT_THREADS-1:0]; - -wire [S_INT_THREADS-1:0] thread_active; -wire [S_INT_THREADS-1:0] thread_match; -wire [S_INT_THREADS-1:0] thread_match_dest; -wire [S_INT_THREADS-1:0] thread_cpl_match; -wire [S_INT_THREADS-1:0] thread_trans_start; -wire [S_INT_THREADS-1:0] thread_trans_complete; - -generate - genvar n; - - for (n = 0; n < S_INT_THREADS; n = n + 1) begin - initial begin - thread_count_reg[n] <= 0; - end - - assign thread_active[n] = thread_count_reg[n] != 0; - assign thread_match[n] = thread_active[n] && thread_id_reg[n] == s_axi_aid; - assign thread_match_dest[n] = thread_match[n] && thread_m_reg[n] == m_select_next && (M_REGIONS < 2 || thread_region_reg[n] == m_axi_aregion_next); - assign thread_cpl_match[n] = thread_active[n] && thread_id_reg[n] == s_cpl_id; - assign thread_trans_start[n] = (thread_match[n] || (!thread_active[n] && !thread_match && !(thread_trans_start & ({S_INT_THREADS{1'b1}} >> (S_INT_THREADS-n))))) && trans_start; - assign thread_trans_complete[n] = thread_cpl_match[n] && trans_complete; - - always @(posedge clk) begin - if (rst) begin - thread_count_reg[n] <= 0; - end else begin - if (thread_trans_start[n] && !thread_trans_complete[n]) begin - thread_count_reg[n] <= thread_count_reg[n] + 1; - end else if (!thread_trans_start[n] && thread_trans_complete[n]) begin - thread_count_reg[n] <= thread_count_reg[n] - 1; - end - end - - if (thread_trans_start[n]) begin - thread_id_reg[n] <= s_axi_aid; - thread_m_reg[n] <= m_select_next; - thread_region_reg[n] <= m_axi_aregion_next; - end - end - end -endgenerate - -always @* begin - state_next = STATE_IDLE; - - match = 1'b0; - trans_start = 1'b0; - trans_complete = 1'b0; - - s_axi_aready_next = 1'b0; - - m_axi_aregion_next = m_axi_aregion_reg; - m_select_next = m_select_reg; - m_axi_avalid_next = m_axi_avalid_reg && !m_axi_aready; - m_decerr_next = m_decerr_reg; - m_wc_valid_next = m_wc_valid_reg && !m_wc_ready; - m_rc_valid_next = m_rc_valid_reg && !m_rc_ready; - - case (state_reg) - STATE_IDLE: begin - // idle state, store values - s_axi_aready_next = 1'b0; - - if (s_axi_avalid && !s_axi_aready) begin - match = 1'b0; - for (i = 0; i < M_COUNT; i = i + 1) begin - for (j = 0; j < M_REGIONS; j = j + 1) begin - if (M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32] && (!M_SECURE[i] || !s_axi_aprot[1]) && (M_CONNECT & (1 << (S+i*S_COUNT))) && (s_axi_aaddr >> M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32]) == (M_BASE_ADDR_INT[(i*M_REGIONS+j)*ADDR_WIDTH +: ADDR_WIDTH] >> M_ADDR_WIDTH[(i*M_REGIONS+j)*32 +: 32])) begin - m_select_next = i; - m_axi_aregion_next = j; - match = 1'b1; - end - end - end - - if (match) begin - // address decode successful - if (!trans_limit && (thread_match_dest || (!(&thread_active) && !thread_match))) begin - // transaction limit not reached - m_axi_avalid_next = 1'b1; - m_decerr_next = 1'b0; - m_wc_valid_next = WC_OUTPUT; - m_rc_valid_next = 1'b0; - trans_start = 1'b1; - state_next = STATE_DECODE; - end else begin - // transaction limit reached; block in idle - state_next = STATE_IDLE; - end - end else begin - // decode error - m_axi_avalid_next = 1'b0; - m_decerr_next = 1'b1; - m_wc_valid_next = WC_OUTPUT; - m_rc_valid_next = 1'b1; - state_next = STATE_DECODE; - end - end else begin - state_next = STATE_IDLE; - end - end - STATE_DECODE: begin - if (!m_axi_avalid_next && (!m_wc_valid_next || !WC_OUTPUT) && !m_rc_valid_next) begin - s_axi_aready_next = 1'b1; - state_next = STATE_IDLE; - end else begin - state_next = STATE_DECODE; - end - end - endcase - - // manage completions - trans_complete = s_cpl_valid; -end - -always @(posedge clk) begin - if (rst) begin - state_reg <= STATE_IDLE; - s_axi_aready_reg <= 1'b0; - m_axi_avalid_reg <= 1'b0; - m_wc_valid_reg <= 1'b0; - m_rc_valid_reg <= 1'b0; - - trans_count_reg <= 0; - end else begin - state_reg <= state_next; - s_axi_aready_reg <= s_axi_aready_next; - m_axi_avalid_reg <= m_axi_avalid_next; - m_wc_valid_reg <= m_wc_valid_next; - m_rc_valid_reg <= m_rc_valid_next; - - if (trans_start && !trans_complete) begin - trans_count_reg <= trans_count_reg + 1; - end else if (!trans_start && trans_complete) begin - trans_count_reg <= trans_count_reg - 1; - end - end - - m_axi_aregion_reg <= m_axi_aregion_next; - m_select_reg <= m_select_next; - m_decerr_reg <= m_decerr_next; -end - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/axi_crossbar_rd.v b/xls/modules/zstd/external/axi_crossbar_rd.v deleted file mode 100644 index 2b1410ac62..0000000000 --- a/xls/modules/zstd/external/axi_crossbar_rd.v +++ /dev/null @@ -1,569 +0,0 @@ -/* - -Copyright (c) 2018 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * AXI4 crossbar (read) - */ -module axi_crossbar_rd # -( - // Number of AXI inputs (slave interfaces) - parameter S_COUNT = 4, - // Number of AXI outputs (master interfaces) - parameter M_COUNT = 4, - // Width of data bus in bits - parameter DATA_WIDTH = 32, - // Width of address bus in bits - parameter ADDR_WIDTH = 32, - // Width of wstrb (width of data bus in words) - parameter STRB_WIDTH = (DATA_WIDTH/8), - // Input ID field width (from AXI masters) - parameter S_ID_WIDTH = 8, - // Output ID field width (towards AXI slaves) - // Additional bits required for response routing - parameter M_ID_WIDTH = S_ID_WIDTH+$clog2(S_COUNT), - // Propagate aruser signal - parameter ARUSER_ENABLE = 0, - // Width of aruser signal - parameter ARUSER_WIDTH = 1, - // Propagate ruser signal - parameter RUSER_ENABLE = 0, - // Width of ruser signal - parameter RUSER_WIDTH = 1, - // Number of concurrent unique IDs for each slave interface - // S_COUNT concatenated fields of 32 bits - parameter S_THREADS = {S_COUNT{32'd2}}, - // Number of concurrent operations for each slave interface - // S_COUNT concatenated fields of 32 bits - parameter S_ACCEPT = {S_COUNT{32'd16}}, - // Number of regions per master interface - parameter M_REGIONS = 1, - // Master interface base addresses - // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits - // set to zero for default addressing based on M_ADDR_WIDTH - parameter M_BASE_ADDR = 0, - // Master interface address widths - // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits - parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, - // Read connections between interfaces - // M_COUNT concatenated fields of S_COUNT bits - parameter M_CONNECT = {M_COUNT{{S_COUNT{1'b1}}}}, - // Number of concurrent operations for each master interface - // M_COUNT concatenated fields of 32 bits - parameter M_ISSUE = {M_COUNT{32'd4}}, - // Secure master (fail operations based on awprot/arprot) - // M_COUNT bits - parameter M_SECURE = {M_COUNT{1'b0}}, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_AR_REG_TYPE = {S_COUNT{2'd0}}, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_R_REG_TYPE = {S_COUNT{2'd2}}, - // Master interface AR channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_AR_REG_TYPE = {M_COUNT{2'd1}}, - // Master interface R channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_R_REG_TYPE = {M_COUNT{2'd0}} -) -( - input wire clk, - input wire rst, - - /* - * AXI slave interfaces - */ - input wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_arid, - input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_araddr, - input wire [S_COUNT*8-1:0] s_axi_arlen, - input wire [S_COUNT*3-1:0] s_axi_arsize, - input wire [S_COUNT*2-1:0] s_axi_arburst, - input wire [S_COUNT-1:0] s_axi_arlock, - input wire [S_COUNT*4-1:0] s_axi_arcache, - input wire [S_COUNT*3-1:0] s_axi_arprot, - input wire [S_COUNT*4-1:0] s_axi_arqos, - input wire [S_COUNT*ARUSER_WIDTH-1:0] s_axi_aruser, - input wire [S_COUNT-1:0] s_axi_arvalid, - output wire [S_COUNT-1:0] s_axi_arready, - output wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_rid, - output wire [S_COUNT*DATA_WIDTH-1:0] s_axi_rdata, - output wire [S_COUNT*2-1:0] s_axi_rresp, - output wire [S_COUNT-1:0] s_axi_rlast, - output wire [S_COUNT*RUSER_WIDTH-1:0] s_axi_ruser, - output wire [S_COUNT-1:0] s_axi_rvalid, - input wire [S_COUNT-1:0] s_axi_rready, - - /* - * AXI master interfaces - */ - output wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_arid, - output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_araddr, - output wire [M_COUNT*8-1:0] m_axi_arlen, - output wire [M_COUNT*3-1:0] m_axi_arsize, - output wire [M_COUNT*2-1:0] m_axi_arburst, - output wire [M_COUNT-1:0] m_axi_arlock, - output wire [M_COUNT*4-1:0] m_axi_arcache, - output wire [M_COUNT*3-1:0] m_axi_arprot, - output wire [M_COUNT*4-1:0] m_axi_arqos, - output wire [M_COUNT*4-1:0] m_axi_arregion, - output wire [M_COUNT*ARUSER_WIDTH-1:0] m_axi_aruser, - output wire [M_COUNT-1:0] m_axi_arvalid, - input wire [M_COUNT-1:0] m_axi_arready, - input wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_rid, - input wire [M_COUNT*DATA_WIDTH-1:0] m_axi_rdata, - input wire [M_COUNT*2-1:0] m_axi_rresp, - input wire [M_COUNT-1:0] m_axi_rlast, - input wire [M_COUNT*RUSER_WIDTH-1:0] m_axi_ruser, - input wire [M_COUNT-1:0] m_axi_rvalid, - output wire [M_COUNT-1:0] m_axi_rready -); - -parameter CL_S_COUNT = $clog2(S_COUNT); -parameter CL_M_COUNT = $clog2(M_COUNT); -parameter M_COUNT_P1 = M_COUNT+1; -parameter CL_M_COUNT_P1 = $clog2(M_COUNT_P1); - -integer i; - -// check configuration -initial begin - if (M_ID_WIDTH < S_ID_WIDTH+$clog2(S_COUNT)) begin - $error("Error: M_ID_WIDTH must be at least $clog2(S_COUNT) larger than S_ID_WIDTH (instance %m)"); - $finish; - end - - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - if (M_ADDR_WIDTH[i*32 +: 32] && (M_ADDR_WIDTH[i*32 +: 32] < 12 || M_ADDR_WIDTH[i*32 +: 32] > ADDR_WIDTH)) begin - $error("Error: value out of range (instance %m)"); - $finish; - end - end -end - -wire [S_COUNT*S_ID_WIDTH-1:0] int_s_axi_arid; -wire [S_COUNT*ADDR_WIDTH-1:0] int_s_axi_araddr; -wire [S_COUNT*8-1:0] int_s_axi_arlen; -wire [S_COUNT*3-1:0] int_s_axi_arsize; -wire [S_COUNT*2-1:0] int_s_axi_arburst; -wire [S_COUNT-1:0] int_s_axi_arlock; -wire [S_COUNT*4-1:0] int_s_axi_arcache; -wire [S_COUNT*3-1:0] int_s_axi_arprot; -wire [S_COUNT*4-1:0] int_s_axi_arqos; -wire [S_COUNT*4-1:0] int_s_axi_arregion; -wire [S_COUNT*ARUSER_WIDTH-1:0] int_s_axi_aruser; -wire [S_COUNT-1:0] int_s_axi_arvalid; -wire [S_COUNT-1:0] int_s_axi_arready; - -wire [S_COUNT*M_COUNT-1:0] int_axi_arvalid; -wire [M_COUNT*S_COUNT-1:0] int_axi_arready; - -wire [M_COUNT*M_ID_WIDTH-1:0] int_m_axi_rid; -wire [M_COUNT*DATA_WIDTH-1:0] int_m_axi_rdata; -wire [M_COUNT*2-1:0] int_m_axi_rresp; -wire [M_COUNT-1:0] int_m_axi_rlast; -wire [M_COUNT*RUSER_WIDTH-1:0] int_m_axi_ruser; -wire [M_COUNT-1:0] int_m_axi_rvalid; -wire [M_COUNT-1:0] int_m_axi_rready; - -wire [M_COUNT*S_COUNT-1:0] int_axi_rvalid; -wire [S_COUNT*M_COUNT-1:0] int_axi_rready; - -generate - - genvar m, n; - - for (m = 0; m < S_COUNT; m = m + 1) begin : s_ifaces - // address decode and admission control - wire [CL_M_COUNT-1:0] a_select; - - wire m_axi_avalid; - wire m_axi_aready; - - wire m_rc_decerr; - wire m_rc_valid; - wire m_rc_ready; - - wire [S_ID_WIDTH-1:0] s_cpl_id; - wire s_cpl_valid; - - axi_crossbar_addr #( - .S(m), - .S_COUNT(S_COUNT), - .M_COUNT(M_COUNT), - .ADDR_WIDTH(ADDR_WIDTH), - .ID_WIDTH(S_ID_WIDTH), - .S_THREADS(S_THREADS[m*32 +: 32]), - .S_ACCEPT(S_ACCEPT[m*32 +: 32]), - .M_REGIONS(M_REGIONS), - .M_BASE_ADDR(M_BASE_ADDR), - .M_ADDR_WIDTH(M_ADDR_WIDTH), - .M_CONNECT(M_CONNECT), - .M_SECURE(M_SECURE), - .WC_OUTPUT(0) - ) - addr_inst ( - .clk(clk), - .rst(rst), - - /* - * Address input - */ - .s_axi_aid(int_s_axi_arid[m*S_ID_WIDTH +: S_ID_WIDTH]), - .s_axi_aaddr(int_s_axi_araddr[m*ADDR_WIDTH +: ADDR_WIDTH]), - .s_axi_aprot(int_s_axi_arprot[m*3 +: 3]), - .s_axi_aqos(int_s_axi_arqos[m*4 +: 4]), - .s_axi_avalid(int_s_axi_arvalid[m]), - .s_axi_aready(int_s_axi_arready[m]), - - /* - * Address output - */ - .m_axi_aregion(int_s_axi_arregion[m*4 +: 4]), - .m_select(a_select), - .m_axi_avalid(m_axi_avalid), - .m_axi_aready(m_axi_aready), - - /* - * Write command output - */ - .m_wc_select(), - .m_wc_decerr(), - .m_wc_valid(), - .m_wc_ready(1'b1), - - /* - * Response command output - */ - .m_rc_decerr(m_rc_decerr), - .m_rc_valid(m_rc_valid), - .m_rc_ready(m_rc_ready), - - /* - * Completion input - */ - .s_cpl_id(s_cpl_id), - .s_cpl_valid(s_cpl_valid) - ); - - assign int_axi_arvalid[m*M_COUNT +: M_COUNT] = m_axi_avalid << a_select; - assign m_axi_aready = int_axi_arready[a_select*S_COUNT+m]; - - // decode error handling - reg [S_ID_WIDTH-1:0] decerr_m_axi_rid_reg = {S_ID_WIDTH{1'b0}}, decerr_m_axi_rid_next; - reg decerr_m_axi_rlast_reg = 1'b0, decerr_m_axi_rlast_next; - reg decerr_m_axi_rvalid_reg = 1'b0, decerr_m_axi_rvalid_next; - wire decerr_m_axi_rready; - - reg [7:0] decerr_len_reg = 8'd0, decerr_len_next; - - assign m_rc_ready = !decerr_m_axi_rvalid_reg; - - always @* begin - decerr_len_next = decerr_len_reg; - decerr_m_axi_rid_next = decerr_m_axi_rid_reg; - decerr_m_axi_rlast_next = decerr_m_axi_rlast_reg; - decerr_m_axi_rvalid_next = decerr_m_axi_rvalid_reg; - - if (decerr_m_axi_rvalid_reg) begin - if (decerr_m_axi_rready) begin - if (decerr_len_reg > 0) begin - decerr_len_next = decerr_len_reg-1; - decerr_m_axi_rlast_next = (decerr_len_next == 0); - decerr_m_axi_rvalid_next = 1'b1; - end else begin - decerr_m_axi_rvalid_next = 1'b0; - end - end - end else if (m_rc_valid && m_rc_ready) begin - decerr_len_next = int_s_axi_arlen[m*8 +: 8]; - decerr_m_axi_rid_next = int_s_axi_arid[m*S_ID_WIDTH +: S_ID_WIDTH]; - decerr_m_axi_rlast_next = (decerr_len_next == 0); - decerr_m_axi_rvalid_next = 1'b1; - end - end - - always @(posedge clk) begin - if (rst) begin - decerr_m_axi_rvalid_reg <= 1'b0; - end else begin - decerr_m_axi_rvalid_reg <= decerr_m_axi_rvalid_next; - end - - decerr_m_axi_rid_reg <= decerr_m_axi_rid_next; - decerr_m_axi_rlast_reg <= decerr_m_axi_rlast_next; - decerr_len_reg <= decerr_len_next; - end - - // read response arbitration - wire [M_COUNT_P1-1:0] r_request; - wire [M_COUNT_P1-1:0] r_acknowledge; - wire [M_COUNT_P1-1:0] r_grant; - wire r_grant_valid; - wire [CL_M_COUNT_P1-1:0] r_grant_encoded; - - arbiter #( - .PORTS(M_COUNT_P1), - .ARB_TYPE_ROUND_ROBIN(1), - .ARB_BLOCK(1), - .ARB_BLOCK_ACK(1), - .ARB_LSB_HIGH_PRIORITY(1) - ) - r_arb_inst ( - .clk(clk), - .rst(rst), - .request(r_request), - .acknowledge(r_acknowledge), - .grant(r_grant), - .grant_valid(r_grant_valid), - .grant_encoded(r_grant_encoded) - ); - - // read response mux - wire [S_ID_WIDTH-1:0] m_axi_rid_mux = {decerr_m_axi_rid_reg, int_m_axi_rid} >> r_grant_encoded*M_ID_WIDTH; - wire [DATA_WIDTH-1:0] m_axi_rdata_mux = {{DATA_WIDTH{1'b0}}, int_m_axi_rdata} >> r_grant_encoded*DATA_WIDTH; - wire [1:0] m_axi_rresp_mux = {2'b11, int_m_axi_rresp} >> r_grant_encoded*2; - wire m_axi_rlast_mux = {decerr_m_axi_rlast_reg, int_m_axi_rlast} >> r_grant_encoded; - wire [RUSER_WIDTH-1:0] m_axi_ruser_mux = {{RUSER_WIDTH{1'b0}}, int_m_axi_ruser} >> r_grant_encoded*RUSER_WIDTH; - wire m_axi_rvalid_mux = ({decerr_m_axi_rvalid_reg, int_m_axi_rvalid} >> r_grant_encoded) & r_grant_valid; - wire m_axi_rready_mux; - - assign int_axi_rready[m*M_COUNT +: M_COUNT] = (r_grant_valid && m_axi_rready_mux) << r_grant_encoded; - assign decerr_m_axi_rready = (r_grant_valid && m_axi_rready_mux) && (r_grant_encoded == M_COUNT_P1-1); - - for (n = 0; n < M_COUNT; n = n + 1) begin - assign r_request[n] = int_axi_rvalid[n*S_COUNT+m] && !r_grant[n]; - assign r_acknowledge[n] = r_grant[n] && int_axi_rvalid[n*S_COUNT+m] && m_axi_rlast_mux && m_axi_rready_mux; - end - - assign r_request[M_COUNT_P1-1] = decerr_m_axi_rvalid_reg && !r_grant[M_COUNT_P1-1]; - assign r_acknowledge[M_COUNT_P1-1] = r_grant[M_COUNT_P1-1] && decerr_m_axi_rvalid_reg && decerr_m_axi_rlast_reg && m_axi_rready_mux; - - assign s_cpl_id = m_axi_rid_mux; - assign s_cpl_valid = m_axi_rvalid_mux && m_axi_rready_mux && m_axi_rlast_mux; - - // S side register - axi_register_rd #( - .DATA_WIDTH(DATA_WIDTH), - .ADDR_WIDTH(ADDR_WIDTH), - .STRB_WIDTH(STRB_WIDTH), - .ID_WIDTH(S_ID_WIDTH), - .ARUSER_ENABLE(ARUSER_ENABLE), - .ARUSER_WIDTH(ARUSER_WIDTH), - .RUSER_ENABLE(RUSER_ENABLE), - .RUSER_WIDTH(RUSER_WIDTH), - .AR_REG_TYPE(S_AR_REG_TYPE[m*2 +: 2]), - .R_REG_TYPE(S_R_REG_TYPE[m*2 +: 2]) - ) - reg_inst ( - .clk(clk), - .rst(rst), - .s_axi_arid(s_axi_arid[m*S_ID_WIDTH +: S_ID_WIDTH]), - .s_axi_araddr(s_axi_araddr[m*ADDR_WIDTH +: ADDR_WIDTH]), - .s_axi_arlen(s_axi_arlen[m*8 +: 8]), - .s_axi_arsize(s_axi_arsize[m*3 +: 3]), - .s_axi_arburst(s_axi_arburst[m*2 +: 2]), - .s_axi_arlock(s_axi_arlock[m]), - .s_axi_arcache(s_axi_arcache[m*4 +: 4]), - .s_axi_arprot(s_axi_arprot[m*3 +: 3]), - .s_axi_arqos(s_axi_arqos[m*4 +: 4]), - .s_axi_arregion(4'd0), - .s_axi_aruser(s_axi_aruser[m*ARUSER_WIDTH +: ARUSER_WIDTH]), - .s_axi_arvalid(s_axi_arvalid[m]), - .s_axi_arready(s_axi_arready[m]), - .s_axi_rid(s_axi_rid[m*S_ID_WIDTH +: S_ID_WIDTH]), - .s_axi_rdata(s_axi_rdata[m*DATA_WIDTH +: DATA_WIDTH]), - .s_axi_rresp(s_axi_rresp[m*2 +: 2]), - .s_axi_rlast(s_axi_rlast[m]), - .s_axi_ruser(s_axi_ruser[m*RUSER_WIDTH +: RUSER_WIDTH]), - .s_axi_rvalid(s_axi_rvalid[m]), - .s_axi_rready(s_axi_rready[m]), - .m_axi_arid(int_s_axi_arid[m*S_ID_WIDTH +: S_ID_WIDTH]), - .m_axi_araddr(int_s_axi_araddr[m*ADDR_WIDTH +: ADDR_WIDTH]), - .m_axi_arlen(int_s_axi_arlen[m*8 +: 8]), - .m_axi_arsize(int_s_axi_arsize[m*3 +: 3]), - .m_axi_arburst(int_s_axi_arburst[m*2 +: 2]), - .m_axi_arlock(int_s_axi_arlock[m]), - .m_axi_arcache(int_s_axi_arcache[m*4 +: 4]), - .m_axi_arprot(int_s_axi_arprot[m*3 +: 3]), - .m_axi_arqos(int_s_axi_arqos[m*4 +: 4]), - .m_axi_arregion(), - .m_axi_aruser(int_s_axi_aruser[m*ARUSER_WIDTH +: ARUSER_WIDTH]), - .m_axi_arvalid(int_s_axi_arvalid[m]), - .m_axi_arready(int_s_axi_arready[m]), - .m_axi_rid(m_axi_rid_mux), - .m_axi_rdata(m_axi_rdata_mux), - .m_axi_rresp(m_axi_rresp_mux), - .m_axi_rlast(m_axi_rlast_mux), - .m_axi_ruser(m_axi_ruser_mux), - .m_axi_rvalid(m_axi_rvalid_mux), - .m_axi_rready(m_axi_rready_mux) - ); - end // s_ifaces - - for (n = 0; n < M_COUNT; n = n + 1) begin : m_ifaces - // in-flight transaction count - wire trans_start; - wire trans_complete; - reg [$clog2(M_ISSUE[n*32 +: 32]+1)-1:0] trans_count_reg = 0; - - wire trans_limit = trans_count_reg >= M_ISSUE[n*32 +: 32] && !trans_complete; - - always @(posedge clk) begin - if (rst) begin - trans_count_reg <= 0; - end else begin - if (trans_start && !trans_complete) begin - trans_count_reg <= trans_count_reg + 1; - end else if (!trans_start && trans_complete) begin - trans_count_reg <= trans_count_reg - 1; - end - end - end - - // address arbitration - wire [S_COUNT-1:0] a_request; - wire [S_COUNT-1:0] a_acknowledge; - wire [S_COUNT-1:0] a_grant; - wire a_grant_valid; - wire [CL_S_COUNT-1:0] a_grant_encoded; - - arbiter #( - .PORTS(S_COUNT), - .ARB_TYPE_ROUND_ROBIN(1), - .ARB_BLOCK(1), - .ARB_BLOCK_ACK(1), - .ARB_LSB_HIGH_PRIORITY(1) - ) - a_arb_inst ( - .clk(clk), - .rst(rst), - .request(a_request), - .acknowledge(a_acknowledge), - .grant(a_grant), - .grant_valid(a_grant_valid), - .grant_encoded(a_grant_encoded) - ); - - // address mux - wire [M_ID_WIDTH-1:0] s_axi_arid_mux = int_s_axi_arid[a_grant_encoded*S_ID_WIDTH +: S_ID_WIDTH] | (a_grant_encoded << S_ID_WIDTH); - wire [ADDR_WIDTH-1:0] s_axi_araddr_mux = int_s_axi_araddr[a_grant_encoded*ADDR_WIDTH +: ADDR_WIDTH]; - wire [7:0] s_axi_arlen_mux = int_s_axi_arlen[a_grant_encoded*8 +: 8]; - wire [2:0] s_axi_arsize_mux = int_s_axi_arsize[a_grant_encoded*3 +: 3]; - wire [1:0] s_axi_arburst_mux = int_s_axi_arburst[a_grant_encoded*2 +: 2]; - wire s_axi_arlock_mux = int_s_axi_arlock[a_grant_encoded]; - wire [3:0] s_axi_arcache_mux = int_s_axi_arcache[a_grant_encoded*4 +: 4]; - wire [2:0] s_axi_arprot_mux = int_s_axi_arprot[a_grant_encoded*3 +: 3]; - wire [3:0] s_axi_arqos_mux = int_s_axi_arqos[a_grant_encoded*4 +: 4]; - wire [3:0] s_axi_arregion_mux = int_s_axi_arregion[a_grant_encoded*4 +: 4]; - wire [ARUSER_WIDTH-1:0] s_axi_aruser_mux = int_s_axi_aruser[a_grant_encoded*ARUSER_WIDTH +: ARUSER_WIDTH]; - wire s_axi_arvalid_mux = int_axi_arvalid[a_grant_encoded*M_COUNT+n] && a_grant_valid; - wire s_axi_arready_mux; - - assign int_axi_arready[n*S_COUNT +: S_COUNT] = (a_grant_valid && s_axi_arready_mux) << a_grant_encoded; - - for (m = 0; m < S_COUNT; m = m + 1) begin - assign a_request[m] = int_axi_arvalid[m*M_COUNT+n] && !a_grant[m] && !trans_limit; - assign a_acknowledge[m] = a_grant[m] && int_axi_arvalid[m*M_COUNT+n] && s_axi_arready_mux; - end - - assign trans_start = s_axi_arvalid_mux && s_axi_arready_mux && a_grant_valid; - - // read response forwarding - wire [CL_S_COUNT-1:0] r_select = m_axi_rid[n*M_ID_WIDTH +: M_ID_WIDTH] >> S_ID_WIDTH; - - assign int_axi_rvalid[n*S_COUNT +: S_COUNT] = int_m_axi_rvalid[n] << r_select; - assign int_m_axi_rready[n] = int_axi_rready[r_select*M_COUNT+n]; - - assign trans_complete = int_m_axi_rvalid[n] && int_m_axi_rready[n] && int_m_axi_rlast[n]; - - // M side register - axi_register_rd #( - .DATA_WIDTH(DATA_WIDTH), - .ADDR_WIDTH(ADDR_WIDTH), - .STRB_WIDTH(STRB_WIDTH), - .ID_WIDTH(M_ID_WIDTH), - .ARUSER_ENABLE(ARUSER_ENABLE), - .ARUSER_WIDTH(ARUSER_WIDTH), - .RUSER_ENABLE(RUSER_ENABLE), - .RUSER_WIDTH(RUSER_WIDTH), - .AR_REG_TYPE(M_AR_REG_TYPE[n*2 +: 2]), - .R_REG_TYPE(M_R_REG_TYPE[n*2 +: 2]) - ) - reg_inst ( - .clk(clk), - .rst(rst), - .s_axi_arid(s_axi_arid_mux), - .s_axi_araddr(s_axi_araddr_mux), - .s_axi_arlen(s_axi_arlen_mux), - .s_axi_arsize(s_axi_arsize_mux), - .s_axi_arburst(s_axi_arburst_mux), - .s_axi_arlock(s_axi_arlock_mux), - .s_axi_arcache(s_axi_arcache_mux), - .s_axi_arprot(s_axi_arprot_mux), - .s_axi_arqos(s_axi_arqos_mux), - .s_axi_arregion(s_axi_arregion_mux), - .s_axi_aruser(s_axi_aruser_mux), - .s_axi_arvalid(s_axi_arvalid_mux), - .s_axi_arready(s_axi_arready_mux), - .s_axi_rid(int_m_axi_rid[n*M_ID_WIDTH +: M_ID_WIDTH]), - .s_axi_rdata(int_m_axi_rdata[n*DATA_WIDTH +: DATA_WIDTH]), - .s_axi_rresp(int_m_axi_rresp[n*2 +: 2]), - .s_axi_rlast(int_m_axi_rlast[n]), - .s_axi_ruser(int_m_axi_ruser[n*RUSER_WIDTH +: RUSER_WIDTH]), - .s_axi_rvalid(int_m_axi_rvalid[n]), - .s_axi_rready(int_m_axi_rready[n]), - .m_axi_arid(m_axi_arid[n*M_ID_WIDTH +: M_ID_WIDTH]), - .m_axi_araddr(m_axi_araddr[n*ADDR_WIDTH +: ADDR_WIDTH]), - .m_axi_arlen(m_axi_arlen[n*8 +: 8]), - .m_axi_arsize(m_axi_arsize[n*3 +: 3]), - .m_axi_arburst(m_axi_arburst[n*2 +: 2]), - .m_axi_arlock(m_axi_arlock[n]), - .m_axi_arcache(m_axi_arcache[n*4 +: 4]), - .m_axi_arprot(m_axi_arprot[n*3 +: 3]), - .m_axi_arqos(m_axi_arqos[n*4 +: 4]), - .m_axi_arregion(m_axi_arregion[n*4 +: 4]), - .m_axi_aruser(m_axi_aruser[n*ARUSER_WIDTH +: ARUSER_WIDTH]), - .m_axi_arvalid(m_axi_arvalid[n]), - .m_axi_arready(m_axi_arready[n]), - .m_axi_rid(m_axi_rid[n*M_ID_WIDTH +: M_ID_WIDTH]), - .m_axi_rdata(m_axi_rdata[n*DATA_WIDTH +: DATA_WIDTH]), - .m_axi_rresp(m_axi_rresp[n*2 +: 2]), - .m_axi_rlast(m_axi_rlast[n]), - .m_axi_ruser(m_axi_ruser[n*RUSER_WIDTH +: RUSER_WIDTH]), - .m_axi_rvalid(m_axi_rvalid[n]), - .m_axi_rready(m_axi_rready[n]) - ); - end // m_ifaces - -endgenerate - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/axi_crossbar_wr.v b/xls/modules/zstd/external/axi_crossbar_wr.v deleted file mode 100644 index 5f55665351..0000000000 --- a/xls/modules/zstd/external/axi_crossbar_wr.v +++ /dev/null @@ -1,678 +0,0 @@ -/* - -Copyright (c) 2018 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * AXI4 crossbar (write) - */ -module axi_crossbar_wr # -( - // Number of AXI inputs (slave interfaces) - parameter S_COUNT = 4, - // Number of AXI outputs (master interfaces) - parameter M_COUNT = 4, - // Width of data bus in bits - parameter DATA_WIDTH = 32, - // Width of address bus in bits - parameter ADDR_WIDTH = 32, - // Width of wstrb (width of data bus in words) - parameter STRB_WIDTH = (DATA_WIDTH/8), - // Input ID field width (from AXI masters) - parameter S_ID_WIDTH = 8, - // Output ID field width (towards AXI slaves) - // Additional bits required for response routing - parameter M_ID_WIDTH = S_ID_WIDTH+$clog2(S_COUNT), - // Propagate awuser signal - parameter AWUSER_ENABLE = 0, - // Width of awuser signal - parameter AWUSER_WIDTH = 1, - // Propagate wuser signal - parameter WUSER_ENABLE = 0, - // Width of wuser signal - parameter WUSER_WIDTH = 1, - // Propagate buser signal - parameter BUSER_ENABLE = 0, - // Width of buser signal - parameter BUSER_WIDTH = 1, - // Number of concurrent unique IDs for each slave interface - // S_COUNT concatenated fields of 32 bits - parameter S_THREADS = {S_COUNT{32'd2}}, - // Number of concurrent operations for each slave interface - // S_COUNT concatenated fields of 32 bits - parameter S_ACCEPT = {S_COUNT{32'd16}}, - // Number of regions per master interface - parameter M_REGIONS = 1, - // Master interface base addresses - // M_COUNT concatenated fields of M_REGIONS concatenated fields of ADDR_WIDTH bits - // set to zero for default addressing based on M_ADDR_WIDTH - parameter M_BASE_ADDR = 0, - // Master interface address widths - // M_COUNT concatenated fields of M_REGIONS concatenated fields of 32 bits - parameter M_ADDR_WIDTH = {M_COUNT{{M_REGIONS{32'd24}}}}, - // Write connections between interfaces - // M_COUNT concatenated fields of S_COUNT bits - parameter M_CONNECT = {M_COUNT{{S_COUNT{1'b1}}}}, - // Number of concurrent operations for each master interface - // M_COUNT concatenated fields of 32 bits - parameter M_ISSUE = {M_COUNT{32'd4}}, - // Secure master (fail operations based on awprot/arprot) - // M_COUNT bits - parameter M_SECURE = {M_COUNT{1'b0}}, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_AW_REG_TYPE = {S_COUNT{2'd0}}, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_W_REG_TYPE = {S_COUNT{2'd0}}, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S_B_REG_TYPE = {S_COUNT{2'd1}}, - // Master interface AW channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_AW_REG_TYPE = {M_COUNT{2'd1}}, - // Master interface W channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_W_REG_TYPE = {M_COUNT{2'd2}}, - // Master interface B channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M_B_REG_TYPE = {M_COUNT{2'd0}} -) -( - input wire clk, - input wire rst, - - /* - * AXI slave interfaces - */ - input wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_awid, - input wire [S_COUNT*ADDR_WIDTH-1:0] s_axi_awaddr, - input wire [S_COUNT*8-1:0] s_axi_awlen, - input wire [S_COUNT*3-1:0] s_axi_awsize, - input wire [S_COUNT*2-1:0] s_axi_awburst, - input wire [S_COUNT-1:0] s_axi_awlock, - input wire [S_COUNT*4-1:0] s_axi_awcache, - input wire [S_COUNT*3-1:0] s_axi_awprot, - input wire [S_COUNT*4-1:0] s_axi_awqos, - input wire [S_COUNT*AWUSER_WIDTH-1:0] s_axi_awuser, - input wire [S_COUNT-1:0] s_axi_awvalid, - output wire [S_COUNT-1:0] s_axi_awready, - input wire [S_COUNT*DATA_WIDTH-1:0] s_axi_wdata, - input wire [S_COUNT*STRB_WIDTH-1:0] s_axi_wstrb, - input wire [S_COUNT-1:0] s_axi_wlast, - input wire [S_COUNT*WUSER_WIDTH-1:0] s_axi_wuser, - input wire [S_COUNT-1:0] s_axi_wvalid, - output wire [S_COUNT-1:0] s_axi_wready, - output wire [S_COUNT*S_ID_WIDTH-1:0] s_axi_bid, - output wire [S_COUNT*2-1:0] s_axi_bresp, - output wire [S_COUNT*BUSER_WIDTH-1:0] s_axi_buser, - output wire [S_COUNT-1:0] s_axi_bvalid, - input wire [S_COUNT-1:0] s_axi_bready, - - /* - * AXI master interfaces - */ - output wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_awid, - output wire [M_COUNT*ADDR_WIDTH-1:0] m_axi_awaddr, - output wire [M_COUNT*8-1:0] m_axi_awlen, - output wire [M_COUNT*3-1:0] m_axi_awsize, - output wire [M_COUNT*2-1:0] m_axi_awburst, - output wire [M_COUNT-1:0] m_axi_awlock, - output wire [M_COUNT*4-1:0] m_axi_awcache, - output wire [M_COUNT*3-1:0] m_axi_awprot, - output wire [M_COUNT*4-1:0] m_axi_awqos, - output wire [M_COUNT*4-1:0] m_axi_awregion, - output wire [M_COUNT*AWUSER_WIDTH-1:0] m_axi_awuser, - output wire [M_COUNT-1:0] m_axi_awvalid, - input wire [M_COUNT-1:0] m_axi_awready, - output wire [M_COUNT*DATA_WIDTH-1:0] m_axi_wdata, - output wire [M_COUNT*STRB_WIDTH-1:0] m_axi_wstrb, - output wire [M_COUNT-1:0] m_axi_wlast, - output wire [M_COUNT*WUSER_WIDTH-1:0] m_axi_wuser, - output wire [M_COUNT-1:0] m_axi_wvalid, - input wire [M_COUNT-1:0] m_axi_wready, - input wire [M_COUNT*M_ID_WIDTH-1:0] m_axi_bid, - input wire [M_COUNT*2-1:0] m_axi_bresp, - input wire [M_COUNT*BUSER_WIDTH-1:0] m_axi_buser, - input wire [M_COUNT-1:0] m_axi_bvalid, - output wire [M_COUNT-1:0] m_axi_bready -); - -parameter CL_S_COUNT = $clog2(S_COUNT); -parameter CL_M_COUNT = $clog2(M_COUNT); -parameter M_COUNT_P1 = M_COUNT+1; -parameter CL_M_COUNT_P1 = $clog2(M_COUNT_P1); - -integer i; - -// check configuration -initial begin - if (M_ID_WIDTH < S_ID_WIDTH+$clog2(S_COUNT)) begin - $error("Error: M_ID_WIDTH must be at least $clog2(S_COUNT) larger than S_ID_WIDTH (instance %m)"); - $finish; - end - - for (i = 0; i < M_COUNT*M_REGIONS; i = i + 1) begin - if (M_ADDR_WIDTH[i*32 +: 32] && (M_ADDR_WIDTH[i*32 +: 32] < 12 || M_ADDR_WIDTH[i*32 +: 32] > ADDR_WIDTH)) begin - $error("Error: value out of range (instance %m)"); - $finish; - end - end -end - -wire [S_COUNT*S_ID_WIDTH-1:0] int_s_axi_awid; -wire [S_COUNT*ADDR_WIDTH-1:0] int_s_axi_awaddr; -wire [S_COUNT*8-1:0] int_s_axi_awlen; -wire [S_COUNT*3-1:0] int_s_axi_awsize; -wire [S_COUNT*2-1:0] int_s_axi_awburst; -wire [S_COUNT-1:0] int_s_axi_awlock; -wire [S_COUNT*4-1:0] int_s_axi_awcache; -wire [S_COUNT*3-1:0] int_s_axi_awprot; -wire [S_COUNT*4-1:0] int_s_axi_awqos; -wire [S_COUNT*4-1:0] int_s_axi_awregion; -wire [S_COUNT*AWUSER_WIDTH-1:0] int_s_axi_awuser; -wire [S_COUNT-1:0] int_s_axi_awvalid; -wire [S_COUNT-1:0] int_s_axi_awready; - -wire [S_COUNT*M_COUNT-1:0] int_axi_awvalid; -wire [M_COUNT*S_COUNT-1:0] int_axi_awready; - -wire [S_COUNT*DATA_WIDTH-1:0] int_s_axi_wdata; -wire [S_COUNT*STRB_WIDTH-1:0] int_s_axi_wstrb; -wire [S_COUNT-1:0] int_s_axi_wlast; -wire [S_COUNT*WUSER_WIDTH-1:0] int_s_axi_wuser; -wire [S_COUNT-1:0] int_s_axi_wvalid; -wire [S_COUNT-1:0] int_s_axi_wready; - -wire [S_COUNT*M_COUNT-1:0] int_axi_wvalid; -wire [M_COUNT*S_COUNT-1:0] int_axi_wready; - -wire [M_COUNT*M_ID_WIDTH-1:0] int_m_axi_bid; -wire [M_COUNT*2-1:0] int_m_axi_bresp; -wire [M_COUNT*BUSER_WIDTH-1:0] int_m_axi_buser; -wire [M_COUNT-1:0] int_m_axi_bvalid; -wire [M_COUNT-1:0] int_m_axi_bready; - -wire [M_COUNT*S_COUNT-1:0] int_axi_bvalid; -wire [S_COUNT*M_COUNT-1:0] int_axi_bready; - -generate - - genvar m, n; - - for (m = 0; m < S_COUNT; m = m + 1) begin : s_ifaces - // address decode and admission control - wire [CL_M_COUNT-1:0] a_select; - - wire m_axi_avalid; - wire m_axi_aready; - - wire [CL_M_COUNT-1:0] m_wc_select; - wire m_wc_decerr; - wire m_wc_valid; - wire m_wc_ready; - - wire m_rc_decerr; - wire m_rc_valid; - wire m_rc_ready; - - wire [S_ID_WIDTH-1:0] s_cpl_id; - wire s_cpl_valid; - - axi_crossbar_addr #( - .S(m), - .S_COUNT(S_COUNT), - .M_COUNT(M_COUNT), - .ADDR_WIDTH(ADDR_WIDTH), - .ID_WIDTH(S_ID_WIDTH), - .S_THREADS(S_THREADS[m*32 +: 32]), - .S_ACCEPT(S_ACCEPT[m*32 +: 32]), - .M_REGIONS(M_REGIONS), - .M_BASE_ADDR(M_BASE_ADDR), - .M_ADDR_WIDTH(M_ADDR_WIDTH), - .M_CONNECT(M_CONNECT), - .M_SECURE(M_SECURE), - .WC_OUTPUT(1) - ) - addr_inst ( - .clk(clk), - .rst(rst), - - /* - * Address input - */ - .s_axi_aid(int_s_axi_awid[m*S_ID_WIDTH +: S_ID_WIDTH]), - .s_axi_aaddr(int_s_axi_awaddr[m*ADDR_WIDTH +: ADDR_WIDTH]), - .s_axi_aprot(int_s_axi_awprot[m*3 +: 3]), - .s_axi_aqos(int_s_axi_awqos[m*4 +: 4]), - .s_axi_avalid(int_s_axi_awvalid[m]), - .s_axi_aready(int_s_axi_awready[m]), - - /* - * Address output - */ - .m_axi_aregion(int_s_axi_awregion[m*4 +: 4]), - .m_select(a_select), - .m_axi_avalid(m_axi_avalid), - .m_axi_aready(m_axi_aready), - - /* - * Write command output - */ - .m_wc_select(m_wc_select), - .m_wc_decerr(m_wc_decerr), - .m_wc_valid(m_wc_valid), - .m_wc_ready(m_wc_ready), - - /* - * Response command output - */ - .m_rc_decerr(m_rc_decerr), - .m_rc_valid(m_rc_valid), - .m_rc_ready(m_rc_ready), - - /* - * Completion input - */ - .s_cpl_id(s_cpl_id), - .s_cpl_valid(s_cpl_valid) - ); - - assign int_axi_awvalid[m*M_COUNT +: M_COUNT] = m_axi_avalid << a_select; - assign m_axi_aready = int_axi_awready[a_select*S_COUNT+m]; - - // write command handling - reg [CL_M_COUNT-1:0] w_select_reg = 0, w_select_next; - reg w_drop_reg = 1'b0, w_drop_next; - reg w_select_valid_reg = 1'b0, w_select_valid_next; - - assign m_wc_ready = !w_select_valid_reg; - - always @* begin - w_select_next = w_select_reg; - w_drop_next = w_drop_reg && !(int_s_axi_wvalid[m] && int_s_axi_wready[m] && int_s_axi_wlast[m]); - w_select_valid_next = w_select_valid_reg && !(int_s_axi_wvalid[m] && int_s_axi_wready[m] && int_s_axi_wlast[m]); - - if (m_wc_valid && !w_select_valid_reg) begin - w_select_next = m_wc_select; - w_drop_next = m_wc_decerr; - w_select_valid_next = m_wc_valid; - end - end - - always @(posedge clk) begin - if (rst) begin - w_select_valid_reg <= 1'b0; - end else begin - w_select_valid_reg <= w_select_valid_next; - end - - w_select_reg <= w_select_next; - w_drop_reg <= w_drop_next; - end - - // write data forwarding - assign int_axi_wvalid[m*M_COUNT +: M_COUNT] = (int_s_axi_wvalid[m] && w_select_valid_reg && !w_drop_reg) << w_select_reg; - assign int_s_axi_wready[m] = int_axi_wready[w_select_reg*S_COUNT+m] || w_drop_reg; - - // decode error handling - reg [S_ID_WIDTH-1:0] decerr_m_axi_bid_reg = {S_ID_WIDTH{1'b0}}, decerr_m_axi_bid_next; - reg decerr_m_axi_bvalid_reg = 1'b0, decerr_m_axi_bvalid_next; - wire decerr_m_axi_bready; - - assign m_rc_ready = !decerr_m_axi_bvalid_reg; - - always @* begin - decerr_m_axi_bid_next = decerr_m_axi_bid_reg; - decerr_m_axi_bvalid_next = decerr_m_axi_bvalid_reg; - - if (decerr_m_axi_bvalid_reg) begin - if (decerr_m_axi_bready) begin - decerr_m_axi_bvalid_next = 1'b0; - end - end else if (m_rc_valid && m_rc_ready) begin - decerr_m_axi_bid_next = int_s_axi_awid[m*S_ID_WIDTH +: S_ID_WIDTH]; - decerr_m_axi_bvalid_next = 1'b1; - end - end - - always @(posedge clk) begin - if (rst) begin - decerr_m_axi_bvalid_reg <= 1'b0; - end else begin - decerr_m_axi_bvalid_reg <= decerr_m_axi_bvalid_next; - end - - decerr_m_axi_bid_reg <= decerr_m_axi_bid_next; - end - - // write response arbitration - wire [M_COUNT_P1-1:0] b_request; - wire [M_COUNT_P1-1:0] b_acknowledge; - wire [M_COUNT_P1-1:0] b_grant; - wire b_grant_valid; - wire [CL_M_COUNT_P1-1:0] b_grant_encoded; - - arbiter #( - .PORTS(M_COUNT_P1), - .ARB_TYPE_ROUND_ROBIN(1), - .ARB_BLOCK(1), - .ARB_BLOCK_ACK(1), - .ARB_LSB_HIGH_PRIORITY(1) - ) - b_arb_inst ( - .clk(clk), - .rst(rst), - .request(b_request), - .acknowledge(b_acknowledge), - .grant(b_grant), - .grant_valid(b_grant_valid), - .grant_encoded(b_grant_encoded) - ); - - // write response mux - wire [S_ID_WIDTH-1:0] m_axi_bid_mux = {decerr_m_axi_bid_reg, int_m_axi_bid} >> b_grant_encoded*M_ID_WIDTH; - wire [1:0] m_axi_bresp_mux = {2'b11, int_m_axi_bresp} >> b_grant_encoded*2; - wire [BUSER_WIDTH-1:0] m_axi_buser_mux = {{BUSER_WIDTH{1'b0}}, int_m_axi_buser} >> b_grant_encoded*BUSER_WIDTH; - wire m_axi_bvalid_mux = ({decerr_m_axi_bvalid_reg, int_m_axi_bvalid} >> b_grant_encoded) & b_grant_valid; - wire m_axi_bready_mux; - - assign int_axi_bready[m*M_COUNT +: M_COUNT] = (b_grant_valid && m_axi_bready_mux) << b_grant_encoded; - assign decerr_m_axi_bready = (b_grant_valid && m_axi_bready_mux) && (b_grant_encoded == M_COUNT_P1-1); - - for (n = 0; n < M_COUNT; n = n + 1) begin - assign b_request[n] = int_axi_bvalid[n*S_COUNT+m] && !b_grant[n]; - assign b_acknowledge[n] = b_grant[n] && int_axi_bvalid[n*S_COUNT+m] && m_axi_bready_mux; - end - - assign b_request[M_COUNT_P1-1] = decerr_m_axi_bvalid_reg && !b_grant[M_COUNT_P1-1]; - assign b_acknowledge[M_COUNT_P1-1] = b_grant[M_COUNT_P1-1] && decerr_m_axi_bvalid_reg && m_axi_bready_mux; - - assign s_cpl_id = m_axi_bid_mux; - assign s_cpl_valid = m_axi_bvalid_mux && m_axi_bready_mux; - - // S side register - axi_register_wr #( - .DATA_WIDTH(DATA_WIDTH), - .ADDR_WIDTH(ADDR_WIDTH), - .STRB_WIDTH(STRB_WIDTH), - .ID_WIDTH(S_ID_WIDTH), - .AWUSER_ENABLE(AWUSER_ENABLE), - .AWUSER_WIDTH(AWUSER_WIDTH), - .WUSER_ENABLE(WUSER_ENABLE), - .WUSER_WIDTH(WUSER_WIDTH), - .BUSER_ENABLE(BUSER_ENABLE), - .BUSER_WIDTH(BUSER_WIDTH), - .AW_REG_TYPE(S_AW_REG_TYPE[m*2 +: 2]), - .W_REG_TYPE(S_W_REG_TYPE[m*2 +: 2]), - .B_REG_TYPE(S_B_REG_TYPE[m*2 +: 2]) - ) - reg_inst ( - .clk(clk), - .rst(rst), - .s_axi_awid(s_axi_awid[m*S_ID_WIDTH +: S_ID_WIDTH]), - .s_axi_awaddr(s_axi_awaddr[m*ADDR_WIDTH +: ADDR_WIDTH]), - .s_axi_awlen(s_axi_awlen[m*8 +: 8]), - .s_axi_awsize(s_axi_awsize[m*3 +: 3]), - .s_axi_awburst(s_axi_awburst[m*2 +: 2]), - .s_axi_awlock(s_axi_awlock[m]), - .s_axi_awcache(s_axi_awcache[m*4 +: 4]), - .s_axi_awprot(s_axi_awprot[m*3 +: 3]), - .s_axi_awqos(s_axi_awqos[m*4 +: 4]), - .s_axi_awregion(4'd0), - .s_axi_awuser(s_axi_awuser[m*AWUSER_WIDTH +: AWUSER_WIDTH]), - .s_axi_awvalid(s_axi_awvalid[m]), - .s_axi_awready(s_axi_awready[m]), - .s_axi_wdata(s_axi_wdata[m*DATA_WIDTH +: DATA_WIDTH]), - .s_axi_wstrb(s_axi_wstrb[m*STRB_WIDTH +: STRB_WIDTH]), - .s_axi_wlast(s_axi_wlast[m]), - .s_axi_wuser(s_axi_wuser[m*WUSER_WIDTH +: WUSER_WIDTH]), - .s_axi_wvalid(s_axi_wvalid[m]), - .s_axi_wready(s_axi_wready[m]), - .s_axi_bid(s_axi_bid[m*S_ID_WIDTH +: S_ID_WIDTH]), - .s_axi_bresp(s_axi_bresp[m*2 +: 2]), - .s_axi_buser(s_axi_buser[m*BUSER_WIDTH +: BUSER_WIDTH]), - .s_axi_bvalid(s_axi_bvalid[m]), - .s_axi_bready(s_axi_bready[m]), - .m_axi_awid(int_s_axi_awid[m*S_ID_WIDTH +: S_ID_WIDTH]), - .m_axi_awaddr(int_s_axi_awaddr[m*ADDR_WIDTH +: ADDR_WIDTH]), - .m_axi_awlen(int_s_axi_awlen[m*8 +: 8]), - .m_axi_awsize(int_s_axi_awsize[m*3 +: 3]), - .m_axi_awburst(int_s_axi_awburst[m*2 +: 2]), - .m_axi_awlock(int_s_axi_awlock[m]), - .m_axi_awcache(int_s_axi_awcache[m*4 +: 4]), - .m_axi_awprot(int_s_axi_awprot[m*3 +: 3]), - .m_axi_awqos(int_s_axi_awqos[m*4 +: 4]), - .m_axi_awregion(), - .m_axi_awuser(int_s_axi_awuser[m*AWUSER_WIDTH +: AWUSER_WIDTH]), - .m_axi_awvalid(int_s_axi_awvalid[m]), - .m_axi_awready(int_s_axi_awready[m]), - .m_axi_wdata(int_s_axi_wdata[m*DATA_WIDTH +: DATA_WIDTH]), - .m_axi_wstrb(int_s_axi_wstrb[m*STRB_WIDTH +: STRB_WIDTH]), - .m_axi_wlast(int_s_axi_wlast[m]), - .m_axi_wuser(int_s_axi_wuser[m*WUSER_WIDTH +: WUSER_WIDTH]), - .m_axi_wvalid(int_s_axi_wvalid[m]), - .m_axi_wready(int_s_axi_wready[m]), - .m_axi_bid(m_axi_bid_mux), - .m_axi_bresp(m_axi_bresp_mux), - .m_axi_buser(m_axi_buser_mux), - .m_axi_bvalid(m_axi_bvalid_mux), - .m_axi_bready(m_axi_bready_mux) - ); - end // s_ifaces - - for (n = 0; n < M_COUNT; n = n + 1) begin : m_ifaces - // in-flight transaction count - wire trans_start; - wire trans_complete; - reg [$clog2(M_ISSUE[n*32 +: 32]+1)-1:0] trans_count_reg = 0; - - wire trans_limit = trans_count_reg >= M_ISSUE[n*32 +: 32] && !trans_complete; - - always @(posedge clk) begin - if (rst) begin - trans_count_reg <= 0; - end else begin - if (trans_start && !trans_complete) begin - trans_count_reg <= trans_count_reg + 1; - end else if (!trans_start && trans_complete) begin - trans_count_reg <= trans_count_reg - 1; - end - end - end - - // address arbitration - reg [CL_S_COUNT-1:0] w_select_reg = 0, w_select_next; - reg w_select_valid_reg = 1'b0, w_select_valid_next; - reg w_select_new_reg = 1'b0, w_select_new_next; - - wire [S_COUNT-1:0] a_request; - wire [S_COUNT-1:0] a_acknowledge; - wire [S_COUNT-1:0] a_grant; - wire a_grant_valid; - wire [CL_S_COUNT-1:0] a_grant_encoded; - - arbiter #( - .PORTS(S_COUNT), - .ARB_TYPE_ROUND_ROBIN(1), - .ARB_BLOCK(1), - .ARB_BLOCK_ACK(1), - .ARB_LSB_HIGH_PRIORITY(1) - ) - a_arb_inst ( - .clk(clk), - .rst(rst), - .request(a_request), - .acknowledge(a_acknowledge), - .grant(a_grant), - .grant_valid(a_grant_valid), - .grant_encoded(a_grant_encoded) - ); - - // address mux - wire [M_ID_WIDTH-1:0] s_axi_awid_mux = int_s_axi_awid[a_grant_encoded*S_ID_WIDTH +: S_ID_WIDTH] | (a_grant_encoded << S_ID_WIDTH); - wire [ADDR_WIDTH-1:0] s_axi_awaddr_mux = int_s_axi_awaddr[a_grant_encoded*ADDR_WIDTH +: ADDR_WIDTH]; - wire [7:0] s_axi_awlen_mux = int_s_axi_awlen[a_grant_encoded*8 +: 8]; - wire [2:0] s_axi_awsize_mux = int_s_axi_awsize[a_grant_encoded*3 +: 3]; - wire [1:0] s_axi_awburst_mux = int_s_axi_awburst[a_grant_encoded*2 +: 2]; - wire s_axi_awlock_mux = int_s_axi_awlock[a_grant_encoded]; - wire [3:0] s_axi_awcache_mux = int_s_axi_awcache[a_grant_encoded*4 +: 4]; - wire [2:0] s_axi_awprot_mux = int_s_axi_awprot[a_grant_encoded*3 +: 3]; - wire [3:0] s_axi_awqos_mux = int_s_axi_awqos[a_grant_encoded*4 +: 4]; - wire [3:0] s_axi_awregion_mux = int_s_axi_awregion[a_grant_encoded*4 +: 4]; - wire [AWUSER_WIDTH-1:0] s_axi_awuser_mux = int_s_axi_awuser[a_grant_encoded*AWUSER_WIDTH +: AWUSER_WIDTH]; - wire s_axi_awvalid_mux = int_axi_awvalid[a_grant_encoded*M_COUNT+n] && a_grant_valid; - wire s_axi_awready_mux; - - assign int_axi_awready[n*S_COUNT +: S_COUNT] = (a_grant_valid && s_axi_awready_mux) << a_grant_encoded; - - for (m = 0; m < S_COUNT; m = m + 1) begin - assign a_request[m] = int_axi_awvalid[m*M_COUNT+n] && !a_grant[m] && !trans_limit && !w_select_valid_next; - assign a_acknowledge[m] = a_grant[m] && int_axi_awvalid[m*M_COUNT+n] && s_axi_awready_mux; - end - - assign trans_start = s_axi_awvalid_mux && s_axi_awready_mux && a_grant_valid; - - // write data mux - wire [DATA_WIDTH-1:0] s_axi_wdata_mux = int_s_axi_wdata[w_select_reg*DATA_WIDTH +: DATA_WIDTH]; - wire [STRB_WIDTH-1:0] s_axi_wstrb_mux = int_s_axi_wstrb[w_select_reg*STRB_WIDTH +: STRB_WIDTH]; - wire s_axi_wlast_mux = int_s_axi_wlast[w_select_reg]; - wire [WUSER_WIDTH-1:0] s_axi_wuser_mux = int_s_axi_wuser[w_select_reg*WUSER_WIDTH +: WUSER_WIDTH]; - wire s_axi_wvalid_mux = int_axi_wvalid[w_select_reg*M_COUNT+n] && w_select_valid_reg; - wire s_axi_wready_mux; - - assign int_axi_wready[n*S_COUNT +: S_COUNT] = (w_select_valid_reg && s_axi_wready_mux) << w_select_reg; - - // write data routing - always @* begin - w_select_next = w_select_reg; - w_select_valid_next = w_select_valid_reg && !(s_axi_wvalid_mux && s_axi_wready_mux && s_axi_wlast_mux); - w_select_new_next = w_select_new_reg || !a_grant_valid || a_acknowledge; - - if (a_grant_valid && !w_select_valid_reg && w_select_new_reg) begin - w_select_next = a_grant_encoded; - w_select_valid_next = a_grant_valid; - w_select_new_next = 1'b0; - end - end - - always @(posedge clk) begin - if (rst) begin - w_select_valid_reg <= 1'b0; - w_select_new_reg <= 1'b1; - end else begin - w_select_valid_reg <= w_select_valid_next; - w_select_new_reg <= w_select_new_next; - end - - w_select_reg <= w_select_next; - end - - // write response forwarding - wire [CL_S_COUNT-1:0] b_select = m_axi_bid[n*M_ID_WIDTH +: M_ID_WIDTH] >> S_ID_WIDTH; - - assign int_axi_bvalid[n*S_COUNT +: S_COUNT] = int_m_axi_bvalid[n] << b_select; - assign int_m_axi_bready[n] = int_axi_bready[b_select*M_COUNT+n]; - - assign trans_complete = int_m_axi_bvalid[n] && int_m_axi_bready[n]; - - // M side register - axi_register_wr #( - .DATA_WIDTH(DATA_WIDTH), - .ADDR_WIDTH(ADDR_WIDTH), - .STRB_WIDTH(STRB_WIDTH), - .ID_WIDTH(M_ID_WIDTH), - .AWUSER_ENABLE(AWUSER_ENABLE), - .AWUSER_WIDTH(AWUSER_WIDTH), - .WUSER_ENABLE(WUSER_ENABLE), - .WUSER_WIDTH(WUSER_WIDTH), - .BUSER_ENABLE(BUSER_ENABLE), - .BUSER_WIDTH(BUSER_WIDTH), - .AW_REG_TYPE(M_AW_REG_TYPE[n*2 +: 2]), - .W_REG_TYPE(M_W_REG_TYPE[n*2 +: 2]), - .B_REG_TYPE(M_B_REG_TYPE[n*2 +: 2]) - ) - reg_inst ( - .clk(clk), - .rst(rst), - .s_axi_awid(s_axi_awid_mux), - .s_axi_awaddr(s_axi_awaddr_mux), - .s_axi_awlen(s_axi_awlen_mux), - .s_axi_awsize(s_axi_awsize_mux), - .s_axi_awburst(s_axi_awburst_mux), - .s_axi_awlock(s_axi_awlock_mux), - .s_axi_awcache(s_axi_awcache_mux), - .s_axi_awprot(s_axi_awprot_mux), - .s_axi_awqos(s_axi_awqos_mux), - .s_axi_awregion(s_axi_awregion_mux), - .s_axi_awuser(s_axi_awuser_mux), - .s_axi_awvalid(s_axi_awvalid_mux), - .s_axi_awready(s_axi_awready_mux), - .s_axi_wdata(s_axi_wdata_mux), - .s_axi_wstrb(s_axi_wstrb_mux), - .s_axi_wlast(s_axi_wlast_mux), - .s_axi_wuser(s_axi_wuser_mux), - .s_axi_wvalid(s_axi_wvalid_mux), - .s_axi_wready(s_axi_wready_mux), - .s_axi_bid(int_m_axi_bid[n*M_ID_WIDTH +: M_ID_WIDTH]), - .s_axi_bresp(int_m_axi_bresp[n*2 +: 2]), - .s_axi_buser(int_m_axi_buser[n*BUSER_WIDTH +: BUSER_WIDTH]), - .s_axi_bvalid(int_m_axi_bvalid[n]), - .s_axi_bready(int_m_axi_bready[n]), - .m_axi_awid(m_axi_awid[n*M_ID_WIDTH +: M_ID_WIDTH]), - .m_axi_awaddr(m_axi_awaddr[n*ADDR_WIDTH +: ADDR_WIDTH]), - .m_axi_awlen(m_axi_awlen[n*8 +: 8]), - .m_axi_awsize(m_axi_awsize[n*3 +: 3]), - .m_axi_awburst(m_axi_awburst[n*2 +: 2]), - .m_axi_awlock(m_axi_awlock[n]), - .m_axi_awcache(m_axi_awcache[n*4 +: 4]), - .m_axi_awprot(m_axi_awprot[n*3 +: 3]), - .m_axi_awqos(m_axi_awqos[n*4 +: 4]), - .m_axi_awregion(m_axi_awregion[n*4 +: 4]), - .m_axi_awuser(m_axi_awuser[n*AWUSER_WIDTH +: AWUSER_WIDTH]), - .m_axi_awvalid(m_axi_awvalid[n]), - .m_axi_awready(m_axi_awready[n]), - .m_axi_wdata(m_axi_wdata[n*DATA_WIDTH +: DATA_WIDTH]), - .m_axi_wstrb(m_axi_wstrb[n*STRB_WIDTH +: STRB_WIDTH]), - .m_axi_wlast(m_axi_wlast[n]), - .m_axi_wuser(m_axi_wuser[n*WUSER_WIDTH +: WUSER_WIDTH]), - .m_axi_wvalid(m_axi_wvalid[n]), - .m_axi_wready(m_axi_wready[n]), - .m_axi_bid(m_axi_bid[n*M_ID_WIDTH +: M_ID_WIDTH]), - .m_axi_bresp(m_axi_bresp[n*2 +: 2]), - .m_axi_buser(m_axi_buser[n*BUSER_WIDTH +: BUSER_WIDTH]), - .m_axi_bvalid(m_axi_bvalid[n]), - .m_axi_bready(m_axi_bready[n]) - ); - end // m_ifaces - -endgenerate - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/axi_crossbar_wrapper.v b/xls/modules/zstd/external/axi_crossbar_wrapper.v deleted file mode 100644 index 32c95071d0..0000000000 --- a/xls/modules/zstd/external/axi_crossbar_wrapper.v +++ /dev/null @@ -1,1246 +0,0 @@ -/* - -Copyright (c) 2020 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * AXI4 15x1 crossbar (wrapper) - */ -module axi_crossbar_wrapper # -( - // Width of data bus in bits - parameter DATA_WIDTH = 32, - // Width of address bus in bits - parameter ADDR_WIDTH = 32, - // Width of wstrb (width of data bus in words) - parameter STRB_WIDTH = (DATA_WIDTH/8), - // Input ID field width (from AXI masters) - parameter S_ID_WIDTH = 8, - // Output ID field width (towards AXI slaves) - // Additional bits required for response routing - parameter M_ID_WIDTH = S_ID_WIDTH+$clog2(S_COUNT), - // Propagate awuser signal - parameter AWUSER_ENABLE = 0, - // Width of awuser signal - parameter AWUSER_WIDTH = 1, - // Propagate wuser signal - parameter WUSER_ENABLE = 0, - // Width of wuser signal - parameter WUSER_WIDTH = 1, - // Propagate buser signal - parameter BUSER_ENABLE = 0, - // Width of buser signal - parameter BUSER_WIDTH = 1, - // Propagate aruser signal - parameter ARUSER_ENABLE = 0, - // Width of aruser signal - parameter ARUSER_WIDTH = 1, - // Propagate ruser signal - parameter RUSER_ENABLE = 0, - // Width of ruser signal - parameter RUSER_WIDTH = 1, - // Number of concurrent unique IDs - parameter S00_THREADS = 2, - // Number of concurrent operations - parameter S00_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S01_THREADS = 2, - // Number of concurrent operations - parameter S01_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S02_THREADS = 2, - // Number of concurrent operations - parameter S02_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S03_THREADS = 2, - // Number of concurrent operations - parameter S03_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S04_THREADS = 2, - // Number of concurrent operations - parameter S04_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S05_THREADS = 2, - // Number of concurrent operations - parameter S05_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S06_THREADS = 2, - // Number of concurrent operations - parameter S06_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S07_THREADS = 2, - // Number of concurrent operations - parameter S07_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S08_THREADS = 2, - // Number of concurrent operations - parameter S08_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S09_THREADS = 2, - // Number of concurrent operations - parameter S09_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S10_THREADS = 2, - // Number of concurrent operations - parameter S10_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S11_THREADS = 2, - // Number of concurrent operations - parameter S11_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S12_THREADS = 2, - // Number of concurrent operations - parameter S12_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S13_THREADS = 2, - // Number of concurrent operations - parameter S13_ACCEPT = 16, - // Number of concurrent unique IDs - parameter S14_THREADS = 2, - // Number of concurrent operations - parameter S14_ACCEPT = 16, - // Number of regions per master interface - parameter M_REGIONS = 1, - // Master interface base addresses - // M_REGIONS concatenated fields of ADDR_WIDTH bits - parameter M00_BASE_ADDR = 0, - // Master interface address widths - // M_REGIONS concatenated fields of 32 bits - parameter M00_ADDR_WIDTH = {M_REGIONS{32'd24}}, - // Read connections between interfaces - // S_COUNT bits - parameter M00_CONNECT_READ = 15'b111111111111111, - // Write connections between interfaces - // S_COUNT bits - parameter M00_CONNECT_WRITE = 15'b111111111111111, - // Number of concurrent operations for each master interface - parameter M00_ISSUE = 4, - // Secure master (fail operations based on awprot/arprot) - parameter M00_SECURE = 0, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S00_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S00_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S00_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S00_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S00_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S01_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S01_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S01_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S01_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S01_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S02_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S02_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S02_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S02_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S02_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S03_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S03_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S03_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S03_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S03_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S04_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S04_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S04_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S04_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S04_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S05_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S05_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S05_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S05_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S05_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S06_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S06_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S06_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S06_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S06_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S07_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S07_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S07_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S07_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S07_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S08_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S08_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S08_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S08_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S08_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S09_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S09_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S09_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S09_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S09_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S10_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S10_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S10_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S10_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S10_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S11_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S11_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S11_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S11_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S11_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S12_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S12_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S12_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S12_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S12_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S13_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S13_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S13_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S13_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S13_R_REG_TYPE = 2, - // Slave interface AW channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S14_AW_REG_TYPE = 0, - // Slave interface W channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S14_W_REG_TYPE = 0, - // Slave interface B channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S14_B_REG_TYPE = 1, - // Slave interface AR channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S14_AR_REG_TYPE = 0, - // Slave interface R channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter S14_R_REG_TYPE = 2, - // Master interface AW channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M00_AW_REG_TYPE = 1, - // Master interface W channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M00_W_REG_TYPE = 2, - // Master interface B channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M00_B_REG_TYPE = 0, - // Master interface AR channel register type (output) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M00_AR_REG_TYPE = 1, - // Master interface R channel register type (input) - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter M00_R_REG_TYPE = 0 -) -( - input wire clk, - input wire rst, - - /* - * AXI slave interface - */ - input wire [S_ID_WIDTH-1:0] s00_axi_awid, - input wire [ADDR_WIDTH-1:0] s00_axi_awaddr, - input wire [7:0] s00_axi_awlen, - input wire [2:0] s00_axi_awsize, - input wire [1:0] s00_axi_awburst, - input wire s00_axi_awlock, - input wire [3:0] s00_axi_awcache, - input wire [2:0] s00_axi_awprot, - input wire [3:0] s00_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s00_axi_awuser, - input wire s00_axi_awvalid, - output wire s00_axi_awready, - input wire [DATA_WIDTH-1:0] s00_axi_wdata, - input wire [STRB_WIDTH-1:0] s00_axi_wstrb, - input wire s00_axi_wlast, - input wire [WUSER_WIDTH-1:0] s00_axi_wuser, - input wire s00_axi_wvalid, - output wire s00_axi_wready, - output wire [S_ID_WIDTH-1:0] s00_axi_bid, - output wire [1:0] s00_axi_bresp, - output wire [BUSER_WIDTH-1:0] s00_axi_buser, - output wire s00_axi_bvalid, - input wire s00_axi_bready, - input wire [S_ID_WIDTH-1:0] s00_axi_arid, - input wire [ADDR_WIDTH-1:0] s00_axi_araddr, - input wire [7:0] s00_axi_arlen, - input wire [2:0] s00_axi_arsize, - input wire [1:0] s00_axi_arburst, - input wire s00_axi_arlock, - input wire [3:0] s00_axi_arcache, - input wire [2:0] s00_axi_arprot, - input wire [3:0] s00_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s00_axi_aruser, - input wire s00_axi_arvalid, - output wire s00_axi_arready, - output wire [S_ID_WIDTH-1:0] s00_axi_rid, - output wire [DATA_WIDTH-1:0] s00_axi_rdata, - output wire [1:0] s00_axi_rresp, - output wire s00_axi_rlast, - output wire [RUSER_WIDTH-1:0] s00_axi_ruser, - output wire s00_axi_rvalid, - input wire s00_axi_rready, - - input wire [S_ID_WIDTH-1:0] s01_axi_awid, - input wire [ADDR_WIDTH-1:0] s01_axi_awaddr, - input wire [7:0] s01_axi_awlen, - input wire [2:0] s01_axi_awsize, - input wire [1:0] s01_axi_awburst, - input wire s01_axi_awlock, - input wire [3:0] s01_axi_awcache, - input wire [2:0] s01_axi_awprot, - input wire [3:0] s01_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s01_axi_awuser, - input wire s01_axi_awvalid, - output wire s01_axi_awready, - input wire [DATA_WIDTH-1:0] s01_axi_wdata, - input wire [STRB_WIDTH-1:0] s01_axi_wstrb, - input wire s01_axi_wlast, - input wire [WUSER_WIDTH-1:0] s01_axi_wuser, - input wire s01_axi_wvalid, - output wire s01_axi_wready, - output wire [S_ID_WIDTH-1:0] s01_axi_bid, - output wire [1:0] s01_axi_bresp, - output wire [BUSER_WIDTH-1:0] s01_axi_buser, - output wire s01_axi_bvalid, - input wire s01_axi_bready, - input wire [S_ID_WIDTH-1:0] s01_axi_arid, - input wire [ADDR_WIDTH-1:0] s01_axi_araddr, - input wire [7:0] s01_axi_arlen, - input wire [2:0] s01_axi_arsize, - input wire [1:0] s01_axi_arburst, - input wire s01_axi_arlock, - input wire [3:0] s01_axi_arcache, - input wire [2:0] s01_axi_arprot, - input wire [3:0] s01_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s01_axi_aruser, - input wire s01_axi_arvalid, - output wire s01_axi_arready, - output wire [S_ID_WIDTH-1:0] s01_axi_rid, - output wire [DATA_WIDTH-1:0] s01_axi_rdata, - output wire [1:0] s01_axi_rresp, - output wire s01_axi_rlast, - output wire [RUSER_WIDTH-1:0] s01_axi_ruser, - output wire s01_axi_rvalid, - input wire s01_axi_rready, - - input wire [S_ID_WIDTH-1:0] s02_axi_awid, - input wire [ADDR_WIDTH-1:0] s02_axi_awaddr, - input wire [7:0] s02_axi_awlen, - input wire [2:0] s02_axi_awsize, - input wire [1:0] s02_axi_awburst, - input wire s02_axi_awlock, - input wire [3:0] s02_axi_awcache, - input wire [2:0] s02_axi_awprot, - input wire [3:0] s02_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s02_axi_awuser, - input wire s02_axi_awvalid, - output wire s02_axi_awready, - input wire [DATA_WIDTH-1:0] s02_axi_wdata, - input wire [STRB_WIDTH-1:0] s02_axi_wstrb, - input wire s02_axi_wlast, - input wire [WUSER_WIDTH-1:0] s02_axi_wuser, - input wire s02_axi_wvalid, - output wire s02_axi_wready, - output wire [S_ID_WIDTH-1:0] s02_axi_bid, - output wire [1:0] s02_axi_bresp, - output wire [BUSER_WIDTH-1:0] s02_axi_buser, - output wire s02_axi_bvalid, - input wire s02_axi_bready, - input wire [S_ID_WIDTH-1:0] s02_axi_arid, - input wire [ADDR_WIDTH-1:0] s02_axi_araddr, - input wire [7:0] s02_axi_arlen, - input wire [2:0] s02_axi_arsize, - input wire [1:0] s02_axi_arburst, - input wire s02_axi_arlock, - input wire [3:0] s02_axi_arcache, - input wire [2:0] s02_axi_arprot, - input wire [3:0] s02_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s02_axi_aruser, - input wire s02_axi_arvalid, - output wire s02_axi_arready, - output wire [S_ID_WIDTH-1:0] s02_axi_rid, - output wire [DATA_WIDTH-1:0] s02_axi_rdata, - output wire [1:0] s02_axi_rresp, - output wire s02_axi_rlast, - output wire [RUSER_WIDTH-1:0] s02_axi_ruser, - output wire s02_axi_rvalid, - input wire s02_axi_rready, - - input wire [S_ID_WIDTH-1:0] s03_axi_awid, - input wire [ADDR_WIDTH-1:0] s03_axi_awaddr, - input wire [7:0] s03_axi_awlen, - input wire [2:0] s03_axi_awsize, - input wire [1:0] s03_axi_awburst, - input wire s03_axi_awlock, - input wire [3:0] s03_axi_awcache, - input wire [2:0] s03_axi_awprot, - input wire [3:0] s03_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s03_axi_awuser, - input wire s03_axi_awvalid, - output wire s03_axi_awready, - input wire [DATA_WIDTH-1:0] s03_axi_wdata, - input wire [STRB_WIDTH-1:0] s03_axi_wstrb, - input wire s03_axi_wlast, - input wire [WUSER_WIDTH-1:0] s03_axi_wuser, - input wire s03_axi_wvalid, - output wire s03_axi_wready, - output wire [S_ID_WIDTH-1:0] s03_axi_bid, - output wire [1:0] s03_axi_bresp, - output wire [BUSER_WIDTH-1:0] s03_axi_buser, - output wire s03_axi_bvalid, - input wire s03_axi_bready, - input wire [S_ID_WIDTH-1:0] s03_axi_arid, - input wire [ADDR_WIDTH-1:0] s03_axi_araddr, - input wire [7:0] s03_axi_arlen, - input wire [2:0] s03_axi_arsize, - input wire [1:0] s03_axi_arburst, - input wire s03_axi_arlock, - input wire [3:0] s03_axi_arcache, - input wire [2:0] s03_axi_arprot, - input wire [3:0] s03_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s03_axi_aruser, - input wire s03_axi_arvalid, - output wire s03_axi_arready, - output wire [S_ID_WIDTH-1:0] s03_axi_rid, - output wire [DATA_WIDTH-1:0] s03_axi_rdata, - output wire [1:0] s03_axi_rresp, - output wire s03_axi_rlast, - output wire [RUSER_WIDTH-1:0] s03_axi_ruser, - output wire s03_axi_rvalid, - input wire s03_axi_rready, - - input wire [S_ID_WIDTH-1:0] s04_axi_awid, - input wire [ADDR_WIDTH-1:0] s04_axi_awaddr, - input wire [7:0] s04_axi_awlen, - input wire [2:0] s04_axi_awsize, - input wire [1:0] s04_axi_awburst, - input wire s04_axi_awlock, - input wire [3:0] s04_axi_awcache, - input wire [2:0] s04_axi_awprot, - input wire [3:0] s04_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s04_axi_awuser, - input wire s04_axi_awvalid, - output wire s04_axi_awready, - input wire [DATA_WIDTH-1:0] s04_axi_wdata, - input wire [STRB_WIDTH-1:0] s04_axi_wstrb, - input wire s04_axi_wlast, - input wire [WUSER_WIDTH-1:0] s04_axi_wuser, - input wire s04_axi_wvalid, - output wire s04_axi_wready, - output wire [S_ID_WIDTH-1:0] s04_axi_bid, - output wire [1:0] s04_axi_bresp, - output wire [BUSER_WIDTH-1:0] s04_axi_buser, - output wire s04_axi_bvalid, - input wire s04_axi_bready, - input wire [S_ID_WIDTH-1:0] s04_axi_arid, - input wire [ADDR_WIDTH-1:0] s04_axi_araddr, - input wire [7:0] s04_axi_arlen, - input wire [2:0] s04_axi_arsize, - input wire [1:0] s04_axi_arburst, - input wire s04_axi_arlock, - input wire [3:0] s04_axi_arcache, - input wire [2:0] s04_axi_arprot, - input wire [3:0] s04_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s04_axi_aruser, - input wire s04_axi_arvalid, - output wire s04_axi_arready, - output wire [S_ID_WIDTH-1:0] s04_axi_rid, - output wire [DATA_WIDTH-1:0] s04_axi_rdata, - output wire [1:0] s04_axi_rresp, - output wire s04_axi_rlast, - output wire [RUSER_WIDTH-1:0] s04_axi_ruser, - output wire s04_axi_rvalid, - input wire s04_axi_rready, - - input wire [S_ID_WIDTH-1:0] s05_axi_awid, - input wire [ADDR_WIDTH-1:0] s05_axi_awaddr, - input wire [7:0] s05_axi_awlen, - input wire [2:0] s05_axi_awsize, - input wire [1:0] s05_axi_awburst, - input wire s05_axi_awlock, - input wire [3:0] s05_axi_awcache, - input wire [2:0] s05_axi_awprot, - input wire [3:0] s05_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s05_axi_awuser, - input wire s05_axi_awvalid, - output wire s05_axi_awready, - input wire [DATA_WIDTH-1:0] s05_axi_wdata, - input wire [STRB_WIDTH-1:0] s05_axi_wstrb, - input wire s05_axi_wlast, - input wire [WUSER_WIDTH-1:0] s05_axi_wuser, - input wire s05_axi_wvalid, - output wire s05_axi_wready, - output wire [S_ID_WIDTH-1:0] s05_axi_bid, - output wire [1:0] s05_axi_bresp, - output wire [BUSER_WIDTH-1:0] s05_axi_buser, - output wire s05_axi_bvalid, - input wire s05_axi_bready, - input wire [S_ID_WIDTH-1:0] s05_axi_arid, - input wire [ADDR_WIDTH-1:0] s05_axi_araddr, - input wire [7:0] s05_axi_arlen, - input wire [2:0] s05_axi_arsize, - input wire [1:0] s05_axi_arburst, - input wire s05_axi_arlock, - input wire [3:0] s05_axi_arcache, - input wire [2:0] s05_axi_arprot, - input wire [3:0] s05_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s05_axi_aruser, - input wire s05_axi_arvalid, - output wire s05_axi_arready, - output wire [S_ID_WIDTH-1:0] s05_axi_rid, - output wire [DATA_WIDTH-1:0] s05_axi_rdata, - output wire [1:0] s05_axi_rresp, - output wire s05_axi_rlast, - output wire [RUSER_WIDTH-1:0] s05_axi_ruser, - output wire s05_axi_rvalid, - input wire s05_axi_rready, - - input wire [S_ID_WIDTH-1:0] s06_axi_awid, - input wire [ADDR_WIDTH-1:0] s06_axi_awaddr, - input wire [7:0] s06_axi_awlen, - input wire [2:0] s06_axi_awsize, - input wire [1:0] s06_axi_awburst, - input wire s06_axi_awlock, - input wire [3:0] s06_axi_awcache, - input wire [2:0] s06_axi_awprot, - input wire [3:0] s06_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s06_axi_awuser, - input wire s06_axi_awvalid, - output wire s06_axi_awready, - input wire [DATA_WIDTH-1:0] s06_axi_wdata, - input wire [STRB_WIDTH-1:0] s06_axi_wstrb, - input wire s06_axi_wlast, - input wire [WUSER_WIDTH-1:0] s06_axi_wuser, - input wire s06_axi_wvalid, - output wire s06_axi_wready, - output wire [S_ID_WIDTH-1:0] s06_axi_bid, - output wire [1:0] s06_axi_bresp, - output wire [BUSER_WIDTH-1:0] s06_axi_buser, - output wire s06_axi_bvalid, - input wire s06_axi_bready, - input wire [S_ID_WIDTH-1:0] s06_axi_arid, - input wire [ADDR_WIDTH-1:0] s06_axi_araddr, - input wire [7:0] s06_axi_arlen, - input wire [2:0] s06_axi_arsize, - input wire [1:0] s06_axi_arburst, - input wire s06_axi_arlock, - input wire [3:0] s06_axi_arcache, - input wire [2:0] s06_axi_arprot, - input wire [3:0] s06_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s06_axi_aruser, - input wire s06_axi_arvalid, - output wire s06_axi_arready, - output wire [S_ID_WIDTH-1:0] s06_axi_rid, - output wire [DATA_WIDTH-1:0] s06_axi_rdata, - output wire [1:0] s06_axi_rresp, - output wire s06_axi_rlast, - output wire [RUSER_WIDTH-1:0] s06_axi_ruser, - output wire s06_axi_rvalid, - input wire s06_axi_rready, - - input wire [S_ID_WIDTH-1:0] s07_axi_awid, - input wire [ADDR_WIDTH-1:0] s07_axi_awaddr, - input wire [7:0] s07_axi_awlen, - input wire [2:0] s07_axi_awsize, - input wire [1:0] s07_axi_awburst, - input wire s07_axi_awlock, - input wire [3:0] s07_axi_awcache, - input wire [2:0] s07_axi_awprot, - input wire [3:0] s07_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s07_axi_awuser, - input wire s07_axi_awvalid, - output wire s07_axi_awready, - input wire [DATA_WIDTH-1:0] s07_axi_wdata, - input wire [STRB_WIDTH-1:0] s07_axi_wstrb, - input wire s07_axi_wlast, - input wire [WUSER_WIDTH-1:0] s07_axi_wuser, - input wire s07_axi_wvalid, - output wire s07_axi_wready, - output wire [S_ID_WIDTH-1:0] s07_axi_bid, - output wire [1:0] s07_axi_bresp, - output wire [BUSER_WIDTH-1:0] s07_axi_buser, - output wire s07_axi_bvalid, - input wire s07_axi_bready, - input wire [S_ID_WIDTH-1:0] s07_axi_arid, - input wire [ADDR_WIDTH-1:0] s07_axi_araddr, - input wire [7:0] s07_axi_arlen, - input wire [2:0] s07_axi_arsize, - input wire [1:0] s07_axi_arburst, - input wire s07_axi_arlock, - input wire [3:0] s07_axi_arcache, - input wire [2:0] s07_axi_arprot, - input wire [3:0] s07_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s07_axi_aruser, - input wire s07_axi_arvalid, - output wire s07_axi_arready, - output wire [S_ID_WIDTH-1:0] s07_axi_rid, - output wire [DATA_WIDTH-1:0] s07_axi_rdata, - output wire [1:0] s07_axi_rresp, - output wire s07_axi_rlast, - output wire [RUSER_WIDTH-1:0] s07_axi_ruser, - output wire s07_axi_rvalid, - input wire s07_axi_rready, - - input wire [S_ID_WIDTH-1:0] s08_axi_awid, - input wire [ADDR_WIDTH-1:0] s08_axi_awaddr, - input wire [7:0] s08_axi_awlen, - input wire [2:0] s08_axi_awsize, - input wire [1:0] s08_axi_awburst, - input wire s08_axi_awlock, - input wire [3:0] s08_axi_awcache, - input wire [2:0] s08_axi_awprot, - input wire [3:0] s08_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s08_axi_awuser, - input wire s08_axi_awvalid, - output wire s08_axi_awready, - input wire [DATA_WIDTH-1:0] s08_axi_wdata, - input wire [STRB_WIDTH-1:0] s08_axi_wstrb, - input wire s08_axi_wlast, - input wire [WUSER_WIDTH-1:0] s08_axi_wuser, - input wire s08_axi_wvalid, - output wire s08_axi_wready, - output wire [S_ID_WIDTH-1:0] s08_axi_bid, - output wire [1:0] s08_axi_bresp, - output wire [BUSER_WIDTH-1:0] s08_axi_buser, - output wire s08_axi_bvalid, - input wire s08_axi_bready, - input wire [S_ID_WIDTH-1:0] s08_axi_arid, - input wire [ADDR_WIDTH-1:0] s08_axi_araddr, - input wire [7:0] s08_axi_arlen, - input wire [2:0] s08_axi_arsize, - input wire [1:0] s08_axi_arburst, - input wire s08_axi_arlock, - input wire [3:0] s08_axi_arcache, - input wire [2:0] s08_axi_arprot, - input wire [3:0] s08_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s08_axi_aruser, - input wire s08_axi_arvalid, - output wire s08_axi_arready, - output wire [S_ID_WIDTH-1:0] s08_axi_rid, - output wire [DATA_WIDTH-1:0] s08_axi_rdata, - output wire [1:0] s08_axi_rresp, - output wire s08_axi_rlast, - output wire [RUSER_WIDTH-1:0] s08_axi_ruser, - output wire s08_axi_rvalid, - input wire s08_axi_rready, - - input wire [S_ID_WIDTH-1:0] s09_axi_awid, - input wire [ADDR_WIDTH-1:0] s09_axi_awaddr, - input wire [7:0] s09_axi_awlen, - input wire [2:0] s09_axi_awsize, - input wire [1:0] s09_axi_awburst, - input wire s09_axi_awlock, - input wire [3:0] s09_axi_awcache, - input wire [2:0] s09_axi_awprot, - input wire [3:0] s09_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s09_axi_awuser, - input wire s09_axi_awvalid, - output wire s09_axi_awready, - input wire [DATA_WIDTH-1:0] s09_axi_wdata, - input wire [STRB_WIDTH-1:0] s09_axi_wstrb, - input wire s09_axi_wlast, - input wire [WUSER_WIDTH-1:0] s09_axi_wuser, - input wire s09_axi_wvalid, - output wire s09_axi_wready, - output wire [S_ID_WIDTH-1:0] s09_axi_bid, - output wire [1:0] s09_axi_bresp, - output wire [BUSER_WIDTH-1:0] s09_axi_buser, - output wire s09_axi_bvalid, - input wire s09_axi_bready, - input wire [S_ID_WIDTH-1:0] s09_axi_arid, - input wire [ADDR_WIDTH-1:0] s09_axi_araddr, - input wire [7:0] s09_axi_arlen, - input wire [2:0] s09_axi_arsize, - input wire [1:0] s09_axi_arburst, - input wire s09_axi_arlock, - input wire [3:0] s09_axi_arcache, - input wire [2:0] s09_axi_arprot, - input wire [3:0] s09_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s09_axi_aruser, - input wire s09_axi_arvalid, - output wire s09_axi_arready, - output wire [S_ID_WIDTH-1:0] s09_axi_rid, - output wire [DATA_WIDTH-1:0] s09_axi_rdata, - output wire [1:0] s09_axi_rresp, - output wire s09_axi_rlast, - output wire [RUSER_WIDTH-1:0] s09_axi_ruser, - output wire s09_axi_rvalid, - input wire s09_axi_rready, - - input wire [S_ID_WIDTH-1:0] s10_axi_awid, - input wire [ADDR_WIDTH-1:0] s10_axi_awaddr, - input wire [7:0] s10_axi_awlen, - input wire [2:0] s10_axi_awsize, - input wire [1:0] s10_axi_awburst, - input wire s10_axi_awlock, - input wire [3:0] s10_axi_awcache, - input wire [2:0] s10_axi_awprot, - input wire [3:0] s10_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s10_axi_awuser, - input wire s10_axi_awvalid, - output wire s10_axi_awready, - input wire [DATA_WIDTH-1:0] s10_axi_wdata, - input wire [STRB_WIDTH-1:0] s10_axi_wstrb, - input wire s10_axi_wlast, - input wire [WUSER_WIDTH-1:0] s10_axi_wuser, - input wire s10_axi_wvalid, - output wire s10_axi_wready, - output wire [S_ID_WIDTH-1:0] s10_axi_bid, - output wire [1:0] s10_axi_bresp, - output wire [BUSER_WIDTH-1:0] s10_axi_buser, - output wire s10_axi_bvalid, - input wire s10_axi_bready, - input wire [S_ID_WIDTH-1:0] s10_axi_arid, - input wire [ADDR_WIDTH-1:0] s10_axi_araddr, - input wire [7:0] s10_axi_arlen, - input wire [2:0] s10_axi_arsize, - input wire [1:0] s10_axi_arburst, - input wire s10_axi_arlock, - input wire [3:0] s10_axi_arcache, - input wire [2:0] s10_axi_arprot, - input wire [3:0] s10_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s10_axi_aruser, - input wire s10_axi_arvalid, - output wire s10_axi_arready, - output wire [S_ID_WIDTH-1:0] s10_axi_rid, - output wire [DATA_WIDTH-1:0] s10_axi_rdata, - output wire [1:0] s10_axi_rresp, - output wire s10_axi_rlast, - output wire [RUSER_WIDTH-1:0] s10_axi_ruser, - output wire s10_axi_rvalid, - input wire s10_axi_rready, - - input wire [S_ID_WIDTH-1:0] s11_axi_awid, - input wire [ADDR_WIDTH-1:0] s11_axi_awaddr, - input wire [7:0] s11_axi_awlen, - input wire [2:0] s11_axi_awsize, - input wire [1:0] s11_axi_awburst, - input wire s11_axi_awlock, - input wire [3:0] s11_axi_awcache, - input wire [2:0] s11_axi_awprot, - input wire [3:0] s11_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s11_axi_awuser, - input wire s11_axi_awvalid, - output wire s11_axi_awready, - input wire [DATA_WIDTH-1:0] s11_axi_wdata, - input wire [STRB_WIDTH-1:0] s11_axi_wstrb, - input wire s11_axi_wlast, - input wire [WUSER_WIDTH-1:0] s11_axi_wuser, - input wire s11_axi_wvalid, - output wire s11_axi_wready, - output wire [S_ID_WIDTH-1:0] s11_axi_bid, - output wire [1:0] s11_axi_bresp, - output wire [BUSER_WIDTH-1:0] s11_axi_buser, - output wire s11_axi_bvalid, - input wire s11_axi_bready, - input wire [S_ID_WIDTH-1:0] s11_axi_arid, - input wire [ADDR_WIDTH-1:0] s11_axi_araddr, - input wire [7:0] s11_axi_arlen, - input wire [2:0] s11_axi_arsize, - input wire [1:0] s11_axi_arburst, - input wire s11_axi_arlock, - input wire [3:0] s11_axi_arcache, - input wire [2:0] s11_axi_arprot, - input wire [3:0] s11_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s11_axi_aruser, - input wire s11_axi_arvalid, - output wire s11_axi_arready, - output wire [S_ID_WIDTH-1:0] s11_axi_rid, - output wire [DATA_WIDTH-1:0] s11_axi_rdata, - output wire [1:0] s11_axi_rresp, - output wire s11_axi_rlast, - output wire [RUSER_WIDTH-1:0] s11_axi_ruser, - output wire s11_axi_rvalid, - input wire s11_axi_rready, - - input wire [S_ID_WIDTH-1:0] s12_axi_awid, - input wire [ADDR_WIDTH-1:0] s12_axi_awaddr, - input wire [7:0] s12_axi_awlen, - input wire [2:0] s12_axi_awsize, - input wire [1:0] s12_axi_awburst, - input wire s12_axi_awlock, - input wire [3:0] s12_axi_awcache, - input wire [2:0] s12_axi_awprot, - input wire [3:0] s12_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s12_axi_awuser, - input wire s12_axi_awvalid, - output wire s12_axi_awready, - input wire [DATA_WIDTH-1:0] s12_axi_wdata, - input wire [STRB_WIDTH-1:0] s12_axi_wstrb, - input wire s12_axi_wlast, - input wire [WUSER_WIDTH-1:0] s12_axi_wuser, - input wire s12_axi_wvalid, - output wire s12_axi_wready, - output wire [S_ID_WIDTH-1:0] s12_axi_bid, - output wire [1:0] s12_axi_bresp, - output wire [BUSER_WIDTH-1:0] s12_axi_buser, - output wire s12_axi_bvalid, - input wire s12_axi_bready, - input wire [S_ID_WIDTH-1:0] s12_axi_arid, - input wire [ADDR_WIDTH-1:0] s12_axi_araddr, - input wire [7:0] s12_axi_arlen, - input wire [2:0] s12_axi_arsize, - input wire [1:0] s12_axi_arburst, - input wire s12_axi_arlock, - input wire [3:0] s12_axi_arcache, - input wire [2:0] s12_axi_arprot, - input wire [3:0] s12_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s12_axi_aruser, - input wire s12_axi_arvalid, - output wire s12_axi_arready, - output wire [S_ID_WIDTH-1:0] s12_axi_rid, - output wire [DATA_WIDTH-1:0] s12_axi_rdata, - output wire [1:0] s12_axi_rresp, - output wire s12_axi_rlast, - output wire [RUSER_WIDTH-1:0] s12_axi_ruser, - output wire s12_axi_rvalid, - input wire s12_axi_rready, - - input wire [S_ID_WIDTH-1:0] s13_axi_awid, - input wire [ADDR_WIDTH-1:0] s13_axi_awaddr, - input wire [7:0] s13_axi_awlen, - input wire [2:0] s13_axi_awsize, - input wire [1:0] s13_axi_awburst, - input wire s13_axi_awlock, - input wire [3:0] s13_axi_awcache, - input wire [2:0] s13_axi_awprot, - input wire [3:0] s13_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s13_axi_awuser, - input wire s13_axi_awvalid, - output wire s13_axi_awready, - input wire [DATA_WIDTH-1:0] s13_axi_wdata, - input wire [STRB_WIDTH-1:0] s13_axi_wstrb, - input wire s13_axi_wlast, - input wire [WUSER_WIDTH-1:0] s13_axi_wuser, - input wire s13_axi_wvalid, - output wire s13_axi_wready, - output wire [S_ID_WIDTH-1:0] s13_axi_bid, - output wire [1:0] s13_axi_bresp, - output wire [BUSER_WIDTH-1:0] s13_axi_buser, - output wire s13_axi_bvalid, - input wire s13_axi_bready, - input wire [S_ID_WIDTH-1:0] s13_axi_arid, - input wire [ADDR_WIDTH-1:0] s13_axi_araddr, - input wire [7:0] s13_axi_arlen, - input wire [2:0] s13_axi_arsize, - input wire [1:0] s13_axi_arburst, - input wire s13_axi_arlock, - input wire [3:0] s13_axi_arcache, - input wire [2:0] s13_axi_arprot, - input wire [3:0] s13_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s13_axi_aruser, - input wire s13_axi_arvalid, - output wire s13_axi_arready, - output wire [S_ID_WIDTH-1:0] s13_axi_rid, - output wire [DATA_WIDTH-1:0] s13_axi_rdata, - output wire [1:0] s13_axi_rresp, - output wire s13_axi_rlast, - output wire [RUSER_WIDTH-1:0] s13_axi_ruser, - output wire s13_axi_rvalid, - input wire s13_axi_rready, - - input wire [S_ID_WIDTH-1:0] s14_axi_awid, - input wire [ADDR_WIDTH-1:0] s14_axi_awaddr, - input wire [7:0] s14_axi_awlen, - input wire [2:0] s14_axi_awsize, - input wire [1:0] s14_axi_awburst, - input wire s14_axi_awlock, - input wire [3:0] s14_axi_awcache, - input wire [2:0] s14_axi_awprot, - input wire [3:0] s14_axi_awqos, - input wire [AWUSER_WIDTH-1:0] s14_axi_awuser, - input wire s14_axi_awvalid, - output wire s14_axi_awready, - input wire [DATA_WIDTH-1:0] s14_axi_wdata, - input wire [STRB_WIDTH-1:0] s14_axi_wstrb, - input wire s14_axi_wlast, - input wire [WUSER_WIDTH-1:0] s14_axi_wuser, - input wire s14_axi_wvalid, - output wire s14_axi_wready, - output wire [S_ID_WIDTH-1:0] s14_axi_bid, - output wire [1:0] s14_axi_bresp, - output wire [BUSER_WIDTH-1:0] s14_axi_buser, - output wire s14_axi_bvalid, - input wire s14_axi_bready, - input wire [S_ID_WIDTH-1:0] s14_axi_arid, - input wire [ADDR_WIDTH-1:0] s14_axi_araddr, - input wire [7:0] s14_axi_arlen, - input wire [2:0] s14_axi_arsize, - input wire [1:0] s14_axi_arburst, - input wire s14_axi_arlock, - input wire [3:0] s14_axi_arcache, - input wire [2:0] s14_axi_arprot, - input wire [3:0] s14_axi_arqos, - input wire [ARUSER_WIDTH-1:0] s14_axi_aruser, - input wire s14_axi_arvalid, - output wire s14_axi_arready, - output wire [S_ID_WIDTH-1:0] s14_axi_rid, - output wire [DATA_WIDTH-1:0] s14_axi_rdata, - output wire [1:0] s14_axi_rresp, - output wire s14_axi_rlast, - output wire [RUSER_WIDTH-1:0] s14_axi_ruser, - output wire s14_axi_rvalid, - input wire s14_axi_rready, - - /* - * AXI master interface - */ - output wire [M_ID_WIDTH-1:0] m00_axi_awid, - output wire [ADDR_WIDTH-1:0] m00_axi_awaddr, - output wire [7:0] m00_axi_awlen, - output wire [2:0] m00_axi_awsize, - output wire [1:0] m00_axi_awburst, - output wire m00_axi_awlock, - output wire [3:0] m00_axi_awcache, - output wire [2:0] m00_axi_awprot, - output wire [3:0] m00_axi_awqos, - output wire [3:0] m00_axi_awregion, - output wire [AWUSER_WIDTH-1:0] m00_axi_awuser, - output wire m00_axi_awvalid, - input wire m00_axi_awready, - output wire [DATA_WIDTH-1:0] m00_axi_wdata, - output wire [STRB_WIDTH-1:0] m00_axi_wstrb, - output wire m00_axi_wlast, - output wire [WUSER_WIDTH-1:0] m00_axi_wuser, - output wire m00_axi_wvalid, - input wire m00_axi_wready, - input wire [M_ID_WIDTH-1:0] m00_axi_bid, - input wire [1:0] m00_axi_bresp, - input wire [BUSER_WIDTH-1:0] m00_axi_buser, - input wire m00_axi_bvalid, - output wire m00_axi_bready, - output wire [M_ID_WIDTH-1:0] m00_axi_arid, - output wire [ADDR_WIDTH-1:0] m00_axi_araddr, - output wire [7:0] m00_axi_arlen, - output wire [2:0] m00_axi_arsize, - output wire [1:0] m00_axi_arburst, - output wire m00_axi_arlock, - output wire [3:0] m00_axi_arcache, - output wire [2:0] m00_axi_arprot, - output wire [3:0] m00_axi_arqos, - output wire [3:0] m00_axi_arregion, - output wire [ARUSER_WIDTH-1:0] m00_axi_aruser, - output wire m00_axi_arvalid, - input wire m00_axi_arready, - input wire [M_ID_WIDTH-1:0] m00_axi_rid, - input wire [DATA_WIDTH-1:0] m00_axi_rdata, - input wire [1:0] m00_axi_rresp, - input wire m00_axi_rlast, - input wire [RUSER_WIDTH-1:0] m00_axi_ruser, - input wire m00_axi_rvalid, - output wire m00_axi_rready -); - -localparam S_COUNT = 15; -localparam M_COUNT = 1; - -// parameter sizing helpers -function [ADDR_WIDTH*M_REGIONS-1:0] w_a_r(input [ADDR_WIDTH*M_REGIONS-1:0] val); - w_a_r = val; -endfunction - -function [32*M_REGIONS-1:0] w_32_r(input [32*M_REGIONS-1:0] val); - w_32_r = val; -endfunction - -function [S_COUNT-1:0] w_s(input [S_COUNT-1:0] val); - w_s = val; -endfunction - -function [31:0] w_32(input [31:0] val); - w_32 = val; -endfunction - -function [1:0] w_2(input [1:0] val); - w_2 = val; -endfunction - -function w_1(input val); - w_1 = val; -endfunction - -axi_crossbar #( - .S_COUNT(S_COUNT), - .M_COUNT(M_COUNT), - .DATA_WIDTH(DATA_WIDTH), - .ADDR_WIDTH(ADDR_WIDTH), - .STRB_WIDTH(STRB_WIDTH), - .S_ID_WIDTH(S_ID_WIDTH), - .M_ID_WIDTH(M_ID_WIDTH), - .AWUSER_ENABLE(AWUSER_ENABLE), - .AWUSER_WIDTH(AWUSER_WIDTH), - .WUSER_ENABLE(WUSER_ENABLE), - .WUSER_WIDTH(WUSER_WIDTH), - .BUSER_ENABLE(BUSER_ENABLE), - .BUSER_WIDTH(BUSER_WIDTH), - .ARUSER_ENABLE(ARUSER_ENABLE), - .ARUSER_WIDTH(ARUSER_WIDTH), - .RUSER_ENABLE(RUSER_ENABLE), - .RUSER_WIDTH(RUSER_WIDTH), - .S_THREADS({ w_32(S14_THREADS), w_32(S13_THREADS), w_32(S12_THREADS), w_32(S11_THREADS), w_32(S10_THREADS), w_32(S09_THREADS), w_32(S08_THREADS), w_32(S07_THREADS), w_32(S06_THREADS), w_32(S05_THREADS), w_32(S04_THREADS), w_32(S03_THREADS), w_32(S02_THREADS), w_32(S01_THREADS), w_32(S00_THREADS) }), - .S_ACCEPT({ w_32(S14_ACCEPT), w_32(S13_ACCEPT), w_32(S12_ACCEPT), w_32(S11_ACCEPT), w_32(S10_ACCEPT), w_32(S09_ACCEPT), w_32(S08_ACCEPT), w_32(S07_ACCEPT), w_32(S06_ACCEPT), w_32(S05_ACCEPT), w_32(S04_ACCEPT), w_32(S03_ACCEPT), w_32(S02_ACCEPT), w_32(S01_ACCEPT), w_32(S00_ACCEPT) }), - .M_REGIONS(M_REGIONS), - .M_BASE_ADDR({ w_a_r(M00_BASE_ADDR) }), - .M_ADDR_WIDTH({ w_32_r(M00_ADDR_WIDTH) }), - .M_CONNECT_READ({ w_s(M00_CONNECT_READ) }), - .M_CONNECT_WRITE({ w_s(M00_CONNECT_WRITE) }), - .M_ISSUE({ w_32(M00_ISSUE) }), - .M_SECURE({ w_1(M00_SECURE) }), - .S_AR_REG_TYPE({ w_2(S14_AR_REG_TYPE), w_2(S13_AR_REG_TYPE), w_2(S12_AR_REG_TYPE), w_2(S11_AR_REG_TYPE), w_2(S10_AR_REG_TYPE), w_2(S09_AR_REG_TYPE), w_2(S08_AR_REG_TYPE), w_2(S07_AR_REG_TYPE), w_2(S06_AR_REG_TYPE), w_2(S05_AR_REG_TYPE), w_2(S04_AR_REG_TYPE), w_2(S03_AR_REG_TYPE), w_2(S02_AR_REG_TYPE), w_2(S01_AR_REG_TYPE), w_2(S00_AR_REG_TYPE) }), - .S_R_REG_TYPE({ w_2(S14_R_REG_TYPE), w_2(S13_R_REG_TYPE), w_2(S12_R_REG_TYPE), w_2(S11_R_REG_TYPE), w_2(S10_R_REG_TYPE), w_2(S09_R_REG_TYPE), w_2(S08_R_REG_TYPE), w_2(S07_R_REG_TYPE), w_2(S06_R_REG_TYPE), w_2(S05_R_REG_TYPE), w_2(S04_R_REG_TYPE), w_2(S03_R_REG_TYPE), w_2(S02_R_REG_TYPE), w_2(S01_R_REG_TYPE), w_2(S00_R_REG_TYPE) }), - .S_AW_REG_TYPE({ w_2(S14_AW_REG_TYPE), w_2(S13_AW_REG_TYPE), w_2(S12_AW_REG_TYPE), w_2(S11_AW_REG_TYPE), w_2(S10_AW_REG_TYPE), w_2(S09_AW_REG_TYPE), w_2(S08_AW_REG_TYPE), w_2(S07_AW_REG_TYPE), w_2(S06_AW_REG_TYPE), w_2(S05_AW_REG_TYPE), w_2(S04_AW_REG_TYPE), w_2(S03_AW_REG_TYPE), w_2(S02_AW_REG_TYPE), w_2(S01_AW_REG_TYPE), w_2(S00_AW_REG_TYPE) }), - .S_W_REG_TYPE({ w_2(S14_W_REG_TYPE), w_2(S13_W_REG_TYPE), w_2(S12_W_REG_TYPE), w_2(S11_W_REG_TYPE), w_2(S10_W_REG_TYPE), w_2(S09_W_REG_TYPE), w_2(S08_W_REG_TYPE), w_2(S07_W_REG_TYPE), w_2(S06_W_REG_TYPE), w_2(S05_W_REG_TYPE), w_2(S04_W_REG_TYPE), w_2(S03_W_REG_TYPE), w_2(S02_W_REG_TYPE), w_2(S01_W_REG_TYPE), w_2(S00_W_REG_TYPE) }), - .S_B_REG_TYPE({ w_2(S14_B_REG_TYPE), w_2(S13_B_REG_TYPE), w_2(S12_B_REG_TYPE), w_2(S11_B_REG_TYPE), w_2(S10_B_REG_TYPE), w_2(S09_B_REG_TYPE), w_2(S08_B_REG_TYPE), w_2(S07_B_REG_TYPE), w_2(S06_B_REG_TYPE), w_2(S05_B_REG_TYPE), w_2(S04_B_REG_TYPE), w_2(S03_B_REG_TYPE), w_2(S02_B_REG_TYPE), w_2(S01_B_REG_TYPE), w_2(S00_B_REG_TYPE) }), - .M_AR_REG_TYPE({ w_2(M00_AR_REG_TYPE) }), - .M_R_REG_TYPE({ w_2(M00_R_REG_TYPE) }), - .M_AW_REG_TYPE({ w_2(M00_AW_REG_TYPE) }), - .M_W_REG_TYPE({ w_2(M00_W_REG_TYPE) }), - .M_B_REG_TYPE({ w_2(M00_B_REG_TYPE) }) -) -axi_crossbar_inst ( - .clk(clk), - .rst(rst), - .s_axi_awid({ s14_axi_awid, s13_axi_awid, s12_axi_awid, s11_axi_awid, s10_axi_awid, s09_axi_awid, s08_axi_awid, s07_axi_awid, s06_axi_awid, s05_axi_awid, s04_axi_awid, s03_axi_awid, s02_axi_awid, s01_axi_awid, s00_axi_awid }), - .s_axi_awaddr({ s14_axi_awaddr, s13_axi_awaddr, s12_axi_awaddr, s11_axi_awaddr, s10_axi_awaddr, s09_axi_awaddr, s08_axi_awaddr, s07_axi_awaddr, s06_axi_awaddr, s05_axi_awaddr, s04_axi_awaddr, s03_axi_awaddr, s02_axi_awaddr, s01_axi_awaddr, s00_axi_awaddr }), - .s_axi_awlen({ s14_axi_awlen, s13_axi_awlen, s12_axi_awlen, s11_axi_awlen, s10_axi_awlen, s09_axi_awlen, s08_axi_awlen, s07_axi_awlen, s06_axi_awlen, s05_axi_awlen, s04_axi_awlen, s03_axi_awlen, s02_axi_awlen, s01_axi_awlen, s00_axi_awlen }), - .s_axi_awsize({ s14_axi_awsize, s13_axi_awsize, s12_axi_awsize, s11_axi_awsize, s10_axi_awsize, s09_axi_awsize, s08_axi_awsize, s07_axi_awsize, s06_axi_awsize, s05_axi_awsize, s04_axi_awsize, s03_axi_awsize, s02_axi_awsize, s01_axi_awsize, s00_axi_awsize }), - .s_axi_awburst({ s14_axi_awburst, s13_axi_awburst, s12_axi_awburst, s11_axi_awburst, s10_axi_awburst, s09_axi_awburst, s08_axi_awburst, s07_axi_awburst, s06_axi_awburst, s05_axi_awburst, s04_axi_awburst, s03_axi_awburst, s02_axi_awburst, s01_axi_awburst, s00_axi_awburst }), - .s_axi_awlock({ s14_axi_awlock, s13_axi_awlock, s12_axi_awlock, s11_axi_awlock, s10_axi_awlock, s09_axi_awlock, s08_axi_awlock, s07_axi_awlock, s06_axi_awlock, s05_axi_awlock, s04_axi_awlock, s03_axi_awlock, s02_axi_awlock, s01_axi_awlock, s00_axi_awlock }), - .s_axi_awcache({ s14_axi_awcache, s13_axi_awcache, s12_axi_awcache, s11_axi_awcache, s10_axi_awcache, s09_axi_awcache, s08_axi_awcache, s07_axi_awcache, s06_axi_awcache, s05_axi_awcache, s04_axi_awcache, s03_axi_awcache, s02_axi_awcache, s01_axi_awcache, s00_axi_awcache }), - .s_axi_awprot({ s14_axi_awprot, s13_axi_awprot, s12_axi_awprot, s11_axi_awprot, s10_axi_awprot, s09_axi_awprot, s08_axi_awprot, s07_axi_awprot, s06_axi_awprot, s05_axi_awprot, s04_axi_awprot, s03_axi_awprot, s02_axi_awprot, s01_axi_awprot, s00_axi_awprot }), - .s_axi_awqos({ s14_axi_awqos, s13_axi_awqos, s12_axi_awqos, s11_axi_awqos, s10_axi_awqos, s09_axi_awqos, s08_axi_awqos, s07_axi_awqos, s06_axi_awqos, s05_axi_awqos, s04_axi_awqos, s03_axi_awqos, s02_axi_awqos, s01_axi_awqos, s00_axi_awqos }), - .s_axi_awuser({ s14_axi_awuser, s13_axi_awuser, s12_axi_awuser, s11_axi_awuser, s10_axi_awuser, s09_axi_awuser, s08_axi_awuser, s07_axi_awuser, s06_axi_awuser, s05_axi_awuser, s04_axi_awuser, s03_axi_awuser, s02_axi_awuser, s01_axi_awuser, s00_axi_awuser }), - .s_axi_awvalid({ s14_axi_awvalid, s13_axi_awvalid, s12_axi_awvalid, s11_axi_awvalid, s10_axi_awvalid, s09_axi_awvalid, s08_axi_awvalid, s07_axi_awvalid, s06_axi_awvalid, s05_axi_awvalid, s04_axi_awvalid, s03_axi_awvalid, s02_axi_awvalid, s01_axi_awvalid, s00_axi_awvalid }), - .s_axi_awready({ s14_axi_awready, s13_axi_awready, s12_axi_awready, s11_axi_awready, s10_axi_awready, s09_axi_awready, s08_axi_awready, s07_axi_awready, s06_axi_awready, s05_axi_awready, s04_axi_awready, s03_axi_awready, s02_axi_awready, s01_axi_awready, s00_axi_awready }), - .s_axi_wdata({ s14_axi_wdata, s13_axi_wdata, s12_axi_wdata, s11_axi_wdata, s10_axi_wdata, s09_axi_wdata, s08_axi_wdata, s07_axi_wdata, s06_axi_wdata, s05_axi_wdata, s04_axi_wdata, s03_axi_wdata, s02_axi_wdata, s01_axi_wdata, s00_axi_wdata }), - .s_axi_wstrb({ s14_axi_wstrb, s13_axi_wstrb, s12_axi_wstrb, s11_axi_wstrb, s10_axi_wstrb, s09_axi_wstrb, s08_axi_wstrb, s07_axi_wstrb, s06_axi_wstrb, s05_axi_wstrb, s04_axi_wstrb, s03_axi_wstrb, s02_axi_wstrb, s01_axi_wstrb, s00_axi_wstrb }), - .s_axi_wlast({ s14_axi_wlast, s13_axi_wlast, s12_axi_wlast, s11_axi_wlast, s10_axi_wlast, s09_axi_wlast, s08_axi_wlast, s07_axi_wlast, s06_axi_wlast, s05_axi_wlast, s04_axi_wlast, s03_axi_wlast, s02_axi_wlast, s01_axi_wlast, s00_axi_wlast }), - .s_axi_wuser({ s14_axi_wuser, s13_axi_wuser, s12_axi_wuser, s11_axi_wuser, s10_axi_wuser, s09_axi_wuser, s08_axi_wuser, s07_axi_wuser, s06_axi_wuser, s05_axi_wuser, s04_axi_wuser, s03_axi_wuser, s02_axi_wuser, s01_axi_wuser, s00_axi_wuser }), - .s_axi_wvalid({ s14_axi_wvalid, s13_axi_wvalid, s12_axi_wvalid, s11_axi_wvalid, s10_axi_wvalid, s09_axi_wvalid, s08_axi_wvalid, s07_axi_wvalid, s06_axi_wvalid, s05_axi_wvalid, s04_axi_wvalid, s03_axi_wvalid, s02_axi_wvalid, s01_axi_wvalid, s00_axi_wvalid }), - .s_axi_wready({ s14_axi_wready, s13_axi_wready, s12_axi_wready, s11_axi_wready, s10_axi_wready, s09_axi_wready, s08_axi_wready, s07_axi_wready, s06_axi_wready, s05_axi_wready, s04_axi_wready, s03_axi_wready, s02_axi_wready, s01_axi_wready, s00_axi_wready }), - .s_axi_bid({ s14_axi_bid, s13_axi_bid, s12_axi_bid, s11_axi_bid, s10_axi_bid, s09_axi_bid, s08_axi_bid, s07_axi_bid, s06_axi_bid, s05_axi_bid, s04_axi_bid, s03_axi_bid, s02_axi_bid, s01_axi_bid, s00_axi_bid }), - .s_axi_bresp({ s14_axi_bresp, s13_axi_bresp, s12_axi_bresp, s11_axi_bresp, s10_axi_bresp, s09_axi_bresp, s08_axi_bresp, s07_axi_bresp, s06_axi_bresp, s05_axi_bresp, s04_axi_bresp, s03_axi_bresp, s02_axi_bresp, s01_axi_bresp, s00_axi_bresp }), - .s_axi_buser({ s14_axi_buser, s13_axi_buser, s12_axi_buser, s11_axi_buser, s10_axi_buser, s09_axi_buser, s08_axi_buser, s07_axi_buser, s06_axi_buser, s05_axi_buser, s04_axi_buser, s03_axi_buser, s02_axi_buser, s01_axi_buser, s00_axi_buser }), - .s_axi_bvalid({ s14_axi_bvalid, s13_axi_bvalid, s12_axi_bvalid, s11_axi_bvalid, s10_axi_bvalid, s09_axi_bvalid, s08_axi_bvalid, s07_axi_bvalid, s06_axi_bvalid, s05_axi_bvalid, s04_axi_bvalid, s03_axi_bvalid, s02_axi_bvalid, s01_axi_bvalid, s00_axi_bvalid }), - .s_axi_bready({ s14_axi_bready, s13_axi_bready, s12_axi_bready, s11_axi_bready, s10_axi_bready, s09_axi_bready, s08_axi_bready, s07_axi_bready, s06_axi_bready, s05_axi_bready, s04_axi_bready, s03_axi_bready, s02_axi_bready, s01_axi_bready, s00_axi_bready }), - .s_axi_arid({ s14_axi_arid, s13_axi_arid, s12_axi_arid, s11_axi_arid, s10_axi_arid, s09_axi_arid, s08_axi_arid, s07_axi_arid, s06_axi_arid, s05_axi_arid, s04_axi_arid, s03_axi_arid, s02_axi_arid, s01_axi_arid, s00_axi_arid }), - .s_axi_araddr({ s14_axi_araddr, s13_axi_araddr, s12_axi_araddr, s11_axi_araddr, s10_axi_araddr, s09_axi_araddr, s08_axi_araddr, s07_axi_araddr, s06_axi_araddr, s05_axi_araddr, s04_axi_araddr, s03_axi_araddr, s02_axi_araddr, s01_axi_araddr, s00_axi_araddr }), - .s_axi_arlen({ s14_axi_arlen, s13_axi_arlen, s12_axi_arlen, s11_axi_arlen, s10_axi_arlen, s09_axi_arlen, s08_axi_arlen, s07_axi_arlen, s06_axi_arlen, s05_axi_arlen, s04_axi_arlen, s03_axi_arlen, s02_axi_arlen, s01_axi_arlen, s00_axi_arlen }), - .s_axi_arsize({ s14_axi_arsize, s13_axi_arsize, s12_axi_arsize, s11_axi_arsize, s10_axi_arsize, s09_axi_arsize, s08_axi_arsize, s07_axi_arsize, s06_axi_arsize, s05_axi_arsize, s04_axi_arsize, s03_axi_arsize, s02_axi_arsize, s01_axi_arsize, s00_axi_arsize }), - .s_axi_arburst({ s14_axi_arburst, s13_axi_arburst, s12_axi_arburst, s11_axi_arburst, s10_axi_arburst, s09_axi_arburst, s08_axi_arburst, s07_axi_arburst, s06_axi_arburst, s05_axi_arburst, s04_axi_arburst, s03_axi_arburst, s02_axi_arburst, s01_axi_arburst, s00_axi_arburst }), - .s_axi_arlock({ s14_axi_arlock, s13_axi_arlock, s12_axi_arlock, s11_axi_arlock, s10_axi_arlock, s09_axi_arlock, s08_axi_arlock, s07_axi_arlock, s06_axi_arlock, s05_axi_arlock, s04_axi_arlock, s03_axi_arlock, s02_axi_arlock, s01_axi_arlock, s00_axi_arlock }), - .s_axi_arcache({ s14_axi_arcache, s13_axi_arcache, s12_axi_arcache, s11_axi_arcache, s10_axi_arcache, s09_axi_arcache, s08_axi_arcache, s07_axi_arcache, s06_axi_arcache, s05_axi_arcache, s04_axi_arcache, s03_axi_arcache, s02_axi_arcache, s01_axi_arcache, s00_axi_arcache }), - .s_axi_arprot({ s14_axi_arprot, s13_axi_arprot, s12_axi_arprot, s11_axi_arprot, s10_axi_arprot, s09_axi_arprot, s08_axi_arprot, s07_axi_arprot, s06_axi_arprot, s05_axi_arprot, s04_axi_arprot, s03_axi_arprot, s02_axi_arprot, s01_axi_arprot, s00_axi_arprot }), - .s_axi_arqos({ s14_axi_arqos, s13_axi_arqos, s12_axi_arqos, s11_axi_arqos, s10_axi_arqos, s09_axi_arqos, s08_axi_arqos, s07_axi_arqos, s06_axi_arqos, s05_axi_arqos, s04_axi_arqos, s03_axi_arqos, s02_axi_arqos, s01_axi_arqos, s00_axi_arqos }), - .s_axi_aruser({ s14_axi_aruser, s13_axi_aruser, s12_axi_aruser, s11_axi_aruser, s10_axi_aruser, s09_axi_aruser, s08_axi_aruser, s07_axi_aruser, s06_axi_aruser, s05_axi_aruser, s04_axi_aruser, s03_axi_aruser, s02_axi_aruser, s01_axi_aruser, s00_axi_aruser }), - .s_axi_arvalid({ s14_axi_arvalid, s13_axi_arvalid, s12_axi_arvalid, s11_axi_arvalid, s10_axi_arvalid, s09_axi_arvalid, s08_axi_arvalid, s07_axi_arvalid, s06_axi_arvalid, s05_axi_arvalid, s04_axi_arvalid, s03_axi_arvalid, s02_axi_arvalid, s01_axi_arvalid, s00_axi_arvalid }), - .s_axi_arready({ s14_axi_arready, s13_axi_arready, s12_axi_arready, s11_axi_arready, s10_axi_arready, s09_axi_arready, s08_axi_arready, s07_axi_arready, s06_axi_arready, s05_axi_arready, s04_axi_arready, s03_axi_arready, s02_axi_arready, s01_axi_arready, s00_axi_arready }), - .s_axi_rid({ s14_axi_rid, s13_axi_rid, s12_axi_rid, s11_axi_rid, s10_axi_rid, s09_axi_rid, s08_axi_rid, s07_axi_rid, s06_axi_rid, s05_axi_rid, s04_axi_rid, s03_axi_rid, s02_axi_rid, s01_axi_rid, s00_axi_rid }), - .s_axi_rdata({ s14_axi_rdata, s13_axi_rdata, s12_axi_rdata, s11_axi_rdata, s10_axi_rdata, s09_axi_rdata, s08_axi_rdata, s07_axi_rdata, s06_axi_rdata, s05_axi_rdata, s04_axi_rdata, s03_axi_rdata, s02_axi_rdata, s01_axi_rdata, s00_axi_rdata }), - .s_axi_rresp({ s14_axi_rresp, s13_axi_rresp, s12_axi_rresp, s11_axi_rresp, s10_axi_rresp, s09_axi_rresp, s08_axi_rresp, s07_axi_rresp, s06_axi_rresp, s05_axi_rresp, s04_axi_rresp, s03_axi_rresp, s02_axi_rresp, s01_axi_rresp, s00_axi_rresp }), - .s_axi_rlast({ s14_axi_rlast, s13_axi_rlast, s12_axi_rlast, s11_axi_rlast, s10_axi_rlast, s09_axi_rlast, s08_axi_rlast, s07_axi_rlast, s06_axi_rlast, s05_axi_rlast, s04_axi_rlast, s03_axi_rlast, s02_axi_rlast, s01_axi_rlast, s00_axi_rlast }), - .s_axi_ruser({ s14_axi_ruser, s13_axi_ruser, s12_axi_ruser, s11_axi_ruser, s10_axi_ruser, s09_axi_ruser, s08_axi_ruser, s07_axi_ruser, s06_axi_ruser, s05_axi_ruser, s04_axi_ruser, s03_axi_ruser, s02_axi_ruser, s01_axi_ruser, s00_axi_ruser }), - .s_axi_rvalid({ s14_axi_rvalid, s13_axi_rvalid, s12_axi_rvalid, s11_axi_rvalid, s10_axi_rvalid, s09_axi_rvalid, s08_axi_rvalid, s07_axi_rvalid, s06_axi_rvalid, s05_axi_rvalid, s04_axi_rvalid, s03_axi_rvalid, s02_axi_rvalid, s01_axi_rvalid, s00_axi_rvalid }), - .s_axi_rready({ s14_axi_rready, s13_axi_rready, s12_axi_rready, s11_axi_rready, s10_axi_rready, s09_axi_rready, s08_axi_rready, s07_axi_rready, s06_axi_rready, s05_axi_rready, s04_axi_rready, s03_axi_rready, s02_axi_rready, s01_axi_rready, s00_axi_rready }), - .m_axi_awid({ m00_axi_awid }), - .m_axi_awaddr({ m00_axi_awaddr }), - .m_axi_awlen({ m00_axi_awlen }), - .m_axi_awsize({ m00_axi_awsize }), - .m_axi_awburst({ m00_axi_awburst }), - .m_axi_awlock({ m00_axi_awlock }), - .m_axi_awcache({ m00_axi_awcache }), - .m_axi_awprot({ m00_axi_awprot }), - .m_axi_awqos({ m00_axi_awqos }), - .m_axi_awregion({ m00_axi_awregion }), - .m_axi_awuser({ m00_axi_awuser }), - .m_axi_awvalid({ m00_axi_awvalid }), - .m_axi_awready({ m00_axi_awready }), - .m_axi_wdata({ m00_axi_wdata }), - .m_axi_wstrb({ m00_axi_wstrb }), - .m_axi_wlast({ m00_axi_wlast }), - .m_axi_wuser({ m00_axi_wuser }), - .m_axi_wvalid({ m00_axi_wvalid }), - .m_axi_wready({ m00_axi_wready }), - .m_axi_bid({ m00_axi_bid }), - .m_axi_bresp({ m00_axi_bresp }), - .m_axi_buser({ m00_axi_buser }), - .m_axi_bvalid({ m00_axi_bvalid }), - .m_axi_bready({ m00_axi_bready }), - .m_axi_arid({ m00_axi_arid }), - .m_axi_araddr({ m00_axi_araddr }), - .m_axi_arlen({ m00_axi_arlen }), - .m_axi_arsize({ m00_axi_arsize }), - .m_axi_arburst({ m00_axi_arburst }), - .m_axi_arlock({ m00_axi_arlock }), - .m_axi_arcache({ m00_axi_arcache }), - .m_axi_arprot({ m00_axi_arprot }), - .m_axi_arqos({ m00_axi_arqos }), - .m_axi_arregion({ m00_axi_arregion }), - .m_axi_aruser({ m00_axi_aruser }), - .m_axi_arvalid({ m00_axi_arvalid }), - .m_axi_arready({ m00_axi_arready }), - .m_axi_rid({ m00_axi_rid }), - .m_axi_rdata({ m00_axi_rdata }), - .m_axi_rresp({ m00_axi_rresp }), - .m_axi_rlast({ m00_axi_rlast }), - .m_axi_ruser({ m00_axi_ruser }), - .m_axi_rvalid({ m00_axi_rvalid }), - .m_axi_rready({ m00_axi_rready }) -); - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/axi_register_rd.v b/xls/modules/zstd/external/axi_register_rd.v deleted file mode 100644 index c0df03a03f..0000000000 --- a/xls/modules/zstd/external/axi_register_rd.v +++ /dev/null @@ -1,530 +0,0 @@ -/* - -Copyright (c) 2018 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * AXI4 register (read) - */ -module axi_register_rd # -( - // Width of data bus in bits - parameter DATA_WIDTH = 32, - // Width of address bus in bits - parameter ADDR_WIDTH = 32, - // Width of wstrb (width of data bus in words) - parameter STRB_WIDTH = (DATA_WIDTH/8), - // Width of ID signal - parameter ID_WIDTH = 8, - // Propagate aruser signal - parameter ARUSER_ENABLE = 0, - // Width of aruser signal - parameter ARUSER_WIDTH = 1, - // Propagate ruser signal - parameter RUSER_ENABLE = 0, - // Width of ruser signal - parameter RUSER_WIDTH = 1, - // AR channel register type - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter AR_REG_TYPE = 1, - // R channel register type - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter R_REG_TYPE = 2 -) -( - input wire clk, - input wire rst, - - /* - * AXI slave interface - */ - input wire [ID_WIDTH-1:0] s_axi_arid, - input wire [ADDR_WIDTH-1:0] s_axi_araddr, - input wire [7:0] s_axi_arlen, - input wire [2:0] s_axi_arsize, - input wire [1:0] s_axi_arburst, - input wire s_axi_arlock, - input wire [3:0] s_axi_arcache, - input wire [2:0] s_axi_arprot, - input wire [3:0] s_axi_arqos, - input wire [3:0] s_axi_arregion, - input wire [ARUSER_WIDTH-1:0] s_axi_aruser, - input wire s_axi_arvalid, - output wire s_axi_arready, - output wire [ID_WIDTH-1:0] s_axi_rid, - output wire [DATA_WIDTH-1:0] s_axi_rdata, - output wire [1:0] s_axi_rresp, - output wire s_axi_rlast, - output wire [RUSER_WIDTH-1:0] s_axi_ruser, - output wire s_axi_rvalid, - input wire s_axi_rready, - - /* - * AXI master interface - */ - output wire [ID_WIDTH-1:0] m_axi_arid, - output wire [ADDR_WIDTH-1:0] m_axi_araddr, - output wire [7:0] m_axi_arlen, - output wire [2:0] m_axi_arsize, - output wire [1:0] m_axi_arburst, - output wire m_axi_arlock, - output wire [3:0] m_axi_arcache, - output wire [2:0] m_axi_arprot, - output wire [3:0] m_axi_arqos, - output wire [3:0] m_axi_arregion, - output wire [ARUSER_WIDTH-1:0] m_axi_aruser, - output wire m_axi_arvalid, - input wire m_axi_arready, - input wire [ID_WIDTH-1:0] m_axi_rid, - input wire [DATA_WIDTH-1:0] m_axi_rdata, - input wire [1:0] m_axi_rresp, - input wire m_axi_rlast, - input wire [RUSER_WIDTH-1:0] m_axi_ruser, - input wire m_axi_rvalid, - output wire m_axi_rready -); - -generate - -// AR channel - -if (AR_REG_TYPE > 1) begin -// skid buffer, no bubble cycles - -// datapath registers -reg s_axi_arready_reg = 1'b0; - -reg [ID_WIDTH-1:0] m_axi_arid_reg = {ID_WIDTH{1'b0}}; -reg [ADDR_WIDTH-1:0] m_axi_araddr_reg = {ADDR_WIDTH{1'b0}}; -reg [7:0] m_axi_arlen_reg = 8'd0; -reg [2:0] m_axi_arsize_reg = 3'd0; -reg [1:0] m_axi_arburst_reg = 2'd0; -reg m_axi_arlock_reg = 1'b0; -reg [3:0] m_axi_arcache_reg = 4'd0; -reg [2:0] m_axi_arprot_reg = 3'd0; -reg [3:0] m_axi_arqos_reg = 4'd0; -reg [3:0] m_axi_arregion_reg = 4'd0; -reg [ARUSER_WIDTH-1:0] m_axi_aruser_reg = {ARUSER_WIDTH{1'b0}}; -reg m_axi_arvalid_reg = 1'b0, m_axi_arvalid_next; - -reg [ID_WIDTH-1:0] temp_m_axi_arid_reg = {ID_WIDTH{1'b0}}; -reg [ADDR_WIDTH-1:0] temp_m_axi_araddr_reg = {ADDR_WIDTH{1'b0}}; -reg [7:0] temp_m_axi_arlen_reg = 8'd0; -reg [2:0] temp_m_axi_arsize_reg = 3'd0; -reg [1:0] temp_m_axi_arburst_reg = 2'd0; -reg temp_m_axi_arlock_reg = 1'b0; -reg [3:0] temp_m_axi_arcache_reg = 4'd0; -reg [2:0] temp_m_axi_arprot_reg = 3'd0; -reg [3:0] temp_m_axi_arqos_reg = 4'd0; -reg [3:0] temp_m_axi_arregion_reg = 4'd0; -reg [ARUSER_WIDTH-1:0] temp_m_axi_aruser_reg = {ARUSER_WIDTH{1'b0}}; -reg temp_m_axi_arvalid_reg = 1'b0, temp_m_axi_arvalid_next; - -// datapath control -reg store_axi_ar_input_to_output; -reg store_axi_ar_input_to_temp; -reg store_axi_ar_temp_to_output; - -assign s_axi_arready = s_axi_arready_reg; - -assign m_axi_arid = m_axi_arid_reg; -assign m_axi_araddr = m_axi_araddr_reg; -assign m_axi_arlen = m_axi_arlen_reg; -assign m_axi_arsize = m_axi_arsize_reg; -assign m_axi_arburst = m_axi_arburst_reg; -assign m_axi_arlock = m_axi_arlock_reg; -assign m_axi_arcache = m_axi_arcache_reg; -assign m_axi_arprot = m_axi_arprot_reg; -assign m_axi_arqos = m_axi_arqos_reg; -assign m_axi_arregion = m_axi_arregion_reg; -assign m_axi_aruser = ARUSER_ENABLE ? m_axi_aruser_reg : {ARUSER_WIDTH{1'b0}}; -assign m_axi_arvalid = m_axi_arvalid_reg; - -// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) -wire s_axi_arready_early = m_axi_arready | (~temp_m_axi_arvalid_reg & (~m_axi_arvalid_reg | ~s_axi_arvalid)); - -always @* begin - // transfer sink ready state to source - m_axi_arvalid_next = m_axi_arvalid_reg; - temp_m_axi_arvalid_next = temp_m_axi_arvalid_reg; - - store_axi_ar_input_to_output = 1'b0; - store_axi_ar_input_to_temp = 1'b0; - store_axi_ar_temp_to_output = 1'b0; - - if (s_axi_arready_reg) begin - // input is ready - if (m_axi_arready | ~m_axi_arvalid_reg) begin - // output is ready or currently not valid, transfer data to output - m_axi_arvalid_next = s_axi_arvalid; - store_axi_ar_input_to_output = 1'b1; - end else begin - // output is not ready, store input in temp - temp_m_axi_arvalid_next = s_axi_arvalid; - store_axi_ar_input_to_temp = 1'b1; - end - end else if (m_axi_arready) begin - // input is not ready, but output is ready - m_axi_arvalid_next = temp_m_axi_arvalid_reg; - temp_m_axi_arvalid_next = 1'b0; - store_axi_ar_temp_to_output = 1'b1; - end -end - -always @(posedge clk) begin - if (rst) begin - s_axi_arready_reg <= 1'b0; - m_axi_arvalid_reg <= 1'b0; - temp_m_axi_arvalid_reg <= 1'b0; - end else begin - s_axi_arready_reg <= s_axi_arready_early; - m_axi_arvalid_reg <= m_axi_arvalid_next; - temp_m_axi_arvalid_reg <= temp_m_axi_arvalid_next; - end - - // datapath - if (store_axi_ar_input_to_output) begin - m_axi_arid_reg <= s_axi_arid; - m_axi_araddr_reg <= s_axi_araddr; - m_axi_arlen_reg <= s_axi_arlen; - m_axi_arsize_reg <= s_axi_arsize; - m_axi_arburst_reg <= s_axi_arburst; - m_axi_arlock_reg <= s_axi_arlock; - m_axi_arcache_reg <= s_axi_arcache; - m_axi_arprot_reg <= s_axi_arprot; - m_axi_arqos_reg <= s_axi_arqos; - m_axi_arregion_reg <= s_axi_arregion; - m_axi_aruser_reg <= s_axi_aruser; - end else if (store_axi_ar_temp_to_output) begin - m_axi_arid_reg <= temp_m_axi_arid_reg; - m_axi_araddr_reg <= temp_m_axi_araddr_reg; - m_axi_arlen_reg <= temp_m_axi_arlen_reg; - m_axi_arsize_reg <= temp_m_axi_arsize_reg; - m_axi_arburst_reg <= temp_m_axi_arburst_reg; - m_axi_arlock_reg <= temp_m_axi_arlock_reg; - m_axi_arcache_reg <= temp_m_axi_arcache_reg; - m_axi_arprot_reg <= temp_m_axi_arprot_reg; - m_axi_arqos_reg <= temp_m_axi_arqos_reg; - m_axi_arregion_reg <= temp_m_axi_arregion_reg; - m_axi_aruser_reg <= temp_m_axi_aruser_reg; - end - - if (store_axi_ar_input_to_temp) begin - temp_m_axi_arid_reg <= s_axi_arid; - temp_m_axi_araddr_reg <= s_axi_araddr; - temp_m_axi_arlen_reg <= s_axi_arlen; - temp_m_axi_arsize_reg <= s_axi_arsize; - temp_m_axi_arburst_reg <= s_axi_arburst; - temp_m_axi_arlock_reg <= s_axi_arlock; - temp_m_axi_arcache_reg <= s_axi_arcache; - temp_m_axi_arprot_reg <= s_axi_arprot; - temp_m_axi_arqos_reg <= s_axi_arqos; - temp_m_axi_arregion_reg <= s_axi_arregion; - temp_m_axi_aruser_reg <= s_axi_aruser; - end -end - -end else if (AR_REG_TYPE == 1) begin -// simple register, inserts bubble cycles - -// datapath registers -reg s_axi_arready_reg = 1'b0; - -reg [ID_WIDTH-1:0] m_axi_arid_reg = {ID_WIDTH{1'b0}}; -reg [ADDR_WIDTH-1:0] m_axi_araddr_reg = {ADDR_WIDTH{1'b0}}; -reg [7:0] m_axi_arlen_reg = 8'd0; -reg [2:0] m_axi_arsize_reg = 3'd0; -reg [1:0] m_axi_arburst_reg = 2'd0; -reg m_axi_arlock_reg = 1'b0; -reg [3:0] m_axi_arcache_reg = 4'd0; -reg [2:0] m_axi_arprot_reg = 3'd0; -reg [3:0] m_axi_arqos_reg = 4'd0; -reg [3:0] m_axi_arregion_reg = 4'd0; -reg [ARUSER_WIDTH-1:0] m_axi_aruser_reg = {ARUSER_WIDTH{1'b0}}; -reg m_axi_arvalid_reg = 1'b0, m_axi_arvalid_next; - -// datapath control -reg store_axi_ar_input_to_output; - -assign s_axi_arready = s_axi_arready_reg; - -assign m_axi_arid = m_axi_arid_reg; -assign m_axi_araddr = m_axi_araddr_reg; -assign m_axi_arlen = m_axi_arlen_reg; -assign m_axi_arsize = m_axi_arsize_reg; -assign m_axi_arburst = m_axi_arburst_reg; -assign m_axi_arlock = m_axi_arlock_reg; -assign m_axi_arcache = m_axi_arcache_reg; -assign m_axi_arprot = m_axi_arprot_reg; -assign m_axi_arqos = m_axi_arqos_reg; -assign m_axi_arregion = m_axi_arregion_reg; -assign m_axi_aruser = ARUSER_ENABLE ? m_axi_aruser_reg : {ARUSER_WIDTH{1'b0}}; -assign m_axi_arvalid = m_axi_arvalid_reg; - -// enable ready input next cycle if output buffer will be empty -wire s_axi_arready_early = !m_axi_arvalid_next; - -always @* begin - // transfer sink ready state to source - m_axi_arvalid_next = m_axi_arvalid_reg; - - store_axi_ar_input_to_output = 1'b0; - - if (s_axi_arready_reg) begin - m_axi_arvalid_next = s_axi_arvalid; - store_axi_ar_input_to_output = 1'b1; - end else if (m_axi_arready) begin - m_axi_arvalid_next = 1'b0; - end -end - -always @(posedge clk) begin - if (rst) begin - s_axi_arready_reg <= 1'b0; - m_axi_arvalid_reg <= 1'b0; - end else begin - s_axi_arready_reg <= s_axi_arready_early; - m_axi_arvalid_reg <= m_axi_arvalid_next; - end - - // datapath - if (store_axi_ar_input_to_output) begin - m_axi_arid_reg <= s_axi_arid; - m_axi_araddr_reg <= s_axi_araddr; - m_axi_arlen_reg <= s_axi_arlen; - m_axi_arsize_reg <= s_axi_arsize; - m_axi_arburst_reg <= s_axi_arburst; - m_axi_arlock_reg <= s_axi_arlock; - m_axi_arcache_reg <= s_axi_arcache; - m_axi_arprot_reg <= s_axi_arprot; - m_axi_arqos_reg <= s_axi_arqos; - m_axi_arregion_reg <= s_axi_arregion; - m_axi_aruser_reg <= s_axi_aruser; - end -end - -end else begin - - // bypass AR channel - assign m_axi_arid = s_axi_arid; - assign m_axi_araddr = s_axi_araddr; - assign m_axi_arlen = s_axi_arlen; - assign m_axi_arsize = s_axi_arsize; - assign m_axi_arburst = s_axi_arburst; - assign m_axi_arlock = s_axi_arlock; - assign m_axi_arcache = s_axi_arcache; - assign m_axi_arprot = s_axi_arprot; - assign m_axi_arqos = s_axi_arqos; - assign m_axi_arregion = s_axi_arregion; - assign m_axi_aruser = ARUSER_ENABLE ? s_axi_aruser : {ARUSER_WIDTH{1'b0}}; - assign m_axi_arvalid = s_axi_arvalid; - assign s_axi_arready = m_axi_arready; - -end - -// R channel - -if (R_REG_TYPE > 1) begin -// skid buffer, no bubble cycles - -// datapath registers -reg m_axi_rready_reg = 1'b0; - -reg [ID_WIDTH-1:0] s_axi_rid_reg = {ID_WIDTH{1'b0}}; -reg [DATA_WIDTH-1:0] s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; -reg [1:0] s_axi_rresp_reg = 2'b0; -reg s_axi_rlast_reg = 1'b0; -reg [RUSER_WIDTH-1:0] s_axi_ruser_reg = {RUSER_WIDTH{1'b0}}; -reg s_axi_rvalid_reg = 1'b0, s_axi_rvalid_next; - -reg [ID_WIDTH-1:0] temp_s_axi_rid_reg = {ID_WIDTH{1'b0}}; -reg [DATA_WIDTH-1:0] temp_s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; -reg [1:0] temp_s_axi_rresp_reg = 2'b0; -reg temp_s_axi_rlast_reg = 1'b0; -reg [RUSER_WIDTH-1:0] temp_s_axi_ruser_reg = {RUSER_WIDTH{1'b0}}; -reg temp_s_axi_rvalid_reg = 1'b0, temp_s_axi_rvalid_next; - -// datapath control -reg store_axi_r_input_to_output; -reg store_axi_r_input_to_temp; -reg store_axi_r_temp_to_output; - -assign m_axi_rready = m_axi_rready_reg; - -assign s_axi_rid = s_axi_rid_reg; -assign s_axi_rdata = s_axi_rdata_reg; -assign s_axi_rresp = s_axi_rresp_reg; -assign s_axi_rlast = s_axi_rlast_reg; -assign s_axi_ruser = RUSER_ENABLE ? s_axi_ruser_reg : {RUSER_WIDTH{1'b0}}; -assign s_axi_rvalid = s_axi_rvalid_reg; - -// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) -wire m_axi_rready_early = s_axi_rready | (~temp_s_axi_rvalid_reg & (~s_axi_rvalid_reg | ~m_axi_rvalid)); - -always @* begin - // transfer sink ready state to source - s_axi_rvalid_next = s_axi_rvalid_reg; - temp_s_axi_rvalid_next = temp_s_axi_rvalid_reg; - - store_axi_r_input_to_output = 1'b0; - store_axi_r_input_to_temp = 1'b0; - store_axi_r_temp_to_output = 1'b0; - - if (m_axi_rready_reg) begin - // input is ready - if (s_axi_rready | ~s_axi_rvalid_reg) begin - // output is ready or currently not valid, transfer data to output - s_axi_rvalid_next = m_axi_rvalid; - store_axi_r_input_to_output = 1'b1; - end else begin - // output is not ready, store input in temp - temp_s_axi_rvalid_next = m_axi_rvalid; - store_axi_r_input_to_temp = 1'b1; - end - end else if (s_axi_rready) begin - // input is not ready, but output is ready - s_axi_rvalid_next = temp_s_axi_rvalid_reg; - temp_s_axi_rvalid_next = 1'b0; - store_axi_r_temp_to_output = 1'b1; - end -end - -always @(posedge clk) begin - if (rst) begin - m_axi_rready_reg <= 1'b0; - s_axi_rvalid_reg <= 1'b0; - temp_s_axi_rvalid_reg <= 1'b0; - end else begin - m_axi_rready_reg <= m_axi_rready_early; - s_axi_rvalid_reg <= s_axi_rvalid_next; - temp_s_axi_rvalid_reg <= temp_s_axi_rvalid_next; - end - - // datapath - if (store_axi_r_input_to_output) begin - s_axi_rid_reg <= m_axi_rid; - s_axi_rdata_reg <= m_axi_rdata; - s_axi_rresp_reg <= m_axi_rresp; - s_axi_rlast_reg <= m_axi_rlast; - s_axi_ruser_reg <= m_axi_ruser; - end else if (store_axi_r_temp_to_output) begin - s_axi_rid_reg <= temp_s_axi_rid_reg; - s_axi_rdata_reg <= temp_s_axi_rdata_reg; - s_axi_rresp_reg <= temp_s_axi_rresp_reg; - s_axi_rlast_reg <= temp_s_axi_rlast_reg; - s_axi_ruser_reg <= temp_s_axi_ruser_reg; - end - - if (store_axi_r_input_to_temp) begin - temp_s_axi_rid_reg <= m_axi_rid; - temp_s_axi_rdata_reg <= m_axi_rdata; - temp_s_axi_rresp_reg <= m_axi_rresp; - temp_s_axi_rlast_reg <= m_axi_rlast; - temp_s_axi_ruser_reg <= m_axi_ruser; - end -end - -end else if (R_REG_TYPE == 1) begin -// simple register, inserts bubble cycles - -// datapath registers -reg m_axi_rready_reg = 1'b0; - -reg [ID_WIDTH-1:0] s_axi_rid_reg = {ID_WIDTH{1'b0}}; -reg [DATA_WIDTH-1:0] s_axi_rdata_reg = {DATA_WIDTH{1'b0}}; -reg [1:0] s_axi_rresp_reg = 2'b0; -reg s_axi_rlast_reg = 1'b0; -reg [RUSER_WIDTH-1:0] s_axi_ruser_reg = {RUSER_WIDTH{1'b0}}; -reg s_axi_rvalid_reg = 1'b0, s_axi_rvalid_next; - -// datapath control -reg store_axi_r_input_to_output; - -assign m_axi_rready = m_axi_rready_reg; - -assign s_axi_rid = s_axi_rid_reg; -assign s_axi_rdata = s_axi_rdata_reg; -assign s_axi_rresp = s_axi_rresp_reg; -assign s_axi_rlast = s_axi_rlast_reg; -assign s_axi_ruser = RUSER_ENABLE ? s_axi_ruser_reg : {RUSER_WIDTH{1'b0}}; -assign s_axi_rvalid = s_axi_rvalid_reg; - -// enable ready input next cycle if output buffer will be empty -wire m_axi_rready_early = !s_axi_rvalid_next; - -always @* begin - // transfer sink ready state to source - s_axi_rvalid_next = s_axi_rvalid_reg; - - store_axi_r_input_to_output = 1'b0; - - if (m_axi_rready_reg) begin - s_axi_rvalid_next = m_axi_rvalid; - store_axi_r_input_to_output = 1'b1; - end else if (s_axi_rready) begin - s_axi_rvalid_next = 1'b0; - end -end - -always @(posedge clk) begin - if (rst) begin - m_axi_rready_reg <= 1'b0; - s_axi_rvalid_reg <= 1'b0; - end else begin - m_axi_rready_reg <= m_axi_rready_early; - s_axi_rvalid_reg <= s_axi_rvalid_next; - end - - // datapath - if (store_axi_r_input_to_output) begin - s_axi_rid_reg <= m_axi_rid; - s_axi_rdata_reg <= m_axi_rdata; - s_axi_rresp_reg <= m_axi_rresp; - s_axi_rlast_reg <= m_axi_rlast; - s_axi_ruser_reg <= m_axi_ruser; - end -end - -end else begin - - // bypass R channel - assign s_axi_rid = m_axi_rid; - assign s_axi_rdata = m_axi_rdata; - assign s_axi_rresp = m_axi_rresp; - assign s_axi_rlast = m_axi_rlast; - assign s_axi_ruser = RUSER_ENABLE ? m_axi_ruser : {RUSER_WIDTH{1'b0}}; - assign s_axi_rvalid = m_axi_rvalid; - assign m_axi_rready = s_axi_rready; - -end - -endgenerate - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/axi_register_wr.v b/xls/modules/zstd/external/axi_register_wr.v deleted file mode 100644 index 9176d6ba95..0000000000 --- a/xls/modules/zstd/external/axi_register_wr.v +++ /dev/null @@ -1,691 +0,0 @@ -/* - -Copyright (c) 2018 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * AXI4 register (write) - */ -module axi_register_wr # -( - // Width of data bus in bits - parameter DATA_WIDTH = 32, - // Width of address bus in bits - parameter ADDR_WIDTH = 32, - // Width of wstrb (width of data bus in words) - parameter STRB_WIDTH = (DATA_WIDTH/8), - // Width of ID signal - parameter ID_WIDTH = 8, - // Propagate awuser signal - parameter AWUSER_ENABLE = 0, - // Width of awuser signal - parameter AWUSER_WIDTH = 1, - // Propagate wuser signal - parameter WUSER_ENABLE = 0, - // Width of wuser signal - parameter WUSER_WIDTH = 1, - // Propagate buser signal - parameter BUSER_ENABLE = 0, - // Width of buser signal - parameter BUSER_WIDTH = 1, - // AW channel register type - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter AW_REG_TYPE = 1, - // W channel register type - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter W_REG_TYPE = 2, - // B channel register type - // 0 to bypass, 1 for simple buffer, 2 for skid buffer - parameter B_REG_TYPE = 1 -) -( - input wire clk, - input wire rst, - - /* - * AXI slave interface - */ - input wire [ID_WIDTH-1:0] s_axi_awid, - input wire [ADDR_WIDTH-1:0] s_axi_awaddr, - input wire [7:0] s_axi_awlen, - input wire [2:0] s_axi_awsize, - input wire [1:0] s_axi_awburst, - input wire s_axi_awlock, - input wire [3:0] s_axi_awcache, - input wire [2:0] s_axi_awprot, - input wire [3:0] s_axi_awqos, - input wire [3:0] s_axi_awregion, - input wire [AWUSER_WIDTH-1:0] s_axi_awuser, - input wire s_axi_awvalid, - output wire s_axi_awready, - input wire [DATA_WIDTH-1:0] s_axi_wdata, - input wire [STRB_WIDTH-1:0] s_axi_wstrb, - input wire s_axi_wlast, - input wire [WUSER_WIDTH-1:0] s_axi_wuser, - input wire s_axi_wvalid, - output wire s_axi_wready, - output wire [ID_WIDTH-1:0] s_axi_bid, - output wire [1:0] s_axi_bresp, - output wire [BUSER_WIDTH-1:0] s_axi_buser, - output wire s_axi_bvalid, - input wire s_axi_bready, - - /* - * AXI master interface - */ - output wire [ID_WIDTH-1:0] m_axi_awid, - output wire [ADDR_WIDTH-1:0] m_axi_awaddr, - output wire [7:0] m_axi_awlen, - output wire [2:0] m_axi_awsize, - output wire [1:0] m_axi_awburst, - output wire m_axi_awlock, - output wire [3:0] m_axi_awcache, - output wire [2:0] m_axi_awprot, - output wire [3:0] m_axi_awqos, - output wire [3:0] m_axi_awregion, - output wire [AWUSER_WIDTH-1:0] m_axi_awuser, - output wire m_axi_awvalid, - input wire m_axi_awready, - output wire [DATA_WIDTH-1:0] m_axi_wdata, - output wire [STRB_WIDTH-1:0] m_axi_wstrb, - output wire m_axi_wlast, - output wire [WUSER_WIDTH-1:0] m_axi_wuser, - output wire m_axi_wvalid, - input wire m_axi_wready, - input wire [ID_WIDTH-1:0] m_axi_bid, - input wire [1:0] m_axi_bresp, - input wire [BUSER_WIDTH-1:0] m_axi_buser, - input wire m_axi_bvalid, - output wire m_axi_bready -); - -generate - -// AW channel - -if (AW_REG_TYPE > 1) begin -// skid buffer, no bubble cycles - -// datapath registers -reg s_axi_awready_reg = 1'b0; - -reg [ID_WIDTH-1:0] m_axi_awid_reg = {ID_WIDTH{1'b0}}; -reg [ADDR_WIDTH-1:0] m_axi_awaddr_reg = {ADDR_WIDTH{1'b0}}; -reg [7:0] m_axi_awlen_reg = 8'd0; -reg [2:0] m_axi_awsize_reg = 3'd0; -reg [1:0] m_axi_awburst_reg = 2'd0; -reg m_axi_awlock_reg = 1'b0; -reg [3:0] m_axi_awcache_reg = 4'd0; -reg [2:0] m_axi_awprot_reg = 3'd0; -reg [3:0] m_axi_awqos_reg = 4'd0; -reg [3:0] m_axi_awregion_reg = 4'd0; -reg [AWUSER_WIDTH-1:0] m_axi_awuser_reg = {AWUSER_WIDTH{1'b0}}; -reg m_axi_awvalid_reg = 1'b0, m_axi_awvalid_next; - -reg [ID_WIDTH-1:0] temp_m_axi_awid_reg = {ID_WIDTH{1'b0}}; -reg [ADDR_WIDTH-1:0] temp_m_axi_awaddr_reg = {ADDR_WIDTH{1'b0}}; -reg [7:0] temp_m_axi_awlen_reg = 8'd0; -reg [2:0] temp_m_axi_awsize_reg = 3'd0; -reg [1:0] temp_m_axi_awburst_reg = 2'd0; -reg temp_m_axi_awlock_reg = 1'b0; -reg [3:0] temp_m_axi_awcache_reg = 4'd0; -reg [2:0] temp_m_axi_awprot_reg = 3'd0; -reg [3:0] temp_m_axi_awqos_reg = 4'd0; -reg [3:0] temp_m_axi_awregion_reg = 4'd0; -reg [AWUSER_WIDTH-1:0] temp_m_axi_awuser_reg = {AWUSER_WIDTH{1'b0}}; -reg temp_m_axi_awvalid_reg = 1'b0, temp_m_axi_awvalid_next; - -// datapath control -reg store_axi_aw_input_to_output; -reg store_axi_aw_input_to_temp; -reg store_axi_aw_temp_to_output; - -assign s_axi_awready = s_axi_awready_reg; - -assign m_axi_awid = m_axi_awid_reg; -assign m_axi_awaddr = m_axi_awaddr_reg; -assign m_axi_awlen = m_axi_awlen_reg; -assign m_axi_awsize = m_axi_awsize_reg; -assign m_axi_awburst = m_axi_awburst_reg; -assign m_axi_awlock = m_axi_awlock_reg; -assign m_axi_awcache = m_axi_awcache_reg; -assign m_axi_awprot = m_axi_awprot_reg; -assign m_axi_awqos = m_axi_awqos_reg; -assign m_axi_awregion = m_axi_awregion_reg; -assign m_axi_awuser = AWUSER_ENABLE ? m_axi_awuser_reg : {AWUSER_WIDTH{1'b0}}; -assign m_axi_awvalid = m_axi_awvalid_reg; - -// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) -wire s_axi_awready_early = m_axi_awready | (~temp_m_axi_awvalid_reg & (~m_axi_awvalid_reg | ~s_axi_awvalid)); - -always @* begin - // transfer sink ready state to source - m_axi_awvalid_next = m_axi_awvalid_reg; - temp_m_axi_awvalid_next = temp_m_axi_awvalid_reg; - - store_axi_aw_input_to_output = 1'b0; - store_axi_aw_input_to_temp = 1'b0; - store_axi_aw_temp_to_output = 1'b0; - - if (s_axi_awready_reg) begin - // input is ready - if (m_axi_awready | ~m_axi_awvalid_reg) begin - // output is ready or currently not valid, transfer data to output - m_axi_awvalid_next = s_axi_awvalid; - store_axi_aw_input_to_output = 1'b1; - end else begin - // output is not ready, store input in temp - temp_m_axi_awvalid_next = s_axi_awvalid; - store_axi_aw_input_to_temp = 1'b1; - end - end else if (m_axi_awready) begin - // input is not ready, but output is ready - m_axi_awvalid_next = temp_m_axi_awvalid_reg; - temp_m_axi_awvalid_next = 1'b0; - store_axi_aw_temp_to_output = 1'b1; - end -end - -always @(posedge clk) begin - if (rst) begin - s_axi_awready_reg <= 1'b0; - m_axi_awvalid_reg <= 1'b0; - temp_m_axi_awvalid_reg <= 1'b0; - end else begin - s_axi_awready_reg <= s_axi_awready_early; - m_axi_awvalid_reg <= m_axi_awvalid_next; - temp_m_axi_awvalid_reg <= temp_m_axi_awvalid_next; - end - - // datapath - if (store_axi_aw_input_to_output) begin - m_axi_awid_reg <= s_axi_awid; - m_axi_awaddr_reg <= s_axi_awaddr; - m_axi_awlen_reg <= s_axi_awlen; - m_axi_awsize_reg <= s_axi_awsize; - m_axi_awburst_reg <= s_axi_awburst; - m_axi_awlock_reg <= s_axi_awlock; - m_axi_awcache_reg <= s_axi_awcache; - m_axi_awprot_reg <= s_axi_awprot; - m_axi_awqos_reg <= s_axi_awqos; - m_axi_awregion_reg <= s_axi_awregion; - m_axi_awuser_reg <= s_axi_awuser; - end else if (store_axi_aw_temp_to_output) begin - m_axi_awid_reg <= temp_m_axi_awid_reg; - m_axi_awaddr_reg <= temp_m_axi_awaddr_reg; - m_axi_awlen_reg <= temp_m_axi_awlen_reg; - m_axi_awsize_reg <= temp_m_axi_awsize_reg; - m_axi_awburst_reg <= temp_m_axi_awburst_reg; - m_axi_awlock_reg <= temp_m_axi_awlock_reg; - m_axi_awcache_reg <= temp_m_axi_awcache_reg; - m_axi_awprot_reg <= temp_m_axi_awprot_reg; - m_axi_awqos_reg <= temp_m_axi_awqos_reg; - m_axi_awregion_reg <= temp_m_axi_awregion_reg; - m_axi_awuser_reg <= temp_m_axi_awuser_reg; - end - - if (store_axi_aw_input_to_temp) begin - temp_m_axi_awid_reg <= s_axi_awid; - temp_m_axi_awaddr_reg <= s_axi_awaddr; - temp_m_axi_awlen_reg <= s_axi_awlen; - temp_m_axi_awsize_reg <= s_axi_awsize; - temp_m_axi_awburst_reg <= s_axi_awburst; - temp_m_axi_awlock_reg <= s_axi_awlock; - temp_m_axi_awcache_reg <= s_axi_awcache; - temp_m_axi_awprot_reg <= s_axi_awprot; - temp_m_axi_awqos_reg <= s_axi_awqos; - temp_m_axi_awregion_reg <= s_axi_awregion; - temp_m_axi_awuser_reg <= s_axi_awuser; - end -end - -end else if (AW_REG_TYPE == 1) begin -// simple register, inserts bubble cycles - -// datapath registers -reg s_axi_awready_reg = 1'b0; - -reg [ID_WIDTH-1:0] m_axi_awid_reg = {ID_WIDTH{1'b0}}; -reg [ADDR_WIDTH-1:0] m_axi_awaddr_reg = {ADDR_WIDTH{1'b0}}; -reg [7:0] m_axi_awlen_reg = 8'd0; -reg [2:0] m_axi_awsize_reg = 3'd0; -reg [1:0] m_axi_awburst_reg = 2'd0; -reg m_axi_awlock_reg = 1'b0; -reg [3:0] m_axi_awcache_reg = 4'd0; -reg [2:0] m_axi_awprot_reg = 3'd0; -reg [3:0] m_axi_awqos_reg = 4'd0; -reg [3:0] m_axi_awregion_reg = 4'd0; -reg [AWUSER_WIDTH-1:0] m_axi_awuser_reg = {AWUSER_WIDTH{1'b0}}; -reg m_axi_awvalid_reg = 1'b0, m_axi_awvalid_next; - -// datapath control -reg store_axi_aw_input_to_output; - -assign s_axi_awready = s_axi_awready_reg; - -assign m_axi_awid = m_axi_awid_reg; -assign m_axi_awaddr = m_axi_awaddr_reg; -assign m_axi_awlen = m_axi_awlen_reg; -assign m_axi_awsize = m_axi_awsize_reg; -assign m_axi_awburst = m_axi_awburst_reg; -assign m_axi_awlock = m_axi_awlock_reg; -assign m_axi_awcache = m_axi_awcache_reg; -assign m_axi_awprot = m_axi_awprot_reg; -assign m_axi_awqos = m_axi_awqos_reg; -assign m_axi_awregion = m_axi_awregion_reg; -assign m_axi_awuser = AWUSER_ENABLE ? m_axi_awuser_reg : {AWUSER_WIDTH{1'b0}}; -assign m_axi_awvalid = m_axi_awvalid_reg; - -// enable ready input next cycle if output buffer will be empty -wire s_axi_awready_eawly = !m_axi_awvalid_next; - -always @* begin - // transfer sink ready state to source - m_axi_awvalid_next = m_axi_awvalid_reg; - - store_axi_aw_input_to_output = 1'b0; - - if (s_axi_awready_reg) begin - m_axi_awvalid_next = s_axi_awvalid; - store_axi_aw_input_to_output = 1'b1; - end else if (m_axi_awready) begin - m_axi_awvalid_next = 1'b0; - end -end - -always @(posedge clk) begin - if (rst) begin - s_axi_awready_reg <= 1'b0; - m_axi_awvalid_reg <= 1'b0; - end else begin - s_axi_awready_reg <= s_axi_awready_eawly; - m_axi_awvalid_reg <= m_axi_awvalid_next; - end - - // datapath - if (store_axi_aw_input_to_output) begin - m_axi_awid_reg <= s_axi_awid; - m_axi_awaddr_reg <= s_axi_awaddr; - m_axi_awlen_reg <= s_axi_awlen; - m_axi_awsize_reg <= s_axi_awsize; - m_axi_awburst_reg <= s_axi_awburst; - m_axi_awlock_reg <= s_axi_awlock; - m_axi_awcache_reg <= s_axi_awcache; - m_axi_awprot_reg <= s_axi_awprot; - m_axi_awqos_reg <= s_axi_awqos; - m_axi_awregion_reg <= s_axi_awregion; - m_axi_awuser_reg <= s_axi_awuser; - end -end - -end else begin - - // bypass AW channel - assign m_axi_awid = s_axi_awid; - assign m_axi_awaddr = s_axi_awaddr; - assign m_axi_awlen = s_axi_awlen; - assign m_axi_awsize = s_axi_awsize; - assign m_axi_awburst = s_axi_awburst; - assign m_axi_awlock = s_axi_awlock; - assign m_axi_awcache = s_axi_awcache; - assign m_axi_awprot = s_axi_awprot; - assign m_axi_awqos = s_axi_awqos; - assign m_axi_awregion = s_axi_awregion; - assign m_axi_awuser = AWUSER_ENABLE ? s_axi_awuser : {AWUSER_WIDTH{1'b0}}; - assign m_axi_awvalid = s_axi_awvalid; - assign s_axi_awready = m_axi_awready; - -end - -// W channel - -if (W_REG_TYPE > 1) begin -// skid buffer, no bubble cycles - -// datapath registers -reg s_axi_wready_reg = 1'b0; - -reg [DATA_WIDTH-1:0] m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; -reg [STRB_WIDTH-1:0] m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; -reg m_axi_wlast_reg = 1'b0; -reg [WUSER_WIDTH-1:0] m_axi_wuser_reg = {WUSER_WIDTH{1'b0}}; -reg m_axi_wvalid_reg = 1'b0, m_axi_wvalid_next; - -reg [DATA_WIDTH-1:0] temp_m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; -reg [STRB_WIDTH-1:0] temp_m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; -reg temp_m_axi_wlast_reg = 1'b0; -reg [WUSER_WIDTH-1:0] temp_m_axi_wuser_reg = {WUSER_WIDTH{1'b0}}; -reg temp_m_axi_wvalid_reg = 1'b0, temp_m_axi_wvalid_next; - -// datapath control -reg store_axi_w_input_to_output; -reg store_axi_w_input_to_temp; -reg store_axi_w_temp_to_output; - -assign s_axi_wready = s_axi_wready_reg; - -assign m_axi_wdata = m_axi_wdata_reg; -assign m_axi_wstrb = m_axi_wstrb_reg; -assign m_axi_wlast = m_axi_wlast_reg; -assign m_axi_wuser = WUSER_ENABLE ? m_axi_wuser_reg : {WUSER_WIDTH{1'b0}}; -assign m_axi_wvalid = m_axi_wvalid_reg; - -// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) -wire s_axi_wready_early = m_axi_wready | (~temp_m_axi_wvalid_reg & (~m_axi_wvalid_reg | ~s_axi_wvalid)); - -always @* begin - // transfer sink ready state to source - m_axi_wvalid_next = m_axi_wvalid_reg; - temp_m_axi_wvalid_next = temp_m_axi_wvalid_reg; - - store_axi_w_input_to_output = 1'b0; - store_axi_w_input_to_temp = 1'b0; - store_axi_w_temp_to_output = 1'b0; - - if (s_axi_wready_reg) begin - // input is ready - if (m_axi_wready | ~m_axi_wvalid_reg) begin - // output is ready or currently not valid, transfer data to output - m_axi_wvalid_next = s_axi_wvalid; - store_axi_w_input_to_output = 1'b1; - end else begin - // output is not ready, store input in temp - temp_m_axi_wvalid_next = s_axi_wvalid; - store_axi_w_input_to_temp = 1'b1; - end - end else if (m_axi_wready) begin - // input is not ready, but output is ready - m_axi_wvalid_next = temp_m_axi_wvalid_reg; - temp_m_axi_wvalid_next = 1'b0; - store_axi_w_temp_to_output = 1'b1; - end -end - -always @(posedge clk) begin - if (rst) begin - s_axi_wready_reg <= 1'b0; - m_axi_wvalid_reg <= 1'b0; - temp_m_axi_wvalid_reg <= 1'b0; - end else begin - s_axi_wready_reg <= s_axi_wready_early; - m_axi_wvalid_reg <= m_axi_wvalid_next; - temp_m_axi_wvalid_reg <= temp_m_axi_wvalid_next; - end - - // datapath - if (store_axi_w_input_to_output) begin - m_axi_wdata_reg <= s_axi_wdata; - m_axi_wstrb_reg <= s_axi_wstrb; - m_axi_wlast_reg <= s_axi_wlast; - m_axi_wuser_reg <= s_axi_wuser; - end else if (store_axi_w_temp_to_output) begin - m_axi_wdata_reg <= temp_m_axi_wdata_reg; - m_axi_wstrb_reg <= temp_m_axi_wstrb_reg; - m_axi_wlast_reg <= temp_m_axi_wlast_reg; - m_axi_wuser_reg <= temp_m_axi_wuser_reg; - end - - if (store_axi_w_input_to_temp) begin - temp_m_axi_wdata_reg <= s_axi_wdata; - temp_m_axi_wstrb_reg <= s_axi_wstrb; - temp_m_axi_wlast_reg <= s_axi_wlast; - temp_m_axi_wuser_reg <= s_axi_wuser; - end -end - -end else if (W_REG_TYPE == 1) begin -// simple register, inserts bubble cycles - -// datapath registers -reg s_axi_wready_reg = 1'b0; - -reg [DATA_WIDTH-1:0] m_axi_wdata_reg = {DATA_WIDTH{1'b0}}; -reg [STRB_WIDTH-1:0] m_axi_wstrb_reg = {STRB_WIDTH{1'b0}}; -reg m_axi_wlast_reg = 1'b0; -reg [WUSER_WIDTH-1:0] m_axi_wuser_reg = {WUSER_WIDTH{1'b0}}; -reg m_axi_wvalid_reg = 1'b0, m_axi_wvalid_next; - -// datapath control -reg store_axi_w_input_to_output; - -assign s_axi_wready = s_axi_wready_reg; - -assign m_axi_wdata = m_axi_wdata_reg; -assign m_axi_wstrb = m_axi_wstrb_reg; -assign m_axi_wlast = m_axi_wlast_reg; -assign m_axi_wuser = WUSER_ENABLE ? m_axi_wuser_reg : {WUSER_WIDTH{1'b0}}; -assign m_axi_wvalid = m_axi_wvalid_reg; - -// enable ready input next cycle if output buffer will be empty -wire s_axi_wready_ewly = !m_axi_wvalid_next; - -always @* begin - // transfer sink ready state to source - m_axi_wvalid_next = m_axi_wvalid_reg; - - store_axi_w_input_to_output = 1'b0; - - if (s_axi_wready_reg) begin - m_axi_wvalid_next = s_axi_wvalid; - store_axi_w_input_to_output = 1'b1; - end else if (m_axi_wready) begin - m_axi_wvalid_next = 1'b0; - end -end - -always @(posedge clk) begin - if (rst) begin - s_axi_wready_reg <= 1'b0; - m_axi_wvalid_reg <= 1'b0; - end else begin - s_axi_wready_reg <= s_axi_wready_ewly; - m_axi_wvalid_reg <= m_axi_wvalid_next; - end - - // datapath - if (store_axi_w_input_to_output) begin - m_axi_wdata_reg <= s_axi_wdata; - m_axi_wstrb_reg <= s_axi_wstrb; - m_axi_wlast_reg <= s_axi_wlast; - m_axi_wuser_reg <= s_axi_wuser; - end -end - -end else begin - - // bypass W channel - assign m_axi_wdata = s_axi_wdata; - assign m_axi_wstrb = s_axi_wstrb; - assign m_axi_wlast = s_axi_wlast; - assign m_axi_wuser = WUSER_ENABLE ? s_axi_wuser : {WUSER_WIDTH{1'b0}}; - assign m_axi_wvalid = s_axi_wvalid; - assign s_axi_wready = m_axi_wready; - -end - -// B channel - -if (B_REG_TYPE > 1) begin -// skid buffer, no bubble cycles - -// datapath registers -reg m_axi_bready_reg = 1'b0; - -reg [ID_WIDTH-1:0] s_axi_bid_reg = {ID_WIDTH{1'b0}}; -reg [1:0] s_axi_bresp_reg = 2'b0; -reg [BUSER_WIDTH-1:0] s_axi_buser_reg = {BUSER_WIDTH{1'b0}}; -reg s_axi_bvalid_reg = 1'b0, s_axi_bvalid_next; - -reg [ID_WIDTH-1:0] temp_s_axi_bid_reg = {ID_WIDTH{1'b0}}; -reg [1:0] temp_s_axi_bresp_reg = 2'b0; -reg [BUSER_WIDTH-1:0] temp_s_axi_buser_reg = {BUSER_WIDTH{1'b0}}; -reg temp_s_axi_bvalid_reg = 1'b0, temp_s_axi_bvalid_next; - -// datapath control -reg store_axi_b_input_to_output; -reg store_axi_b_input_to_temp; -reg store_axi_b_temp_to_output; - -assign m_axi_bready = m_axi_bready_reg; - -assign s_axi_bid = s_axi_bid_reg; -assign s_axi_bresp = s_axi_bresp_reg; -assign s_axi_buser = BUSER_ENABLE ? s_axi_buser_reg : {BUSER_WIDTH{1'b0}}; -assign s_axi_bvalid = s_axi_bvalid_reg; - -// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) -wire m_axi_bready_early = s_axi_bready | (~temp_s_axi_bvalid_reg & (~s_axi_bvalid_reg | ~m_axi_bvalid)); - -always @* begin - // transfer sink ready state to source - s_axi_bvalid_next = s_axi_bvalid_reg; - temp_s_axi_bvalid_next = temp_s_axi_bvalid_reg; - - store_axi_b_input_to_output = 1'b0; - store_axi_b_input_to_temp = 1'b0; - store_axi_b_temp_to_output = 1'b0; - - if (m_axi_bready_reg) begin - // input is ready - if (s_axi_bready | ~s_axi_bvalid_reg) begin - // output is ready or currently not valid, transfer data to output - s_axi_bvalid_next = m_axi_bvalid; - store_axi_b_input_to_output = 1'b1; - end else begin - // output is not ready, store input in temp - temp_s_axi_bvalid_next = m_axi_bvalid; - store_axi_b_input_to_temp = 1'b1; - end - end else if (s_axi_bready) begin - // input is not ready, but output is ready - s_axi_bvalid_next = temp_s_axi_bvalid_reg; - temp_s_axi_bvalid_next = 1'b0; - store_axi_b_temp_to_output = 1'b1; - end -end - -always @(posedge clk) begin - if (rst) begin - m_axi_bready_reg <= 1'b0; - s_axi_bvalid_reg <= 1'b0; - temp_s_axi_bvalid_reg <= 1'b0; - end else begin - m_axi_bready_reg <= m_axi_bready_early; - s_axi_bvalid_reg <= s_axi_bvalid_next; - temp_s_axi_bvalid_reg <= temp_s_axi_bvalid_next; - end - - // datapath - if (store_axi_b_input_to_output) begin - s_axi_bid_reg <= m_axi_bid; - s_axi_bresp_reg <= m_axi_bresp; - s_axi_buser_reg <= m_axi_buser; - end else if (store_axi_b_temp_to_output) begin - s_axi_bid_reg <= temp_s_axi_bid_reg; - s_axi_bresp_reg <= temp_s_axi_bresp_reg; - s_axi_buser_reg <= temp_s_axi_buser_reg; - end - - if (store_axi_b_input_to_temp) begin - temp_s_axi_bid_reg <= m_axi_bid; - temp_s_axi_bresp_reg <= m_axi_bresp; - temp_s_axi_buser_reg <= m_axi_buser; - end -end - -end else if (B_REG_TYPE == 1) begin -// simple register, inserts bubble cycles - -// datapath registers -reg m_axi_bready_reg = 1'b0; - -reg [ID_WIDTH-1:0] s_axi_bid_reg = {ID_WIDTH{1'b0}}; -reg [1:0] s_axi_bresp_reg = 2'b0; -reg [BUSER_WIDTH-1:0] s_axi_buser_reg = {BUSER_WIDTH{1'b0}}; -reg s_axi_bvalid_reg = 1'b0, s_axi_bvalid_next; - -// datapath control -reg store_axi_b_input_to_output; - -assign m_axi_bready = m_axi_bready_reg; - -assign s_axi_bid = s_axi_bid_reg; -assign s_axi_bresp = s_axi_bresp_reg; -assign s_axi_buser = BUSER_ENABLE ? s_axi_buser_reg : {BUSER_WIDTH{1'b0}}; -assign s_axi_bvalid = s_axi_bvalid_reg; - -// enable ready input next cycle if output buffer will be empty -wire m_axi_bready_early = !s_axi_bvalid_next; - -always @* begin - // transfer sink ready state to source - s_axi_bvalid_next = s_axi_bvalid_reg; - - store_axi_b_input_to_output = 1'b0; - - if (m_axi_bready_reg) begin - s_axi_bvalid_next = m_axi_bvalid; - store_axi_b_input_to_output = 1'b1; - end else if (s_axi_bready) begin - s_axi_bvalid_next = 1'b0; - end -end - -always @(posedge clk) begin - if (rst) begin - m_axi_bready_reg <= 1'b0; - s_axi_bvalid_reg <= 1'b0; - end else begin - m_axi_bready_reg <= m_axi_bready_early; - s_axi_bvalid_reg <= s_axi_bvalid_next; - end - - // datapath - if (store_axi_b_input_to_output) begin - s_axi_bid_reg <= m_axi_bid; - s_axi_bresp_reg <= m_axi_bresp; - s_axi_buser_reg <= m_axi_buser; - end -end - -end else begin - - // bypass B channel - assign s_axi_bid = m_axi_bid; - assign s_axi_bresp = m_axi_bresp; - assign s_axi_buser = BUSER_ENABLE ? m_axi_buser : {BUSER_WIDTH{1'b0}}; - assign s_axi_bvalid = m_axi_bvalid; - assign m_axi_bready = s_axi_bready; - -end - -endgenerate - -endmodule - -`resetall diff --git a/xls/modules/zstd/external/priority_encoder.v b/xls/modules/zstd/external/priority_encoder.v deleted file mode 100644 index cf82512ba8..0000000000 --- a/xls/modules/zstd/external/priority_encoder.v +++ /dev/null @@ -1,92 +0,0 @@ -/* - -Copyright (c) 2014-2021 Alex Forencich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -// Language: Verilog 2001 - -`resetall -`timescale 1ns / 1ps -`default_nettype none - -/* - * Priority encoder module - */ -module priority_encoder # -( - parameter WIDTH = 4, - // LSB priority selection - parameter LSB_HIGH_PRIORITY = 0 -) -( - input wire [WIDTH-1:0] input_unencoded, - output wire output_valid, - output wire [$clog2(WIDTH)-1:0] output_encoded, - output wire [WIDTH-1:0] output_unencoded -); - -parameter LEVELS = WIDTH > 2 ? $clog2(WIDTH) : 1; -parameter W = 2**LEVELS; - -// pad input to even power of two -wire [W-1:0] input_padded = {{W-WIDTH{1'b0}}, input_unencoded}; - -wire [W/2-1:0] stage_valid[LEVELS-1:0]; -wire [W/2-1:0] stage_enc[LEVELS-1:0]; - -generate - genvar l, n; - - // process input bits; generate valid bit and encoded bit for each pair - for (n = 0; n < W/2; n = n + 1) begin : loop_in - assign stage_valid[0][n] = |input_padded[n*2+1:n*2]; - if (LSB_HIGH_PRIORITY) begin - // bit 0 is highest priority - assign stage_enc[0][n] = !input_padded[n*2+0]; - end else begin - // bit 0 is lowest priority - assign stage_enc[0][n] = input_padded[n*2+1]; - end - end - - // compress down to single valid bit and encoded bus - for (l = 1; l < LEVELS; l = l + 1) begin : loop_levels - for (n = 0; n < W/(2*2**l); n = n + 1) begin : loop_compress - assign stage_valid[l][n] = |stage_valid[l-1][n*2+1:n*2]; - if (LSB_HIGH_PRIORITY) begin - // bit 0 is highest priority - assign stage_enc[l][(n+1)*(l+1)-1:n*(l+1)] = stage_valid[l-1][n*2+0] ? {1'b0, stage_enc[l-1][(n*2+1)*l-1:(n*2+0)*l]} : {1'b1, stage_enc[l-1][(n*2+2)*l-1:(n*2+1)*l]}; - end else begin - // bit 0 is lowest priority - assign stage_enc[l][(n+1)*(l+1)-1:n*(l+1)] = stage_valid[l-1][n*2+1] ? {1'b1, stage_enc[l-1][(n*2+2)*l-1:(n*2+1)*l]} : {1'b0, stage_enc[l-1][(n*2+1)*l-1:(n*2+0)*l]}; - end - end - end -endgenerate - -assign output_valid = stage_valid[LEVELS-1]; -assign output_encoded = stage_enc[LEVELS-1]; -assign output_unencoded = 1 << output_encoded; - -endmodule - -`resetall diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index 8985397377..1bf439c728 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -1696,15 +1696,15 @@ async def zstd_compressed_frames_test(dut): modified_zstd_verilog.name, "xls/modules/zstd/rtl/xls_fifo_wrapper.sv", "xls/modules/zstd/rtl/zstd_dec_wrapper.sv", - "xls/modules/zstd/external/axi_crossbar_wrapper.v", - "xls/modules/zstd/external/axi_crossbar.v", - "xls/modules/zstd/external/axi_crossbar_rd.v", - "xls/modules/zstd/external/axi_crossbar_wr.v", - "xls/modules/zstd/external/axi_crossbar_addr.v", - "xls/modules/zstd/external/axi_register_rd.v", - "xls/modules/zstd/external/axi_register_wr.v", - "xls/modules/zstd/external/arbiter.v", - "xls/modules/zstd/external/priority_encoder.v", + "xls/modules/zstd/axi_crossbar_wrapper.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_rd.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_wr.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_addr.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_register_rd.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_register_wr.v", + "external/com_github_alexforencich_verilog_axi/rtl/arbiter.v", + "external/com_github_alexforencich_verilog_axi/rtl/priority_encoder.v", "xls/modules/zstd/rtl/ram_1r1w.v", ] From a05cfbaa02b13b9f17b004f83c5d30d0d1368941 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 23 Jul 2025 20:33:08 +0200 Subject: [PATCH 023/159] Downgrade dependencies Signed-off-by: Robert Winkler --- dependency_support/pip_requirements.in | 3 +- dependency_support/pip_requirements_lock.txt | 85 +++++++------------ xls/modules/zstd/BUILD | 8 -- xls/modules/zstd/cocotb/utils.py | 38 ++++++--- xls/modules/zstd/memory/BUILD | 8 -- .../zstd/memory/axi_writer_cocotb_test.py | 17 ++-- .../zstd/memory/mem_writer_cocotb_test.py | 17 ++-- xls/modules/zstd/rtl/zstd_dec_wrapper.sv | 6 +- xls/modules/zstd/zstd_dec_cocotb_test.py | 5 +- 9 files changed, 88 insertions(+), 99 deletions(-) diff --git a/dependency_support/pip_requirements.in b/dependency_support/pip_requirements.in index 989620844a..2b17ca92d9 100644 --- a/dependency_support/pip_requirements.in +++ b/dependency_support/pip_requirements.in @@ -15,8 +15,7 @@ pyyaml==6.0.1 # We build most of z3 ourselves but building python is really complicated. Just # use pypi version z3-solver==4.14.0.0 -pytest==8.2.2 -cocotb==1.9.0 +cocotb==1.7.2 cocotbext-axi==0.1.24 cocotb_bus==0.2.1 zstandard==0.23.0 diff --git a/dependency_support/pip_requirements_lock.txt b/dependency_support/pip_requirements_lock.txt index 3a84ca1a7c..d54e046dd5 100644 --- a/dependency_support/pip_requirements_lock.txt +++ b/dependency_support/pip_requirements_lock.txt @@ -14,43 +14,38 @@ click==8.1.3 \ # via # -r dependency_support/pip_requirements.in # flask -cocotb==1.9.0 \ - --hash=sha256:02a58ef6c941114964096e7c039bdd4e67e63816cfd2f6a9af6a34cd92b00e8e \ - --hash=sha256:0819794ef5e8fd14fee0b265933226cf600e85edc2f1a749b4d5f8fa2d31ce4e \ - --hash=sha256:0ba35617a677ff65a1273411a3dfdfc5f587128ad8cb9e941ab0eb17ec8fb3e2 \ - --hash=sha256:17556e3a23562f64d577d0eb117fe02e384aedee997b29497b5c395f5010ff82 \ - --hash=sha256:19b4e27b53a16e0b9c4cc5227c7f9d4dccac06e431a4f937e9f5513350196333 \ - --hash=sha256:1a0381ced5590a726032ba2265c6b70ac12cfb49edb152be86a081bb7d104751 \ - --hash=sha256:1aff68cf77059448a9a3278079037e34b50c8c2aee466d984295fa7fe699d390 \ - --hash=sha256:277281420fd6fc3002bb85d6bec497bd20ff3a3905d4b5f1301faf975f750ede \ - --hash=sha256:2daf743320331615f4e8ffb877ab0b04e6f913b911bb11bf9dbc1d876d9c4220 \ - --hash=sha256:2e9bcdbfba3e99c9297bd0d74ba781772d89d2c86e893980784ada252bd1a0f8 \ - --hash=sha256:3058c977f9d4e1f6333d505947f34b9142910719f1d8631c40a151dd86bad727 \ - --hash=sha256:5832d894419a9e8fe5c242e3ac86588e16e2cb379822dcb154bfec8544ae858e \ - --hash=sha256:598b841ed0809e5c64d8c383b8035f6ace5a6f9013f680cdc6981221911c005d \ - --hash=sha256:5a5c91027d7652aaf10e101743edd6b1e832039a19af75fca301275ef30f01d4 \ - --hash=sha256:61418f619af72c8cca8de622785b4f4bfc17ace09981de6eb44feae560cf3bbb \ - --hash=sha256:784c914c8df3fd79cfb148d2bcd17c4b2703c89af1278ed98773afb57ceea3e6 \ - --hash=sha256:87a19d3012f505ba7fda37483b851ef0ca40290ad8a9b28a820b84f8574287bb \ - --hash=sha256:89503f0749362d36b6fab8636710f1848943c21f9d488672921bac21e9edd29f \ - --hash=sha256:89e5189fd393918c27af2daefdcb13df4d52fa761f065d5964d2c4ff5c0642fb \ - --hash=sha256:8cb4b0edf8f0b47c3b604b461cb574fc75fd97efa893cbaf828f4f2f71cf459e \ - --hash=sha256:94e884e16186899ad5b4d131c3f7ff0a2277e67ea0660754e8810a4bbf2d610e \ - --hash=sha256:997dbca2a2cd933fd0a44d9fadeebc1e8a40701db15ea06f207811933dceb350 \ - --hash=sha256:a7cea13cb2fe4f5ca735490846342885117778a73008a67ed9cac667aaaf3f0d \ - --hash=sha256:a84edfbfa57dc6e16845a55feb0b4e1c8b6bbfa5ef1ab6768beba8d81e0546aa \ - --hash=sha256:a95b5e5708a3629d319d2b655d11345cc7e97fea9bdc9bc1df7435926ac30966 \ - --hash=sha256:aa6818c39ca1ce699e4bb1d84899c4f98c2d25c7671bd6c7beee3b1ee9d68834 \ - --hash=sha256:ab99bf7e055780b57419d4133fd4dca9c72a03b766a3e2200552f10498eb8845 \ - --hash=sha256:b966f5560a494fd99f95a1562f9326ca20c35bb118d4e6b50db41da8e4a6f718 \ - --hash=sha256:bc44a7708a5a63d3059a622c2fb90831dc33534c3343e971f5a6c78905097baa \ - --hash=sha256:c11e21d291ba2f889e33c21d76e9aec6ffdfb5666053dc34452666579daa675b \ - --hash=sha256:c848de13583478d71cc91e528e17c051ca6a3b92e89d703ac5015f17cab1287b \ - --hash=sha256:d944aa5509a0f0786d6f30554a2f8b1f229847f9ac9988879d7a05497739f668 \ - --hash=sha256:f50862153e1364f6edeaef9d70505093549fa097e9b2555ea46d1e4f94ac3287 \ - --hash=sha256:f74c598e230e1035103f6e3a97dd7a0e1bcacf7f3ea7481cd3bcde477b74e379 \ - --hash=sha256:fcb81c6c37e11b0729768dd8e192a9cfb809778699ab1fe89f4d92ba0beb3092 \ - --hash=sha256:ff2ddc8b304eb7076ceead2534a1b9828df771798fa9c2601ea983c86d23ec08 +cocotb==1.7.2 \ + --hash=sha256:0c1687ac78141724b8529e029ee6299698ecaa8a2c431b744eeff487a4bb18de \ + --hash=sha256:163e5262020cc21f6a0391fb4727c9ab3ecbf6ee12a1472c8f7320b3ba211a50 \ + --hash=sha256:170cf4d01c4d7c6c5b141ffc1824e846a6c8adbed553a50984cd522c1dddb111 \ + --hash=sha256:1851ac56eed7bb6c745aabfc0e417195cb4f08b5df50846c04eb77a868bfeaba \ + --hash=sha256:1abffb36183b07469c490836c66d8b9e24fc1bec7c27356818618a6719fabd4b \ + --hash=sha256:33be79f048f4072240668a079d2bcebd1a24611a0a1e55439b65ffa0ff077790 \ + --hash=sha256:34ab1bf3f18476724dd4e21dbcc0e060e813eb502abe155b800084fb6945360c \ + --hash=sha256:360019f74270661d14e9caa8103e740a070cb466ab08376a565ec0ef4c13dbbf \ + --hash=sha256:37ddb79f4ab60d2d2dc5a9db5bf767d226eb4978fd15b84dfb968d31ab2fcda5 \ + --hash=sha256:43f5af578803e5726b5c75421c0e35e54021ab423d3aa4efe930feb740d6479d \ + --hash=sha256:4738f36b9730cc05b74ccba3648dba0455cf9f237abf822ef307a274a29474c2 \ + --hash=sha256:4aa5d73ebdb59ef24cef36a1f8cca11dcecb3ee7b71a84df02751020bc67ea77 \ + --hash=sha256:574d21501ff1a3d36889397cd58a18d102d0e40391aa7a0274b600d1cc4c7dc3 \ + --hash=sha256:69f4e539dd308c9e169ab23135138ec397061b700f209803a6022ae9fbe08933 \ + --hash=sha256:6f289ac00f4884046ec64db7006e47b1c857a36dcd2a80ea0873cbff00248368 \ + --hash=sha256:707f795a17679b4653a50bd4094536a46fbfee5c6e3d951fac4320ee211ad13f \ + --hash=sha256:7828e22946f128aa59cb9254de4037b99e3bd5a51fe8f590cf64a3141d742a37 \ + --hash=sha256:82f694da656a699154b15ee28be3ac39c41a71d33985313deda12a3645f8b3db \ + --hash=sha256:959892eb94bd0b3ff40e0fca51d33a3936416deb853e2bac4f7f766b40002650 \ + --hash=sha256:a7ec6a2d212c27ec46bed17a15d60b7b29cd0f734f11cc16d2cb4d3f6136e133 \ + --hash=sha256:a90c77f4bbfdf73aa16093dfe95c68af1a1ca685ebfa525f3f150eab252f6728 \ + --hash=sha256:b288a59fa8dffc1cbc53105e71e2f8c82421081b17282e41319832654b309477 \ + --hash=sha256:c41cc8d4ece57f5e26076cd12f1e11d464d7f118fdb74b958269535185d99a30 \ + --hash=sha256:c8dce91d2a918ee63338d79b08e3d52f1d2797efd9c2bedd13c33d674f730db8 \ + --hash=sha256:d26a8a40cea61f295be04b1164a5dd9ec873f13a39814ad00efec7fd899320e0 \ + --hash=sha256:d80b3baafff1a8a91ac860023c448c603767bed502258160a5cb6029976fec4f \ + --hash=sha256:dcf5354268f16d9e11e05cf3616172ca5ef503b45567f75ebd96a0bfdb9832d1 \ + --hash=sha256:ded849360fb31746f1ba3a994f89c3bba2466ec2d0b4b5da0030645645f938d4 \ + --hash=sha256:e03df73573aec261447602904bd66927eeb2f00dd24370dc9a57f47fd42c4d70 \ + --hash=sha256:f97c2eb92cb68831f19b82ba0038ce40fa73c5edbffb7930745edac20c5358d1 \ + --hash=sha256:fa8abed5260baf4306fbfb997c8789fe24bc229cd762b12d7dba0b9c20147b1d # via # -r dependency_support/pip_requirements.in # cocotb-bus @@ -184,10 +179,6 @@ fonttools==4.55.8 \ --hash=sha256:f089e8da0990cfe2d67e81d9cf581ff372b48dc5acf2782701844211cd1f0eb3 \ --hash=sha256:f971aa5f50c22dc4b63a891503624ae2c77330429b34ead32f23c2260c5618cd # via matplotlib -iniconfig==2.0.0 \ - --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ - --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 - # via pytest itsdangerous==2.1.2 \ --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a @@ -450,9 +441,7 @@ numpy==2.3.4 \ packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f - # via - # matplotlib - # pytest + # via matplotlib pillow==11.1.0 \ --hash=sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83 \ --hash=sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96 \ @@ -526,10 +515,6 @@ pillow==11.1.0 \ --hash=sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9 \ --hash=sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761 # via matplotlib -pluggy==1.5.0 \ - --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ - --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 - # via pytest portpicker==1.3.1 \ --hash=sha256:d2cdc776873635ed421315c4d22e63280042456bbfa07397817e687b142b9667 # via -r dependency_support/pip_requirements.in @@ -550,10 +535,6 @@ pyparsing==3.2.1 \ --hash=sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1 \ --hash=sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a # via matplotlib -pytest==8.2.2 \ - --hash=sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343 \ - --hash=sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977 - # via -r dependency_support/pip_requirements.in python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 546579e119..be5fe67291 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1821,17 +1821,11 @@ py_test( "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], - env = { - "BUILD_WORKING_DIRECTORY": "sim_build", - "PYTHONUNBUFFERED": "1", - }, - imports = ["."], tags = ["manual"], visibility = ["//xls:xls_users"], deps = [ requirement("cocotb"), requirement("cocotbext-axi"), - requirement("pytest"), requirement("zstandard"), "//xls/common:runfiles", "//xls/modules/zstd/cocotb:channel", @@ -2268,7 +2262,6 @@ py_test( "@com_icarus_iverilog//:vvp", ], env = { - "BUILD_WORKING_DIRECTORY": "sim_build", "PYTHONUNBUFFERED": "1", }, imports = ["."], @@ -2277,7 +2270,6 @@ py_test( deps = [ requirement("cocotb"), requirement("cocotbext-axi"), - requirement("pytest"), requirement("zstandard"), "//xls/common:runfiles", "//xls/modules/zstd/cocotb:channel", diff --git a/xls/modules/zstd/cocotb/utils.py b/xls/modules/zstd/cocotb/utils.py index 01688f96a3..43f9ca16bc 100644 --- a/xls/modules/zstd/cocotb/utils.py +++ b/xls/modules/zstd/cocotb/utils.py @@ -34,27 +34,45 @@ def setup_com_iverilog(): ) os.environ["PATH"] += os.pathsep + str(iverilog_path.parent) os.environ["PATH"] += os.pathsep + str(vvp_path.parent) - build_dir = pathlib.Path(os.environ['BUILD_WORKING_DIRECTORY'], "sim_build") + build_dir = pathlib.Path("sim_build").absolute() return build_dir -def run_test(toplevel, test_module, verilog_sources): + +def run_test(toplevel, test_module, verilog_sources, timescale=("1ns", "1ps")): """Builds and runs a Cocotb testbench using Icarus Verilog.""" build_dir = setup_com_iverilog() - runner = get_runner("icarus") + runner = get_runner("icarus")() + build_args = [] + + cmds_file = build_dir / pathlib.Path("cmds.f") + cmds_file.parent.mkdir(parents=True, exist_ok=True) + with open(cmds_file, "w") as f: + f.write("+timescale+{}/{}\n".format(*timescale)) + build_args += ["-f", str(cmds_file)] + + dump_file = build_dir / pathlib.Path("cocotb_iverilog_dump.v") + wave_file = build_dir / pathlib.Path(f"{toplevel}.fst") + with open(dump_file, "w") as f: + f.write("module cocotb_iverilog_dump();\n") + f.write("initial begin\n") + f.write(f' $dumpfile("{wave_file}");\n') + f.write(f" $dumpvars(0, {toplevel});\n") + f.write("end\n") + f.write("endmodule\n") + wave_file.parent.mkdir(parents=True, exist_ok=True) + runner.build( - verilog_sources=verilog_sources, - hdl_toplevel=toplevel, - timescale=("1ns", "1ps"), + verilog_sources=(verilog_sources + [str(dump_file)]), + toplevel=toplevel, build_dir=build_dir, + extra_args=build_args, defines={"SIMULATION": "1"}, - waves=True, ) try: results_xml = runner.test( - hdl_toplevel=toplevel, - test_module=test_module, - waves=True, + toplevel=toplevel, + py_module=test_module, ) finally: check_results_file(results_xml) diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index d30f9b02c8..afcc829bfe 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -557,13 +557,11 @@ py_test( "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], - env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, tags = ["manual"], visibility = ["//xls:xls_users"], deps = [ requirement("cocotb"), requirement("cocotbext-axi"), - requirement("pytest"), "//xls/common:runfiles", "//xls/modules/zstd/cocotb:channel", "//xls/modules/zstd/cocotb:memory", @@ -653,14 +651,11 @@ py_test( "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], - env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, - imports = ["."], tags = ["manual"], visibility = ["//xls:xls_users"], deps = [ requirement("cocotb"), requirement("cocotbext-axi"), - requirement("pytest"), "//xls/common:runfiles", "//xls/modules/zstd/cocotb:channel", "//xls/modules/zstd/cocotb:utils", @@ -819,14 +814,11 @@ py_test( "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", ], - env = {"BUILD_WORKING_DIRECTORY": "sim_build"}, - imports = ["."], tags = ["manual"], visibility = ["//xls:xls_users"], deps = [ requirement("cocotb"), requirement("cocotbext-axi"), - requirement("pytest"), "//xls/common:runfiles", "//xls/modules/zstd/cocotb:channel", "//xls/modules/zstd/cocotb:utils", diff --git a/xls/modules/zstd/memory/axi_writer_cocotb_test.py b/xls/modules/zstd/memory/axi_writer_cocotb_test.py index 2ad396c3c6..270a390416 100644 --- a/xls/modules/zstd/memory/axi_writer_cocotb_test.py +++ b/xls/modules/zstd/memory/axi_writer_cocotb_test.py @@ -16,6 +16,7 @@ import random import logging import pathlib +import sys import cocotb from cocotb.clock import Clock @@ -300,10 +301,12 @@ def generate_test_data_arbitrary(mem_size): ) if __name__ == "__main__": - toplevel = "axi_writer_wrapper" - verilog_sources = [ - "xls/modules/zstd/memory/axi_writer.v", - "xls/modules/zstd/memory/rtl/axi_writer_wrapper.v", - ] - test_module=[pathlib.Path(__file__).stem] - utils.run_test(toplevel, test_module, verilog_sources) + sys.path.append(str(pathlib.Path(__file__).parent)) + + toplevel = "axi_writer_wrapper" + verilog_sources = [ + "xls/modules/zstd/memory/axi_writer.v", + "xls/modules/zstd/memory/rtl/axi_writer_wrapper.v", + ] + test_module=[pathlib.Path(__file__).stem] + utils.run_test(toplevel, test_module, verilog_sources) diff --git a/xls/modules/zstd/memory/mem_writer_cocotb_test.py b/xls/modules/zstd/memory/mem_writer_cocotb_test.py index 5767fbba2e..a74a5db2e7 100644 --- a/xls/modules/zstd/memory/mem_writer_cocotb_test.py +++ b/xls/modules/zstd/memory/mem_writer_cocotb_test.py @@ -17,6 +17,7 @@ import logging import enum import pathlib +import sys import cocotb from cocotb.clock import Clock @@ -717,13 +718,15 @@ def generate_padded_test_data_arbitrary(mem_size, test_cases): ) if __name__ == "__main__": - toplevel = "mem_writer_wrapper" - verilog_sources = [ - "xls/modules/zstd/memory/mem_writer.v", - "xls/modules/zstd/memory/rtl/mem_writer_wrapper.v", - ] - test_module=[pathlib.Path(__file__).stem] - utils.run_test(toplevel, test_module, verilog_sources) + sys.path.append(str(pathlib.Path(__file__).parent)) + + toplevel = "mem_writer_wrapper" + verilog_sources = [ + "xls/modules/zstd/memory/mem_writer.v", + "xls/modules/zstd/memory/rtl/mem_writer_wrapper.v", + ] + test_module=[pathlib.Path(__file__).stem] + utils.run_test(toplevel, test_module, verilog_sources) test_cases_single_burst_1_transfer = [ # Aligned Address; Aligned Length diff --git a/xls/modules/zstd/rtl/zstd_dec_wrapper.sv b/xls/modules/zstd/rtl/zstd_dec_wrapper.sv index 36029c7917..79a6199810 100644 --- a/xls/modules/zstd/rtl/zstd_dec_wrapper.sv +++ b/xls/modules/zstd/rtl/zstd_dec_wrapper.sv @@ -1304,7 +1304,7 @@ module zstd_dec_wrapper #( .SIZE(256), .NUM_PARTITIONS(1), .ADDR_WIDTH(15), - .INIT_FILE("../../xls/modules/zstd/zstd_dec_ll_fse_default.mem") + .INIT_FILE("../xls/modules/zstd/zstd_dec_ll_fse_default.mem") ) ll_def_fse_ram ( .clk(clk), .rst(rst), @@ -1361,7 +1361,7 @@ module zstd_dec_wrapper #( .SIZE(256), .NUM_PARTITIONS(1), .ADDR_WIDTH(15), - .INIT_FILE("../../xls/modules/zstd/zstd_dec_ml_fse_default.mem") + .INIT_FILE("../xls/modules/zstd/zstd_dec_ml_fse_default.mem") ) ml_def_fse_ram ( .clk(clk), .rst(rst), @@ -1418,7 +1418,7 @@ module zstd_dec_wrapper #( .SIZE(256), .NUM_PARTITIONS(1), .ADDR_WIDTH(15), - .INIT_FILE("../../xls/modules/zstd/zstd_dec_of_fse_default.mem") + .INIT_FILE("../xls/modules/zstd/zstd_dec_of_fse_default.mem") ) of_def_fse_ram ( .clk(clk), .rst(rst), diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index 1bf439c728..e212265fc9 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. - +import sys import enum import pathlib import tempfile @@ -550,7 +550,7 @@ async def zstd_rle_frames_test(dut): # Use them to verify progress in specific parts of the decoder. # TODO the workdir / data relation is weird. How to pass this better? -PREGENERATED_FILES_DIR = "../../xls/modules/zstd/data/" +PREGENERATED_FILES_DIR = "../xls/modules/zstd/data/" @cocotb.test(timeout_time=2000, timeout_unit="ms") @@ -1689,6 +1689,7 @@ async def zstd_compressed_frames_test(dut): # await testing_routine(dut, test_cases, block_type) if __name__ == "__main__": + sys.path.append(str(pathlib.Path(__file__).parent)) with tempfile.NamedTemporaryFile(mode="w") as modified_zstd_verilog: toplevel = "zstd_dec_wrapper" test_module = [pathlib.Path(__file__).stem] From d17570b1178facc48ad965b989f499a15cab3f4c Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 24 Jul 2025 16:49:36 +0200 Subject: [PATCH 024/159] Run tests selectively in GitHub CI Signed-off-by: Robert Winkler --- .github/workflows/modules-zstd.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/modules-zstd.yml b/.github/workflows/modules-zstd.yml index 4272e4b91b..bdf61906f5 100644 --- a/.github/workflows/modules-zstd.yml +++ b/.github/workflows/modules-zstd.yml @@ -45,7 +45,9 @@ jobs: - name: Test ZSTD Module - DSLX Tests (opt) if: ${{ !cancelled() }} run: | - bazel test -c opt --test_output=errors -- $(bazel query 'filter("_dslx_test$", kind(rule, //xls/modules/zstd/...))') + bazel test -c opt --test_output=errors -- $(bazel query 'filter("_dslx_test$", kind(rule, //xls/modules/zstd/...)) except filter("zstd_dec_dslx_test$", kind(rule, //xls/modules/zstd/...))'); + # Run all tests except those ending with `_skip` + bazel test -c opt --test_strategy=exclusive --test_output=errors --test_filter='^(.*[^_]|.*_[^s]|.*_s[^k]|.*_sk[^i]|.*_ski[^p])$' //xls/modules/zstd:zstd_dec_dslx_test; - name: Build ZSTD verilog targets (opt) if: ${{ !cancelled() }} From 2ce8a34ef36a2750bb415c202b40f0a60518ddc3 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 25 Jul 2025 16:19:06 +0200 Subject: [PATCH 025/159] Remove zstandard python library Signed-off-by: Robert Winkler --- dependency_support/pip_requirements.in | 1 - dependency_support/pip_requirements_lock.txt | 99 -------------------- xls/modules/zstd/BUILD | 3 - xls/modules/zstd/cocotb/BUILD | 1 + xls/modules/zstd/cocotb/data_generator.py | 14 ++- 5 files changed, 12 insertions(+), 106 deletions(-) diff --git a/dependency_support/pip_requirements.in b/dependency_support/pip_requirements.in index 2b17ca92d9..e7ae68826e 100644 --- a/dependency_support/pip_requirements.in +++ b/dependency_support/pip_requirements.in @@ -18,7 +18,6 @@ z3-solver==4.14.0.0 cocotb==1.7.2 cocotbext-axi==0.1.24 cocotb_bus==0.2.1 -zstandard==0.23.0 # Note: numpy and scipy version availability seems to differ between Ubuntu # versions that we want to support (e.g. 18.04 vs 20.04), so we accept a diff --git a/dependency_support/pip_requirements_lock.txt b/dependency_support/pip_requirements_lock.txt index d54e046dd5..c461d7d542 100644 --- a/dependency_support/pip_requirements_lock.txt +++ b/dependency_support/pip_requirements_lock.txt @@ -676,102 +676,3 @@ z3-solver==4.14.0.0 \ --hash=sha256:c10f899c6a876e3a50e9b2c4927604c2c3da3cca672b8ed3b7db1bc97259e47f \ --hash=sha256:e6ae32bc1cf4d25b96f790755d790a23118e8a09b9b6060e32238fe6ff43606d # via -r dependency_support/pip_requirements.in -zstandard==0.23.0 \ - --hash=sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473 \ - --hash=sha256:0a7f0804bb3799414af278e9ad51be25edf67f78f916e08afdb983e74161b916 \ - --hash=sha256:11e3bf3c924853a2d5835b24f03eeba7fc9b07d8ca499e247e06ff5676461a15 \ - --hash=sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072 \ - --hash=sha256:1516c8c37d3a053b01c1c15b182f3b5f5eef19ced9b930b684a73bad121addf4 \ - --hash=sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e \ - --hash=sha256:1bfe8de1da6d104f15a60d4a8a768288f66aa953bbe00d027398b93fb9680b26 \ - --hash=sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8 \ - --hash=sha256:1fd7e0f1cfb70eb2f95a19b472ee7ad6d9a0a992ec0ae53286870c104ca939e5 \ - --hash=sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd \ - --hash=sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c \ - --hash=sha256:29a2bc7c1b09b0af938b7a8343174b987ae021705acabcbae560166567f5a8db \ - --hash=sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5 \ - --hash=sha256:2ef3775758346d9ac6214123887d25c7061c92afe1f2b354f9388e9e4d48acfc \ - --hash=sha256:2f146f50723defec2975fb7e388ae3a024eb7151542d1599527ec2aa9cacb152 \ - --hash=sha256:2fb4535137de7e244c230e24f9d1ec194f61721c86ebea04e1581d9d06ea1269 \ - --hash=sha256:32ba3b5ccde2d581b1e6aa952c836a6291e8435d788f656fe5976445865ae045 \ - --hash=sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e \ - --hash=sha256:379b378ae694ba78cef921581ebd420c938936a153ded602c4fea612b7eaa90d \ - --hash=sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a \ - --hash=sha256:3aa014d55c3af933c1315eb4bb06dd0459661cc0b15cd61077afa6489bec63bb \ - --hash=sha256:4051e406288b8cdbb993798b9a45c59a4896b6ecee2f875424ec10276a895740 \ - --hash=sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105 \ - --hash=sha256:43da0f0092281bf501f9c5f6f3b4c975a8a0ea82de49ba3f7100e64d422a1274 \ - --hash=sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2 \ - --hash=sha256:48ef6a43b1846f6025dde6ed9fee0c24e1149c1c25f7fb0a0585572b2f3adc58 \ - --hash=sha256:50a80baba0285386f97ea36239855f6020ce452456605f262b2d33ac35c7770b \ - --hash=sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4 \ - --hash=sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db \ - --hash=sha256:53ea7cdc96c6eb56e76bb06894bcfb5dfa93b7adcf59d61c6b92674e24e2dd5e \ - --hash=sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9 \ - --hash=sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0 \ - --hash=sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813 \ - --hash=sha256:61062387ad820c654b6a6b5f0b94484fa19515e0c5116faf29f41a6bc91ded6e \ - --hash=sha256:61f89436cbfede4bc4e91b4397eaa3e2108ebe96d05e93d6ccc95ab5714be512 \ - --hash=sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0 \ - --hash=sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b \ - --hash=sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48 \ - --hash=sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a \ - --hash=sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772 \ - --hash=sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed \ - --hash=sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373 \ - --hash=sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea \ - --hash=sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd \ - --hash=sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f \ - --hash=sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc \ - --hash=sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23 \ - --hash=sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2 \ - --hash=sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db \ - --hash=sha256:82d17e94d735c99621bf8ebf9995f870a6b3e6d14543b99e201ae046dfe7de70 \ - --hash=sha256:837bb6764be6919963ef41235fd56a6486b132ea64afe5fafb4cb279ac44f259 \ - --hash=sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9 \ - --hash=sha256:8c24f21fa2af4bb9f2c492a86fe0c34e6d2c63812a839590edaf177b7398f700 \ - --hash=sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003 \ - --hash=sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba \ - --hash=sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a \ - --hash=sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c \ - --hash=sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90 \ - --hash=sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690 \ - --hash=sha256:a05e6d6218461eb1b4771d973728f0133b2a4613a6779995df557f70794fd60f \ - --hash=sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840 \ - --hash=sha256:a4ae99c57668ca1e78597d8b06d5af837f377f340f4cce993b551b2d7731778d \ - --hash=sha256:a8c86881813a78a6f4508ef9daf9d4995b8ac2d147dcb1a450448941398091c9 \ - --hash=sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35 \ - --hash=sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd \ - --hash=sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a \ - --hash=sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea \ - --hash=sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1 \ - --hash=sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573 \ - --hash=sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09 \ - --hash=sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094 \ - --hash=sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78 \ - --hash=sha256:b8c0bd73aeac689beacd4e7667d48c299f61b959475cdbb91e7d3d88d27c56b9 \ - --hash=sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5 \ - --hash=sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9 \ - --hash=sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391 \ - --hash=sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847 \ - --hash=sha256:c7c517d74bea1a6afd39aa612fa025e6b8011982a0897768a2f7c8ab4ebb78a2 \ - --hash=sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c \ - --hash=sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2 \ - --hash=sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057 \ - --hash=sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20 \ - --hash=sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d \ - --hash=sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4 \ - --hash=sha256:e2d1a054f8f0a191004675755448d12be47fa9bebbcffa3cdf01db19f2d30a54 \ - --hash=sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171 \ - --hash=sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e \ - --hash=sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160 \ - --hash=sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b \ - --hash=sha256:f8346bfa098532bc1fb6c7ef06783e969d87a99dd1d2a5a18a892c1d7a643c58 \ - --hash=sha256:f83fa6cae3fff8e98691248c9320356971b59678a17f20656a9e59cd32cee6d8 \ - --hash=sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33 \ - --hash=sha256:fb2b1ecfef1e67897d336de3a0e3f52478182d6a47eda86cbd42504c5cbd009a \ - --hash=sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880 \ - --hash=sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca \ - --hash=sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b \ - --hash=sha256:fe3b385d996ee0822fd46528d9f0443b880d4d05528fd26a9119a54ec3f91c69 - # via -r dependency_support/pip_requirements.in diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index be5fe67291..17313eadee 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1422,7 +1422,6 @@ py_binary( tags = ["manual"], visibility = ["//xls:xls_users"], deps = [ - requirement("zstandard"), "//xls/common:runfiles", "//xls/modules/zstd/cocotb:data_generator", "@com_google_protobuf//:protobuf_python", @@ -1826,7 +1825,6 @@ py_test( deps = [ requirement("cocotb"), requirement("cocotbext-axi"), - requirement("zstandard"), "//xls/common:runfiles", "//xls/modules/zstd/cocotb:channel", "//xls/modules/zstd/cocotb:data_generator", @@ -2270,7 +2268,6 @@ py_test( deps = [ requirement("cocotb"), requirement("cocotbext-axi"), - requirement("zstandard"), "//xls/common:runfiles", "//xls/modules/zstd/cocotb:channel", "//xls/modules/zstd/cocotb:data_generator", diff --git a/xls/modules/zstd/cocotb/BUILD b/xls/modules/zstd/cocotb/BUILD index cdb788d732..6c08efa697 100644 --- a/xls/modules/zstd/cocotb/BUILD +++ b/xls/modules/zstd/cocotb/BUILD @@ -71,5 +71,6 @@ py_library( deps = [ "//xls/common:runfiles", "@zstd//:decodecorpus", + "@zstd//:zstd_cli", ], ) diff --git a/xls/modules/zstd/cocotb/data_generator.py b/xls/modules/zstd/cocotb/data_generator.py index 3c9233f6d6..d0c5cc0495 100644 --- a/xls/modules/zstd/cocotb/data_generator.py +++ b/xls/modules/zstd/cocotb/data_generator.py @@ -19,7 +19,7 @@ from xls.common import runfiles import subprocess -import zstandard +import tempfile class BlockType(enum.Enum): """Enum encoding of ZSTD block types.""" @@ -67,8 +67,16 @@ def CallDecodecorpus(args): subprocess.run(cmd_concat, shell=True, check=True) def DecompressFrame(data): - dctx = zstandard.ZstdDecompressor() - return dctx.decompress(data) + zstd_cli = pathlib.Path(runfiles.get_path("zstd_cli", repository = "zstd")) + with tempfile.NamedTemporaryFile(mode='wb') as input_data, \ + tempfile.NamedTemporaryFile(mode='wb') as output_data: + input_data.write(data) + input_data.flush() + cmd = f"{str(zstd_cli)} -f -d {input_data.name} -o {output_data.name}" + output_data.close() + subprocess.run(cmd, shell=True, check=True) + with open(output_data.name, "rb") as output_data: + return output_data.read() def GenerateFrame(seed, btype, output_path, ltype=LiteralType.RANDOM): args = [] From 06faeb18d218e6142406eb07746b38925b81c950 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 10 Sep 2025 17:08:04 +0200 Subject: [PATCH 026/159] modules: zstd: update readme Co-autored-by: Dominik Lau Signed-off-by: Robert Winkler --- xls/modules/zstd/README.md | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index aae9a03c8c..c0785c1fc7 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -10,7 +10,7 @@ the diagram below. The decoder comprises: * Control and Status Registers, * Frame Header Decoder, * Block Header Decoder, -* 3 types of processing units: RAW-, RLE-, and Compressed Block Decoders[^1], +* 3 types of processing units: RAW-, RLE-, and Compressed Block Decoders, * Command Aggregator, The Decoder interacts with the environment through a set of ports: @@ -39,7 +39,7 @@ Once the decoding process is started, the decoder: updates the history, 7. Prepares the final output of the decoder and writes it to the memory, 8. (Optional) Calculates checksum and compares it against the checksum read - from the frame.[^2] + from the frame.[^1] ![brief data flow diagram of ZstdDecoder](img/ZSTD_decoder.png) @@ -220,7 +220,7 @@ processing request to the `RleBlockDecoder`. The proc produces `N*s` repeats of the given symbol. This step preserves the block ID and attaches the literals tag to all its outputs. -#### CompressedBlockDecoder[^1] +#### CompressedBlockDecoder This part of the design is responsible for decoding the compressed data blocks. It ingests the bytes stream, and internally translates and interprets incoming @@ -262,7 +262,7 @@ will show the following behavior, depending on the tag: * Copy `copy_length` literals starting `offset _length` from the newest in history buffer to the history buffer as the newest. -### Compressed block decoder architecture[^1] {#compressed-block-decoder-architecture1} +### Compressed block decoder architecture {#compressed-block-decoder-architecture1} This part of the design is responsible for processing the compressed blocks up to the `literals`/`copy` command sequence. This sequence is then processed by @@ -278,7 +278,7 @@ Treeless blocks. #### Compressed block dispatcher This proc parses literals section headers to calculate block compression format, -Huffmman tree size (if applicable based on compression format), compressed and +Huffman tree size (if applicable based on compression format), compressed and regenerated sizes for literals. If compressed block format is `Compressed_Literals_Block`, dispatcher reads Huffman tree header byte from Huffman bitstream, and directs expected number of bytes to the Huffman tree @@ -493,6 +493,8 @@ The Basic test case for the ZstdDecoder is composed of the following steps: 6. Test case succeeds once `Notify` is asserted, all expected data is received and the decoder lands in `IDLE` state with status `OKAY` in the `Status` CSR. +Additionally, pregenerated test cases are provided in the [data](data/) subdirectory. `*.zst` files contain frames encoded using the ZSTD library. Supplementary `*.log` files provide additional info regarding the contents of each frame. Among the aforementioned test cases, those generated using `decodecorpus` follow the `__seed_` naming convention. + ### Failure points #### User-facing decoder errors @@ -506,6 +508,10 @@ The design will fail the tests under the following conditions: results from the reference library * The decoding result from the simulation has different contents than the results from the reference library +* Failures caused by incorrect intermediate results (only in the selected tests): + * Incorrect decoding of the FSE table + * Incorrect Huffman weights + * Incorrect Huffman codebook Currently, all mentioned conditions lead to an eventual test failure. @@ -566,14 +572,6 @@ This is done for example in: * Frame header decoder * SequenceExecutor -### Testing against [libzstd](https://github.com/facebook/zstd) - -Design is verified by comparing decoding results to the reference library -`libzstd`. ZSTD frames used for testing are generated with -[decodecorpus](https://github.com/facebook/zstd/blob/dev/tests/decodecorpus.c) -utility. The generated frame is then decoded with `libzstd` and with simulated -`ZstdDecoder`. - #### Positive test cases If the results of decoding with `libzstd` are valid, the test runs the same @@ -618,5 +616,4 @@ The alternatives for writing negative tests include: then tweaking the raw bits in this frame to trigger the error response from the decoder -[^1]: `CompressedBlockDecoder` is to be added in follow-up PRs. -[^2]: Checksum verification is currently unsupported. +[^1]: Checksum verification is currently unsupported. From 4296f1d388fe52174a38fe9fdfed785228b1c8bb Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Sat, 19 Jul 2025 00:18:18 +0200 Subject: [PATCH 027/159] Add cocotb check for verifying huffman codes Signed-off-by: Robert Winkler --- xls/modules/zstd/huffman_decoder.x | 2 +- xls/modules/zstd/zstd_dec_cocotb_test.py | 293 ++++++++++++++++++++++- 2 files changed, 292 insertions(+), 3 deletions(-) diff --git a/xls/modules/zstd/huffman_decoder.x b/xls/modules/zstd/huffman_decoder.x index 5e33c26c7d..bddf9eaab3 100644 --- a/xls/modules/zstd/huffman_decoder.x +++ b/xls/modules/zstd/huffman_decoder.x @@ -223,7 +223,7 @@ pub proc HuffmanDecoder { trace_fmt!("[HuffmanDecoder] Received codes:"); for (i, ()) in u32:0..SYMBOLS_N { if symbol_valid[i] { - trace_fmt!("[HuffmanDecoder] {:#b} (len {}) -> {:#x}", symbol_code[i], symbol_code_len[i], i); + trace_fmt!("[HuffmanDecoder] {:#x} (len {}) -> {:#x}", symbol_code[i], symbol_code_len[i], i); } else {}; }(()); FSM::READ_DATA diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index e212265fc9..f685ce4fc4 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -13,6 +13,7 @@ # limitations under the License. import sys +import math import enum import pathlib import tempfile @@ -93,6 +94,40 @@ class FseTableRecord(xlsstruct.XLSStruct): symbol: SYMBOL_W +PARALLEL_ACCESS_WIDTH = 8 +MAX_WEIGHT = 11 +WEIGHT_LOG = math.ceil(math.log2(MAX_WEIGHT + 1)) +VALID_W = 1 + + +@xlsstruct.xls_dataclass +class CodeBuilderOutput(xlsstruct.XLSStruct): + symbol_valid_7: VALID_W + symbol_valid_6: VALID_W + symbol_valid_5: VALID_W + symbol_valid_4: VALID_W + symbol_valid_3: VALID_W + symbol_valid_2: VALID_W + symbol_valid_1: VALID_W + symbol_valid_0: VALID_W + code_length_7: WEIGHT_LOG + code_length_6: WEIGHT_LOG + code_length_5: WEIGHT_LOG + code_length_4: WEIGHT_LOG + code_length_3: WEIGHT_LOG + code_length_2: WEIGHT_LOG + code_length_1: WEIGHT_LOG + code_length_0: WEIGHT_LOG + code_7: MAX_WEIGHT + code_6: MAX_WEIGHT + code_5: MAX_WEIGHT + code_4: MAX_WEIGHT + code_3: MAX_WEIGHT + code_2: MAX_WEIGHT + code_1: MAX_WEIGHT + code_0: MAX_WEIGHT + + class CSR(enum.Enum): """ Maps the offsets to the ZSTD Decoder registers. @@ -129,6 +164,10 @@ def print_ram_contents(mem, name="", size=None): print(f"{name} [{i}]\t: {hex(mem[i].value)}") +def fields_as_array(data, prefix, count): + return [getattr(data, f"{prefix}_{i}") for i in range(count)] + + def set_termination_event(monitor, event, transactions): def terminate_cb(_): if monitor.stats.received_transactions == transactions: @@ -375,6 +414,76 @@ def func(): cocotb.start_soon(get_handshake_event(dut, fse_lookup_resp_handshake, func)) +def reverse_expected_huffman_codes(exp_codes): + def reverse_bits(value, max_bits): + bv = BinaryValue(value=value, n_bits=max_bits, bigEndian=False) + return int(BinaryValue(value=bv.binstr[::-1], n_bits=max_bits, bigEndian=False)) + + max_bits = max(d["length"] for d in exp_codes) + + codes = [] + for record in exp_codes: + codes += [ + { + "code": reverse_bits(record["code"], max_bits), + "length": record["length"], + "symbol": record["symbol"], + } + ] + return codes + + +async def test_huffman_codes(dut, clock, expected_codes): + WEIGHT_CODE_BUILDER_INST = ( + dut.ZstdDecoder.xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next_inst19 + ) + CODES_CHANNEL_NAME = "zstd_dec__code_builder_codes" + + codes_channel = xlschannel.XLSChannel( + WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME, dut.clk + ) + huffman_code_handshake = triggers.Event() + + codes = [] + block_cnt = 0 + packet_cnt = 0 + symbol_cnt = 0 + + def func(): + nonlocal codes + nonlocal symbol_cnt + nonlocal packet_cnt + nonlocal block_cnt + + assert block_cnt <= 32 + codes_data = getattr(WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME) + data = CodeBuilderOutput.from_int(codes_data.value) + + symbol_valid_array = fields_as_array(data, "symbol_valid", 8) + code_length_array = fields_as_array(data, "code_length", 8) + code_array = fields_as_array(data, "code", 8) + + for symbol_valid, code_length, code in zip( + symbol_valid_array, code_length_array, code_array + ): + if symbol_valid == 1: + codes += [{"symbol": symbol_cnt, "code": code, "length": code_length}] + symbol_cnt += 1 + packet_cnt += 1 + + if packet_cnt == 32: + assert codes == reverse_expected_huffman_codes(expected_codes[block_cnt]) + packet_cnt = 0 + symbol_cnt = 0 + block_cnt += 1 + codes = [] + + cocotb.start_soon( + set_handshake_event(dut.clk, codes_channel, huffman_code_handshake) + ) + cocotb.start_soon(get_handshake_event(dut, huffman_code_handshake, func)) + + async def test_huffman_weights(dut, clock, expected_huffman_weights): lookup_dec_resp_channel = xlschannel.XLSChannel( dut.ZstdDecoder.xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next_inst20, @@ -498,6 +607,7 @@ async def testing_routine( expected_fse_lookups=None, expected_fse_huffman_lookups=None, expected_huffman_weights=None, + expected_huffman_codes=None, ): (axi_buses, cpu, clock) = prepare_test_environment(dut) frame_id = 0 @@ -508,6 +618,8 @@ async def testing_routine( await test_fse_lookup_decoder_for_huffman( dut, clock, expected_fse_huffman_lookups ) + if expected_huffman_codes is not None: + await test_huffman_codes(dut, clock, expected_huffman_codes) if expected_huffman_weights is not None: await test_huffman_weights(dut, clock, expected_huffman_weights) await test_decoder( @@ -661,6 +773,31 @@ async def pregenerated_compressed_random_1(dut): ], ] + expected_huffman_codes = [ + [ + {"code": 0x00, "length": 8, "symbol": 0x31}, + {"code": 0x01, "length": 8, "symbol": 0x35}, + {"code": 0x20, "length": 3, "symbol": 0x39}, + {"code": 0x02, "length": 8, "symbol": 0x6E}, + {"code": 0x03, "length": 8, "symbol": 0x72}, + {"code": 0x08, "length": 6, "symbol": 0x76}, + {"code": 0x40, "length": 2, "symbol": 0x7A}, + {"code": 0x04, "length": 8, "symbol": 0xAF}, + {"code": 0x05, "length": 8, "symbol": 0xB3}, + {"code": 0x0C, "length": 6, "symbol": 0xB7}, + {"code": 0x80, "length": 1, "symbol": 0xBB}, + {"code": 0x06, "length": 8, "symbol": 0xF0}, + {"code": 0x07, "length": 8, "symbol": 0xF4}, + {"code": 0x10, "length": 4, "symbol": 0xF8}, + ], + [ + {"code": 0x02, "length": 2, "symbol": 0x01}, + {"code": 0x00, "length": 3, "symbol": 0x66}, + {"code": 0x04, "length": 1, "symbol": 0x9C}, + {"code": 0x01, "length": 3, "symbol": 0xCB}, + ], + ] + expected_fse_huffman_lookups = [ [ FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0016), @@ -804,6 +941,7 @@ async def pregenerated_compressed_random_1(dut): block_type, literal_type, input_name, + expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, expected_fse_huffman_lookups=expected_fse_huffman_lookups, ) @@ -961,7 +1099,51 @@ async def fse_huffman_literals_predefined_sequences_seed_319146(dut): test_cases = 1 block_type = data_generator.BlockType.COMPRESSED literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + + expected_huffman_codes = [ + [ + {"code": 0x300, "length": 2, "symbol": 0x07}, + {"code": 0x60, "length": 5, "symbol": 0x0E}, + {"code": 0x0A, "length": 9, "symbol": 0x15}, + {"code": 0x00, "length": 10, "symbol": 0x1C}, + {"code": 0x180, "length": 3, "symbol": 0x25}, + {"code": 0x30, "length": 6, "symbol": 0x2C}, + {"code": 0x0C, "length": 9, "symbol": 0x33}, + {"code": 0x01, "length": 10, "symbol": 0x3A}, + {"code": 0xC0, "length": 4, "symbol": 0x43}, + {"code": 0x18, "length": 7, "symbol": 0x4A}, + {"code": 0x02, "length": 10, "symbol": 0x51}, + {"code": 0x100, "length": 4, "symbol": 0x61}, + {"code": 0x20, "length": 7, "symbol": 0x68}, + {"code": 0x03, "length": 10, "symbol": 0x6F}, + {"code": 0x80, "length": 5, "symbol": 0x7F}, + {"code": 0x10, "length": 8, "symbol": 0x86}, + {"code": 0x04, "length": 10, "symbol": 0x8D}, + {"code": 0x200, "length": 3, "symbol": 0x96}, + {"code": 0x40, "length": 6, "symbol": 0x9D}, + {"code": 0x0E, "length": 9, "symbol": 0xA4}, + {"code": 0x05, "length": 10, "symbol": 0xAB}, + {"code": 0x280, "length": 3, "symbol": 0xB4}, + {"code": 0x50, "length": 6, "symbol": 0xBB}, + {"code": 0x06, "length": 10, "symbol": 0xC2}, + {"code": 0x07, "length": 10, "symbol": 0xC9}, + {"code": 0x140, "length": 4, "symbol": 0xD2}, + {"code": 0x28, "length": 7, "symbol": 0xD9}, + {"code": 0x08, "length": 10, "symbol": 0xE0}, + {"code": 0xA0, "length": 5, "symbol": 0xF0}, + {"code": 0x14, "length": 8, "symbol": 0xF7}, + {"code": 0x09, "length": 10, "symbol": 0xFE}, + ] + ] + + await testing_routine( + dut, + test_cases, + block_type, + literal_type, + input_name, + expected_huffman_codes=expected_huffman_codes, + ) @cocotb.test(timeout_time=1000, timeout_unit="ms") @@ -973,7 +1155,38 @@ async def fse_huffman_literals_predefined_sequences_seed_331938(dut): test_cases = 1 block_type = data_generator.BlockType.COMPRESSED literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + + expected_huffman_codes = [ + [ + {"code": 0x00, "length": 9, "symbol": 0x13}, + {"code": 0x20, "length": 5, "symbol": 0x1B}, + {"code": 0x01, "length": 9, "symbol": 0x32}, + {"code": 0x10, "length": 6, "symbol": 0x3A}, + {"code": 0x100, "length": 2, "symbol": 0x42}, + {"code": 0x02, "length": 9, "symbol": 0x51}, + {"code": 0x18, "length": 6, "symbol": 0x59}, + {"code": 0x180, "length": 2, "symbol": 0x61}, + {"code": 0x08, "length": 7, "symbol": 0x78}, + {"code": 0x80, "length": 3, "symbol": 0x80}, + {"code": 0x0C, "length": 7, "symbol": 0x97}, + {"code": 0xC0, "length": 3, "symbol": 0x9F}, + {"code": 0x04, "length": 8, "symbol": 0xB6}, + {"code": 0x40, "length": 4, "symbol": 0xBE}, + {"code": 0x06, "length": 8, "symbol": 0xD5}, + {"code": 0x60, "length": 4, "symbol": 0xDD}, + {"code": 0x03, "length": 9, "symbol": 0xF4}, + {"code": 0x30, "length": 5, "symbol": 0xFC}, + ] + ] + + await testing_routine( + dut, + test_cases, + block_type, + literal_type, + input_name, + expected_huffman_codes=expected_huffman_codes, + ) @cocotb.test(timeout_time=350, timeout_unit="ms") @@ -1473,6 +1686,16 @@ async def treeless_huffman_literals_compressed_sequences_seed_400077(dut): ] ] + expected_huffman_codes = [ + [ + {"code": 0x00, "length": 4, "symbol": 0x00}, + {"code": 0x02, "length": 3, "symbol": 0x3D}, + {"code": 0x04, "length": 2, "symbol": 0x7A}, + {"code": 0x08, "length": 1, "symbol": 0xB7}, + {"code": 0x01, "length": 4, "symbol": 0xC3}, + ] + ] + expected_fse_huffman_lookups = [ [ FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0018), @@ -1516,6 +1739,7 @@ async def treeless_huffman_literals_compressed_sequences_seed_400077(dut): block_type, literal_type, input_name, + expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, expected_fse_huffman_lookups=expected_fse_huffman_lookups, ) @@ -1540,12 +1764,34 @@ async def treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400 ] ] + expected_huffman_codes = [ + [ + {"code": 0x00, "length": 4, "symbol": 0x00}, + {"code": 0x01, "length": 4, "symbol": 0x01}, + {"code": 0x02, "length": 4, "symbol": 0x02}, + {"code": 0x03, "length": 4, "symbol": 0x03}, + {"code": 0x04, "length": 4, "symbol": 0x04}, + {"code": 0x05, "length": 4, "symbol": 0x05}, + {"code": 0x06, "length": 4, "symbol": 0x06}, + {"code": 0x07, "length": 4, "symbol": 0x07}, + {"code": 0x08, "length": 4, "symbol": 0x08}, + {"code": 0x09, "length": 4, "symbol": 0x09}, + {"code": 0x0A, "length": 4, "symbol": 0x0A}, + {"code": 0x0B, "length": 4, "symbol": 0x0B}, + {"code": 0x0C, "length": 4, "symbol": 0x0C}, + {"code": 0x0D, "length": 4, "symbol": 0x0D}, + {"code": 0x0E, "length": 4, "symbol": 0x0E}, + {"code": 0x0F, "length": 4, "symbol": 0x0F}, + ] + ] + await testing_routine( dut, test_cases, block_type, literal_type, input_name, + expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, ) @@ -1569,12 +1815,34 @@ async def treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400 ] ] + expected_huffman_codes = [ + [ + {"code": 0x00, "length": 4, "symbol": 0x00}, + {"code": 0x01, "length": 4, "symbol": 0x01}, + {"code": 0x02, "length": 4, "symbol": 0x02}, + {"code": 0x03, "length": 4, "symbol": 0x03}, + {"code": 0x04, "length": 4, "symbol": 0x04}, + {"code": 0x05, "length": 4, "symbol": 0x05}, + {"code": 0x06, "length": 4, "symbol": 0x06}, + {"code": 0x07, "length": 4, "symbol": 0x07}, + {"code": 0x08, "length": 4, "symbol": 0x08}, + {"code": 0x09, "length": 4, "symbol": 0x09}, + {"code": 0x0A, "length": 4, "symbol": 0x0A}, + {"code": 0x0B, "length": 4, "symbol": 0x0B}, + {"code": 0x0C, "length": 4, "symbol": 0x0C}, + {"code": 0x0D, "length": 4, "symbol": 0x0D}, + {"code": 0x0E, "length": 4, "symbol": 0x0E}, + {"code": 0x0F, "length": 4, "symbol": 0x0F}, + ] + ] + await testing_routine( dut, test_cases, block_type, literal_type, input_name, + expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, ) @@ -1623,6 +1891,26 @@ async def treeless_huffman_literals_rle_sequences_seed_403927(dut): ] ] + expected_huffman_codes = [ + [ + {"code": 0x0C, "length": 6, "symbol": 0x04}, + {"code": 0x00, "length": 8, "symbol": 0x07}, + {"code": 0x08, "length": 7, "symbol": 0x29}, + {"code": 0x01, "length": 8, "symbol": 0x2C}, + {"code": 0x0A, "length": 7, "symbol": 0x4E}, + {"code": 0x02, "length": 8, "symbol": 0x51}, + {"code": 0x80, "length": 1, "symbol": 0x70}, + {"code": 0x03, "length": 8, "symbol": 0x73}, + {"code": 0x04, "length": 8, "symbol": 0x76}, + {"code": 0x40, "length": 2, "symbol": 0x95}, + {"code": 0x05, "length": 8, "symbol": 0x98}, + {"code": 0x20, "length": 3, "symbol": 0xBA}, + {"code": 0x06, "length": 8, "symbol": 0xBD}, + {"code": 0x10, "length": 4, "symbol": 0xDF}, + {"code": 0x07, "length": 8, "symbol": 0xE2}, + ] + ] + expected_fse_huffman_lookups = [ [ FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0012), @@ -1666,6 +1954,7 @@ async def treeless_huffman_literals_rle_sequences_seed_403927(dut): block_type, literal_type, input_name, + expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, expected_fse_huffman_lookups=expected_fse_huffman_lookups, ) From 904a651a2597f048d180e874daa59feb06276cd8 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 10 Sep 2025 17:08:53 +0200 Subject: [PATCH 028/159] modules: zstd: cocotb: drop unused testcase Co-autored-by: Dominik Lau Signed-off-by: Robert Winkler --- xls/modules/zstd/zstd_dec_cocotb_test.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index f685ce4fc4..c9c96d9544 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -1970,13 +1970,6 @@ async def zstd_compressed_frames_test(dut): literal_type = data_generator.LiteralType.RAW await testing_routine(dut, test_cases, block_type, literal_type) - -# @cocotb.test(timeout_time=1000, timeout_unit="ms") -# async def zstd_random_frames_test(dut): -# test_cases = 1 -# block_type = BlockType.RANDOM -# await testing_routine(dut, test_cases, block_type) - if __name__ == "__main__": sys.path.append(str(pathlib.Path(__file__).parent)) with tempfile.NamedTemporaryFile(mode="w") as modified_zstd_verilog: From 7073d51de713fc2ea129fd852ff0400642443133 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 10 Sep 2025 17:09:37 +0200 Subject: [PATCH 029/159] modules: zstd: disable huffman code builder PnR Co-autored-by: Dominik Lau Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 17313eadee..4222f0f5c7 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -3288,7 +3288,7 @@ benchmark_synth( ) place_and_route( - name = "huffman_code_builder_place_and_route", + name = "huffman_code_builder_place_and_route_skip", clock_period = CLOCK_PERIOD_PS, core_padding_microns = 2, min_pin_distance = "0.5", From 047ce03fff45a7130a4f5f66e3bad616ceb861db Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 10 Sep 2025 17:10:09 +0200 Subject: [PATCH 030/159] modules: zstd: fix formatting Co-autored-by: Dominik Lau Signed-off-by: Robert Winkler --- xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.log | 1 - xls/modules/zstd/zstd_dec_ll_fse_default.mem | 2 +- xls/modules/zstd/zstd_dec_ml_fse_default.mem | 2 +- xls/modules/zstd/zstd_dec_of_fse_default.mem | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.log b/xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.log index ea44ca6642..bf6b092bbe 100644 --- a/xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.log +++ b/xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.log @@ -40,4 +40,3 @@ frame seed: 2 number of sequences: 1 block type: compressed block size field: 10 - diff --git a/xls/modules/zstd/zstd_dec_ll_fse_default.mem b/xls/modules/zstd/zstd_dec_ll_fse_default.mem index f61d7ce353..fd3bfafa24 100644 --- a/xls/modules/zstd/zstd_dec_ll_fse_default.mem +++ b/xls/modules/zstd/zstd_dec_ll_fse_default.mem @@ -253,4 +253,4 @@ 00000000 00000000 00000000 -00000000 \ No newline at end of file +00000000 diff --git a/xls/modules/zstd/zstd_dec_ml_fse_default.mem b/xls/modules/zstd/zstd_dec_ml_fse_default.mem index 704d69a731..04b32295c8 100644 --- a/xls/modules/zstd/zstd_dec_ml_fse_default.mem +++ b/xls/modules/zstd/zstd_dec_ml_fse_default.mem @@ -253,4 +253,4 @@ 00000000 00000000 00000000 -00000000 \ No newline at end of file +00000000 diff --git a/xls/modules/zstd/zstd_dec_of_fse_default.mem b/xls/modules/zstd/zstd_dec_of_fse_default.mem index 2872c3e282..9d81689c66 100644 --- a/xls/modules/zstd/zstd_dec_of_fse_default.mem +++ b/xls/modules/zstd/zstd_dec_of_fse_default.mem @@ -253,4 +253,4 @@ 00000000 00000000 00000000 -00000000 \ No newline at end of file +00000000 From 73f4ea8219dcf7c92a40d7febe917a172757d7b4 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 19 Sep 2025 15:45:06 +0200 Subject: [PATCH 031/159] Update python lock file Signed-off-by: Robert Winkler --- MODULE.bazel.lock | 54 +++++++++++++++++++++++++- dependency_support/pip_requirements.in | 3 +- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index c9d27b0531..38891b1c95 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -2110,7 +2110,7 @@ "@@or-tools~//bazel/ortools_requirements.txt": "37e22395e78ef3572ab57b7717fd8f54851919bb73ca404f367536dc15a8e3eb", "@@pybind11_abseil~//pybind11_abseil/requirements/requirements_lock_3_11.txt": "7d1074311e9f32f25ca112fc86fbec98bc024d820a8dc00f94e8679a7e6b480c", "@@rules_python~//tools/publish/requirements_linux.txt": "8175b4c8df50ae2f22d1706961884beeb54e7da27bd2447018314a175981997d", - "@@//dependency_support/pip_requirements_lock.txt": "e813fef63629aaca3c070b25f1265afb21da8400c4dc846646f3056965670718", + "@@//dependency_support/pip_requirements_lock.txt": "5108b11922afcb1c3b8abc232356cbff368ad481d6bb54472356427cba87f4fc", "@@or-tools~//bazel/notebook_requirements.txt": "ca78fad693f1b35eed8bb7c54e5ddf7ad255c4b9d94ce18efa55859759a8fb70", "@@protoc-gen-validate~//python/requirements.txt": "6a540bc029bbf3a5f78f9f9282bd30e2965c98eeb150f2cfc5facccbd8a98297", "@@pybind11_protobuf~//pybind11_protobuf/requirements/requirements_lock_3_10.txt": "afd6f9406f4e80a504f1575121a937f4c15388aec4a17393f0cb1ac9f09d18dd", @@ -14405,6 +14405,39 @@ "timeout": 600000 } }, + "xls_pip_deps_311_cocotb": { + "bzlFile": "@@rules_python~//python/private/pypi:whl_library.bzl", + "ruleClassName": "whl_library", + "attributes": { + "dep_template": "@xls_pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "repo": "xls_pip_deps_311", + "requirement": "cocotb==1.7.2 --hash=sha256:0c1687ac78141724b8529e029ee6299698ecaa8a2c431b744eeff487a4bb18de --hash=sha256:163e5262020cc21f6a0391fb4727c9ab3ecbf6ee12a1472c8f7320b3ba211a50 --hash=sha256:170cf4d01c4d7c6c5b141ffc1824e846a6c8adbed553a50984cd522c1dddb111 --hash=sha256:1851ac56eed7bb6c745aabfc0e417195cb4f08b5df50846c04eb77a868bfeaba --hash=sha256:1abffb36183b07469c490836c66d8b9e24fc1bec7c27356818618a6719fabd4b --hash=sha256:33be79f048f4072240668a079d2bcebd1a24611a0a1e55439b65ffa0ff077790 --hash=sha256:34ab1bf3f18476724dd4e21dbcc0e060e813eb502abe155b800084fb6945360c --hash=sha256:360019f74270661d14e9caa8103e740a070cb466ab08376a565ec0ef4c13dbbf --hash=sha256:37ddb79f4ab60d2d2dc5a9db5bf767d226eb4978fd15b84dfb968d31ab2fcda5 --hash=sha256:43f5af578803e5726b5c75421c0e35e54021ab423d3aa4efe930feb740d6479d --hash=sha256:4738f36b9730cc05b74ccba3648dba0455cf9f237abf822ef307a274a29474c2 --hash=sha256:4aa5d73ebdb59ef24cef36a1f8cca11dcecb3ee7b71a84df02751020bc67ea77 --hash=sha256:574d21501ff1a3d36889397cd58a18d102d0e40391aa7a0274b600d1cc4c7dc3 --hash=sha256:69f4e539dd308c9e169ab23135138ec397061b700f209803a6022ae9fbe08933 --hash=sha256:6f289ac00f4884046ec64db7006e47b1c857a36dcd2a80ea0873cbff00248368 --hash=sha256:707f795a17679b4653a50bd4094536a46fbfee5c6e3d951fac4320ee211ad13f --hash=sha256:7828e22946f128aa59cb9254de4037b99e3bd5a51fe8f590cf64a3141d742a37 --hash=sha256:82f694da656a699154b15ee28be3ac39c41a71d33985313deda12a3645f8b3db --hash=sha256:959892eb94bd0b3ff40e0fca51d33a3936416deb853e2bac4f7f766b40002650 --hash=sha256:a7ec6a2d212c27ec46bed17a15d60b7b29cd0f734f11cc16d2cb4d3f6136e133 --hash=sha256:a90c77f4bbfdf73aa16093dfe95c68af1a1ca685ebfa525f3f150eab252f6728 --hash=sha256:b288a59fa8dffc1cbc53105e71e2f8c82421081b17282e41319832654b309477 --hash=sha256:c41cc8d4ece57f5e26076cd12f1e11d464d7f118fdb74b958269535185d99a30 --hash=sha256:c8dce91d2a918ee63338d79b08e3d52f1d2797efd9c2bedd13c33d674f730db8 --hash=sha256:d26a8a40cea61f295be04b1164a5dd9ec873f13a39814ad00efec7fd899320e0 --hash=sha256:d80b3baafff1a8a91ac860023c448c603767bed502258160a5cb6029976fec4f --hash=sha256:dcf5354268f16d9e11e05cf3616172ca5ef503b45567f75ebd96a0bfdb9832d1 --hash=sha256:ded849360fb31746f1ba3a994f89c3bba2466ec2d0b4b5da0030645645f938d4 --hash=sha256:e03df73573aec261447602904bd66927eeb2f00dd24370dc9a57f47fd42c4d70 --hash=sha256:f97c2eb92cb68831f19b82ba0038ce40fa73c5edbffb7930745edac20c5358d1 --hash=sha256:fa8abed5260baf4306fbfb997c8789fe24bc229cd762b12d7dba0b9c20147b1d", + "timeout": 600000 + } + }, + "xls_pip_deps_311_cocotb_bus": { + "bzlFile": "@@rules_python~//python/private/pypi:whl_library.bzl", + "ruleClassName": "whl_library", + "attributes": { + "dep_template": "@xls_pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "repo": "xls_pip_deps_311", + "requirement": "cocotb-bus==0.2.1 --hash=sha256:a197aa4b0e0ad28469c8877b41b3fb2ec0206da9f491b9276d1578ce6dd8aa8d", + "timeout": 600000 + } + }, + "xls_pip_deps_311_cocotbext_axi": { + "bzlFile": "@@rules_python~//python/private/pypi:whl_library.bzl", + "ruleClassName": "whl_library", + "attributes": { + "dep_template": "@xls_pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "repo": "xls_pip_deps_311", + "requirement": "cocotbext-axi==0.1.24 --hash=sha256:3ed62dcaf9448833176826507c5bc5c346431c4846a731e409d87c862d960593 --hash=sha256:533ba6c7503c6302bdb9ef86e43a549ad5da876eafb1adce23d39751c54cced4", + "timeout": 600000 + } + }, "xls_pip_deps_312_contourpy": { "bzlFile": "@@rules_python~//python/private/pypi:whl_library.bzl", "ruleClassName": "whl_library", @@ -14427,6 +14460,17 @@ "timeout": 600000 } }, + "xls_pip_deps_311_find_libpython": { + "bzlFile": "@@rules_python~//python/private/pypi:whl_library.bzl", + "ruleClassName": "whl_library", + "attributes": { + "dep_template": "@xls_pip_deps//{name}:{target}", + "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", + "repo": "xls_pip_deps_311", + "requirement": "find-libpython==0.4.0 --hash=sha256:034a4253bd57da3408aefc59aeac1650150f6c1f42e10fdd31615cf1df0842e3 --hash=sha256:46f9cdcd397ddb563b2d7592ded3796a41c1df5222443bd9d981721c906c03e6", + "timeout": 600000 + } + }, "xls_pip_deps_312_flask": { "bzlFile": "@@rules_python~//python/private/pypi:whl_library.bzl", "ruleClassName": "whl_library", @@ -15192,8 +15236,12 @@ "whl_map": { "blinker": "{\"xls_pip_deps_312_blinker\":[{\"version\":\"3.12\"}]}", "click": "{\"xls_pip_deps_312_click\":[{\"version\":\"3.12\"}]}", + "cocotb": "{\"xls_pip_deps_311_cocotb\":[{\"version\":\"3.11\"}]}", + "cocotb_bus": "{\"xls_pip_deps_311_cocotb_bus\":[{\"version\":\"3.11\"}]}", + "cocotbext_axi": "{\"xls_pip_deps_311_cocotbext_axi\":[{\"version\":\"3.11\"}]}", "contourpy": "{\"xls_pip_deps_312_contourpy\":[{\"version\":\"3.12\"}]}", "cycler": "{\"xls_pip_deps_312_cycler\":[{\"version\":\"3.12\"}]}", + "find_libpython": "{\"xls_pip_deps_311_find_libpython\":[{\"version\":\"3.11\"}]}", "flask": "{\"xls_pip_deps_312_flask\":[{\"version\":\"3.12\"}]}", "fonttools": "{\"xls_pip_deps_312_fonttools\":[{\"version\":\"3.12\"}]}", "itsdangerous": "{\"xls_pip_deps_312_itsdangerous\":[{\"version\":\"3.12\"}]}", @@ -15219,8 +15267,12 @@ "packages": [ "blinker", "click", + "cocotb", + "cocotb_bus", + "cocotbext_axi", "contourpy", "cycler", + "find_libpython", "flask", "fonttools", "itsdangerous", diff --git a/dependency_support/pip_requirements.in b/dependency_support/pip_requirements.in index e7ae68826e..6046107abe 100644 --- a/dependency_support/pip_requirements.in +++ b/dependency_support/pip_requirements.in @@ -15,7 +15,8 @@ pyyaml==6.0.1 # We build most of z3 ourselves but building python is really complicated. Just # use pypi version z3-solver==4.14.0.0 -cocotb==1.7.2 +pytest==8.2.2 +cocotb==1.9.0 cocotbext-axi==0.1.24 cocotb_bus==0.2.1 From 30b753e48729c89f0ac756350ee6be9f28bfbd67 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 2 Sep 2025 14:18:00 +0200 Subject: [PATCH 032/159] modules: zstd: cocotb: update signal names Signed-off-by: Robert Winkler --- xls/modules/zstd/zstd_dec_cocotb_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index c9c96d9544..16923db637 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -347,7 +347,7 @@ def prepare_test_environment(dut): async def test_fse_lookup_decoder(dut, clock, expected_fse_lookups): lookup_dec_resp_channel = xlschannel.XLSChannel( - dut.ZstdDecoder.xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next_inst148, + dut.ZstdDecoder.xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next_inst149, "zstd_dec__flc_resp", dut.clk, ) @@ -435,7 +435,7 @@ def reverse_bits(value, max_bits): async def test_huffman_codes(dut, clock, expected_codes): WEIGHT_CODE_BUILDER_INST = ( - dut.ZstdDecoder.xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next_inst19 + dut.ZstdDecoder.xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next_inst20 ) CODES_CHANNEL_NAME = "zstd_dec__code_builder_codes" @@ -486,7 +486,7 @@ def func(): async def test_huffman_weights(dut, clock, expected_huffman_weights): lookup_dec_resp_channel = xlschannel.XLSChannel( - dut.ZstdDecoder.xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next_inst20, + dut.ZstdDecoder.xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next_inst21, "zstd_dec__weights_dec_resp", dut.clk, ) From 02c76ddccfb0aa25133d0daab6b377c50defda2d Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Wed, 30 Jul 2025 12:38:36 +0200 Subject: [PATCH 033/159] modules: zstd: cocotb: refactor tests environment --- xls/modules/zstd/BUILD | 103 +++- xls/modules/zstd/cocotb/utils.py | 68 +- xls/modules/zstd/zstd_dec_cocotb_common.py | 682 +++++++++++++++++++++ 3 files changed, 798 insertions(+), 55 deletions(-) create mode 100644 xls/modules/zstd/zstd_dec_cocotb_common.py diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 4222f0f5c7..539b3e9e32 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1644,7 +1644,16 @@ xls_dslx_verilog( dslx_top = "ZstdDecoderInst", library = ":zstd_dec_dslx", tags = ["manual"], - verilog_file = "zstd_dec.v", + verilog_file = "zstd_dec_prepatch.v", +) + +# Work around issues with acessing stuff starting with double underscore from cocotb +# (python considers such names private) +genrule( + name = "patch_zstd_dec", + srcs = ["zstd_dec_prepatch.v"], + outs = ["zstd_dec.v"], + cmd = 'sed "s/__xls_modules_zstd/xls_modules_zstd/g" "$(SRCS)" > "$(OUTS)"' ) ZSTD_DEC_INTERNAL_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { @@ -1708,7 +1717,7 @@ place_and_route( verilog_library( name = "zstd_dec_verilog_lib", srcs = [ - ":zstd_dec.v", + ":patch_zstd_dec", ], tags = ["manual"], ) @@ -1760,6 +1769,46 @@ genrule( ], ) + +py_library( + name = "zstd_dec_cocotb_common", + srcs = ["zstd_dec_cocotb_common.py"], + data = [ + ":axi_crossbar_wrapper.v", + ":patch_zstd_dec", + ":zstd_dec_xx_fse_default", + "//xls/modules/zstd/rtl:ram_1r1w.v", + "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", + "//xls/modules/zstd/rtl:zstd_dec_wrapper.sv", + "@com_github_alexforencich_verilog_axi//:rtl/arbiter.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_addr.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_rd.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_wr.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_register_rd.v", + "@com_github_alexforencich_verilog_axi//:rtl/axi_register_wr.v", + "@com_github_alexforencich_verilog_axi//:rtl/priority_encoder.v", + "@com_icarus_iverilog//:iverilog", + "@com_icarus_iverilog//:vvp", + ], + imports = ["."], + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("cocotb"), + requirement("cocotbext-axi"), + "//xls/common:runfiles", + "//xls/modules/zstd/cocotb:channel", + "//xls/modules/zstd/cocotb:data_generator", + "//xls/modules/zstd/cocotb:memory", + "//xls/modules/zstd/cocotb:utils", + "//xls/modules/zstd/cocotb:xlsstruct", + "@abseil-py//absl:app", + "@abseil-py//absl/flags", + "@com_google_protobuf//:protobuf_python", + ], +) + py_test( name = "zstd_dec_cocotb_test", srcs = ["zstd_dec_cocotb_test.py"], @@ -1803,38 +1852,36 @@ py_test( "data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst", "data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst", "data/treeless_huffman_literals_rle_sequences_seed_403927.zst", - ":axi_crossbar_wrapper.v", - ":zstd_dec.v", - ":zstd_dec_xx_fse_default", - "//xls/modules/zstd/rtl:ram_1r1w.v", - "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", - "//xls/modules/zstd/rtl:zstd_dec_wrapper.sv", - "@com_github_alexforencich_verilog_axi//:rtl/arbiter.v", - "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar.v", - "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_addr.v", - "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_rd.v", - "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_wr.v", - "@com_github_alexforencich_verilog_axi//:rtl/axi_register_rd.v", - "@com_github_alexforencich_verilog_axi//:rtl/axi_register_wr.v", - "@com_github_alexforencich_verilog_axi//:rtl/priority_encoder.v", - "@com_icarus_iverilog//:iverilog", - "@com_icarus_iverilog//:vvp", ], - tags = ["manual"], + tags = [ + "manual", + "exclusive", # Test with heavy use of multithreading. Running it in parallel results in significant performance drop + ], visibility = ["//xls:xls_users"], deps = [ requirement("cocotb"), - requirement("cocotbext-axi"), - "//xls/common:runfiles", - "//xls/modules/zstd/cocotb:channel", + "//xls/modules/zstd:zstd_dec_cocotb_common", "//xls/modules/zstd/cocotb:data_generator", - "//xls/modules/zstd/cocotb:memory", - "//xls/modules/zstd/cocotb:utils", - "//xls/modules/zstd/cocotb:xlsstruct", - "@abseil-py//absl:app", - "@abseil-py//absl/flags", - "@com_google_protobuf//:protobuf_python", ], + toolchains=["@rules_perl//:current_toolchain"], +) + +py_binary( + name = "zstd_dec_cocotb_cli", + srcs = ["zstd_dec_cocotb_cli.py"], + env = { + "PYTHONUNBUFFERED": "1", + "BUILD_WORKING_DIRECTORY": "sim_build", + "PERL": "$(PERL)", + }, + imports = ["."], + tags = ["manual"], + visibility = ["//xls:xls_users"], + deps = [ + requirement("cocotb"), + "//xls/modules/zstd:zstd_dec_cocotb_common", + ], + toolchains=["@rules_perl//:current_toolchain"], ) xls_dslx_library( diff --git a/xls/modules/zstd/cocotb/utils.py b/xls/modules/zstd/cocotb/utils.py index 43f9ca16bc..6b0abf64d0 100644 --- a/xls/modules/zstd/cocotb/utils.py +++ b/xls/modules/zstd/cocotb/utils.py @@ -17,6 +17,7 @@ import os import pathlib +import subprocess import cocotb from cocotb.runner import check_results_file from cocotb.runner import get_runner @@ -24,6 +25,34 @@ from xls.common import runfiles +import tempfile + +def add_to_path(executable): + dir = str(pathlib.Path(executable).parent) + os.environ["PATH"] = dir + os.pathsep + os.environ["PATH"] + +def setup_verilator(): + build_dir = pathlib.Path("sim_build").absolute() + build_dir.mkdir(parents=True, exist_ok=True) + if not os.path.exists(build_dir / "external"): + # verilator will be launched in build dir. We need to link external there + # to make relative paths to toolchains (CC and other) work + os.symlink(os.path.relpath("external", build_dir), build_dir / "external") + + # cocotb does something like `perl $(which verilator) so we have to add both of them to $PATH + verilator_perl_wrapper_path = runfiles.get_path("bin/verilator", repository = "verilator") + add_to_path(verilator_perl_wrapper_path) + add_to_path(os.environ["PERL"]) + + # normally verilator's perl wrapper exepects that binary is located next to it under name "verilator_bin" + # rules_hdl uses different location and name so we have to use $VERILATOR_BIN. + verilator_binary_path = pathlib.Path(runfiles.get_path("verilator_executable", repository = "verilator")) + os.environ["VERILATOR_BIN"] = str(verilator_binary_path) + + os.environ.pop("VERILATOR_ROOT", None) # prevent picking wrong env from host + + return build_dir + def setup_com_iverilog(): iverilog_path = pathlib.Path( @@ -38,41 +67,26 @@ def setup_com_iverilog(): return build_dir -def run_test(toplevel, test_module, verilog_sources, timescale=("1ns", "1ps")): - """Builds and runs a Cocotb testbench using Icarus Verilog.""" - build_dir = setup_com_iverilog() - runner = get_runner("icarus")() - build_args = [] - - cmds_file = build_dir / pathlib.Path("cmds.f") - cmds_file.parent.mkdir(parents=True, exist_ok=True) - with open(cmds_file, "w") as f: - f.write("+timescale+{}/{}\n".format(*timescale)) - build_args += ["-f", str(cmds_file)] - - dump_file = build_dir / pathlib.Path("cocotb_iverilog_dump.v") - wave_file = build_dir / pathlib.Path(f"{toplevel}.fst") - with open(dump_file, "w") as f: - f.write("module cocotb_iverilog_dump();\n") - f.write("initial begin\n") - f.write(f' $dumpfile("{wave_file}");\n') - f.write(f" $dumpvars(0, {toplevel});\n") - f.write("end\n") - f.write("endmodule\n") - wave_file.parent.mkdir(parents=True, exist_ok=True) +def run_test(toplevel, test_module, verilog_sources, timescale=("1ns", "1ps"), sim="icarus", build_args=[]): + """Builds and runs a Cocotb testbench""" + build_dir = setup_verilator() if sim == "verilator" else setup_com_iverilog() + runner = get_runner(sim) runner.build( - verilog_sources=(verilog_sources + [str(dump_file)]), - toplevel=toplevel, + verilog_sources=verilog_sources, + hdl_toplevel=toplevel, + timescale=("1ns", "1ps"), build_dir=build_dir, - extra_args=build_args, defines={"SIMULATION": "1"}, + waves=True, + build_args=build_args, ) try: results_xml = runner.test( - toplevel=toplevel, - py_module=test_module, + hdl_toplevel=toplevel, + test_module=test_module, + waves=True, ) finally: check_results_file(results_xml) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py new file mode 100644 index 0000000000..a820c65542 --- /dev/null +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -0,0 +1,682 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import math +import enum +import tempfile + +import cocotb +from cocotb import triggers +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge, ClockCycles, Event +from cocotb.binary import BinaryValue +from cocotb.utils import get_sim_time +from cocotb_bus.scoreboard import Scoreboard + +from cocotbext.axi import axi_channels +from cocotbext.axi.axi_master import AxiMaster +from cocotbext.axi.axi_channels import ( + AxiAWBus, + AxiWBus, + AxiWMonitor, + AxiBBus, + AxiWriteBus, + AxiARBus, + AxiRBus, + AxiReadBus, + AxiBus, + AxiBTransaction, + AxiBSource, + AxiBSink, + AxiBMonitor, + AxiRTransaction, + AxiRSource, + AxiRSink, + AxiRMonitor, +) +from cocotbext.axi.axi_ram import AxiRam +from cocotbext.axi.sparse_memory import SparseMemory + +import xls.modules.zstd.cocotb.channel as xlschannel +import xls.modules.zstd.cocotb.utils as cocotb_utils +from xls.modules.zstd.cocotb import data_generator +from xls.modules.zstd.cocotb.memory import AxiRamFromFile +from xls.modules.zstd.cocotb import xlsstruct + +AXI_DATA_W = 64 +AXI_DATA_W_BYTES = AXI_DATA_W // 8 +MAX_ENCODED_FRAME_SIZE_B = 2**32 +NOTIFY_CHANNEL = "notify" +RESET_CHANNEL = "reset" + +# Override default widths of AXI response signals +signal_widths = {"bresp": 3} +axi_channels.AxiBBus._signal_widths = signal_widths +axi_channels.AxiBTransaction._signal_widths = signal_widths +axi_channels.AxiBSource._signal_widths = signal_widths +axi_channels.AxiBSink._signal_widths = signal_widths +axi_channels.AxiBMonitor._signal_widths = signal_widths +signal_widths = {"rresp": 3, "rlast": 1} +axi_channels.AxiRBus._signal_widths = signal_widths +axi_channels.AxiRTransaction._signal_widths = signal_widths +axi_channels.AxiRSource._signal_widths = signal_widths +axi_channels.AxiRSink._signal_widths = signal_widths +axi_channels.AxiRMonitor._signal_widths = signal_widths + + +@xlsstruct.xls_dataclass +class NotifyStruct(xlsstruct.XLSStruct): + pass + + +SYMBOL_W = 8 +NUM_OF_BITS_W = 8 +BASE_W = 16 + + +@xlsstruct.xls_dataclass +class FseTableRecord(xlsstruct.XLSStruct): + base: BASE_W + num_of_bits: NUM_OF_BITS_W + symbol: SYMBOL_W + + +PARALLEL_ACCESS_WIDTH = 8 +MAX_WEIGHT = 11 +WEIGHT_LOG = math.ceil(math.log2(MAX_WEIGHT + 1)) +VALID_W = 1 + + +@xlsstruct.xls_dataclass +class CodeBuilderOutput(xlsstruct.XLSStruct): + symbol_valid_7: VALID_W + symbol_valid_6: VALID_W + symbol_valid_5: VALID_W + symbol_valid_4: VALID_W + symbol_valid_3: VALID_W + symbol_valid_2: VALID_W + symbol_valid_1: VALID_W + symbol_valid_0: VALID_W + code_length_7: WEIGHT_LOG + code_length_6: WEIGHT_LOG + code_length_5: WEIGHT_LOG + code_length_4: WEIGHT_LOG + code_length_3: WEIGHT_LOG + code_length_2: WEIGHT_LOG + code_length_1: WEIGHT_LOG + code_length_0: WEIGHT_LOG + code_7: MAX_WEIGHT + code_6: MAX_WEIGHT + code_5: MAX_WEIGHT + code_4: MAX_WEIGHT + code_3: MAX_WEIGHT + code_2: MAX_WEIGHT + code_1: MAX_WEIGHT + code_0: MAX_WEIGHT + + +class CSR(enum.Enum): + """ + Maps the offsets to the ZSTD Decoder registers. + """ + + STATUS = 0 + START = 1 + INPUTBUFFER = 2 + OUTPUTBUFFER = 3 + + +class Status(enum.Enum): + """ + Codes for the Status register. + """ + + IDLE = 0x0 + RUNNING = 0x1 + + +def check_ram_contents(mem, expected, name=""): + for i, value in enumerate(expected): + assert mem[i].value == value + + +def print_fse_ram_contents(mem, name="", size=None): + for i in range(size): + data = FseTableRecord.from_int(mem[i].value) + print(f"{name} [{i}]: {data}") + + +def print_ram_contents(mem, name="", size=None): + for i in range(size): + print(f"{name} [{i}]\t: {hex(mem[i].value)}") + + +def fields_as_array(data, prefix, count): + return [getattr(data, f"{prefix}_{i}") for i in range(count)] + + +def set_termination_event(monitor, event, transactions): + def terminate_cb(_): + if monitor.stats.received_transactions == transactions: + event.set() + + monitor.add_callback(terminate_cb) + + +@cocotb.coroutine +async def set_handshake_event(clk, channel, event): + while True: + await RisingEdge(clk) + if channel.rdy.value and channel.vld.value: + event.set() + + +@cocotb.coroutine +async def get_handshake_event(dut, event, func): + while True: + await event.wait() + func() + event.clear() + + +def connect_axi_read_bus(dut, name=""): + axi_ar = "axi_ar" + axi_r = "axi_r" + + if name: + name += "_" + + bus_axi_ar = axi_channels.AxiARBus.from_prefix(dut, name + axi_ar) + bus_axi_r = axi_channels.AxiRBus.from_prefix(dut, name + axi_r) + + return axi_channels.AxiReadBus(bus_axi_ar, bus_axi_r) + + +def connect_axi_write_bus(dut, name=""): + axi_aw = "axi_aw" + axi_w = "axi_w" + axi_b = "axi_b" + + if name: + name += "_" + + bus_axi_aw = axi_channels.AxiAWBus.from_prefix(dut, name + axi_aw) + bus_axi_w = axi_channels.AxiWBus.from_prefix(dut, name + axi_w) + bus_axi_b = axi_channels.AxiBBus.from_prefix(dut, name + axi_b) + + return axi_channels.AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) + + +def connect_axi_bus(dut, name=""): + bus_axi_read = connect_axi_read_bus(dut, name) + bus_axi_write = connect_axi_write_bus(dut, name) + + return axi_channels.AxiBus(bus_axi_write, bus_axi_read) + + +async def csr_write(cpu, csr, data): + if isinstance(data, int): + data = data.to_bytes(AXI_DATA_W_BYTES, byteorder="little") + assert len(data) <= AXI_DATA_W_BYTES + await cpu.write(csr.value * AXI_DATA_W_BYTES, data) + + +async def csr_read(cpu, csr): + return await cpu.read(csr.value * AXI_DATA_W_BYTES, AXI_DATA_W_BYTES) + + +async def test_csr(dut): + + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + await reset_dut(dut, 50) + + csr_bus = connect_axi_bus(dut, "csr") + + cpu = AxiMaster(csr_bus, dut.clk, dut.rst) + + await triggers.ClockCycles(dut.clk, 10) + i = 0 + for reg in CSR: + expected_src = bytearray.fromhex("0DF0AD8BEFBEADDE") + assert len(expected_src) >= AXI_DATA_W_BYTES + expected = expected_src[-AXI_DATA_W_BYTES:] + expected[0] += i + await csr_write(cpu, reg, expected) + read = await csr_read(cpu, reg) + assert ( + read.data == expected + ), "Expected data doesn't match contents of the {}".format(reg) + i += 1 + await triggers.ClockCycles(dut.clk, 10) + + +async def test_reset(dut): + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + await reset_dut(dut, 50) + + csr_bus = connect_axi_bus(dut, "csr") + cpu = AxiMaster(csr_bus, dut.clk, dut.rst) + + await triggers.ClockCycles(dut.clk, 10) + await start_decoder(cpu) + timeout = 10 + status = await csr_read(cpu, CSR.STATUS) + while (int.from_bytes(status.data, byteorder="little") == Status.IDLE.value) & ( + timeout != 0 + ): + status = await csr_read(cpu, CSR.STATUS) + timeout -= 1 + assert timeout != 0 + + await reset_dut(dut, 50) + await wait_for_idle(cpu, 10) + + await triggers.ClockCycles(dut.clk, 10) + + +async def configure_decoder(dut, cpu, ibuf_addr, obuf_addr): + status = await csr_read(cpu, CSR.STATUS) + if int.from_bytes(status.data, byteorder="little") != Status.IDLE.value: + await reset_dut(dut, 50) + await csr_write(cpu, CSR.INPUTBUFFER, ibuf_addr) + await csr_write(cpu, CSR.OUTPUTBUFFER, obuf_addr) + + +async def start_decoder(cpu): + await csr_write(cpu, CSR.START, 0x1) + + +async def wait_for_idle(cpu, timeout=100): + status = await csr_read(cpu, CSR.STATUS) + while (int.from_bytes(status.data, byteorder="little") != Status.IDLE.value) & ( + timeout != 0 + ): + status = await csr_read(cpu, CSR.STATUS) + timeout -= 1 + assert timeout != 0 + + +async def reset_dut(dut, rst_len=10): + dut.rst.setimmediatevalue(0) + await triggers.ClockCycles(dut.clk, rst_len) + dut.rst.setimmediatevalue(1) + await triggers.ClockCycles(dut.clk, rst_len) + dut.rst.setimmediatevalue(0) + + +def get_clock_time(clock: Clock): + return get_sim_time(units="step") / clock.period + + +def connect_xls_channel(dut, channel_name, xls_struct): + channel = xlschannel.XLSChannel(dut, channel_name, dut.clk, start_now=True) + monitor = xlschannel.XLSChannelMonitor(dut, channel_name, dut.clk, xls_struct) + + return (channel, monitor) + + +def prepare_test_environment(dut): + clock = Clock(dut.clk, 10, units="us") + cocotb.start_soon(clock.start()) + + memory_bus = connect_axi_bus(dut, "memory") + csr_bus = connect_axi_bus(dut, "csr") + axi_buses = {"memory": memory_bus, "csr": csr_bus} + + cpu = AxiMaster(csr_bus, dut.clk, dut.rst) + + return (axi_buses, cpu, clock) + + +async def test_fse_lookup_decoder(dut, clock, expected_fse_lookups): + lookup_dec_resp_channel = xlschannel.XLSChannel( + dut.ZstdDecoder.xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next_inst148, + "zstd_dec__flc_resp", + dut.clk, + ) + fse_lookup_resp_handshake = triggers.Event() + + block_cnt = 0 + + def func(): + nonlocal block_cnt + assert block_cnt <= len(expected_fse_lookups) + print_fse_ram_contents( + dut.ll_fse_ram.mem, "LL", size=len(expected_fse_lookups[block_cnt]["ll"]) + ) + print_fse_ram_contents( + dut.ml_fse_ram.mem, "ML", size=len(expected_fse_lookups[block_cnt]["ml"]) + ) + print_fse_ram_contents( + dut.of_fse_ram.mem, "OF", size=len(expected_fse_lookups[block_cnt]["of"]) + ) + check_ram_contents( + dut.ll_fse_ram.mem, [x.value for x in expected_fse_lookups[block_cnt]["ll"]] + ) + check_ram_contents( + dut.ml_fse_ram.mem, [x.value for x in expected_fse_lookups[block_cnt]["ml"]] + ) + check_ram_contents( + dut.of_fse_ram.mem, [x.value for x in expected_fse_lookups[block_cnt]["of"]] + ) + block_cnt += 1 + + cocotb.start_soon( + set_handshake_event(dut.clk, lookup_dec_resp_channel, fse_lookup_resp_handshake) + ) + cocotb.start_soon(get_handshake_event(dut, fse_lookup_resp_handshake, func)) + + +async def test_fse_lookup_decoder_for_huffman(dut, clock, expected_fse_lookups): + lookup_dec_resp_channel = xlschannel.XLSChannel( + dut.ZstdDecoder.xls_modules_zstd_comp_lookup_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__HuffmanFseWeightsDecoder_0__CompLookupDecoder_0__64_8_16_1_15_8_32_1_7_9_8_1_8_16_1_next_inst5, + "zstd_dec__fse_table_finish__1", + dut.clk, + ) + fse_lookup_resp_handshake = triggers.Event() + + block_cnt = 0 + + def func(): + nonlocal block_cnt + assert block_cnt <= len(expected_fse_lookups) + print_fse_ram_contents( + dut.huffman_literals_weights_fse_ram_ram.mem, + f"HUFMMAN ({block_cnt})", + size=len(expected_fse_lookups[block_cnt]), + ) + check_ram_contents( + dut.huffman_literals_weights_fse_ram_ram.mem, + [x.value for x in expected_fse_lookups[block_cnt]], + ) + block_cnt += 1 + + cocotb.start_soon( + set_handshake_event(dut.clk, lookup_dec_resp_channel, fse_lookup_resp_handshake) + ) + cocotb.start_soon(get_handshake_event(dut, fse_lookup_resp_handshake, func)) + + +def reverse_expected_huffman_codes(exp_codes): + def reverse_bits(value, max_bits): + bv = BinaryValue(value=value, n_bits=max_bits, bigEndian=False) + return int(BinaryValue(value=bv.binstr[::-1], n_bits=max_bits, bigEndian=False)) + + max_bits = max(d["length"] for d in exp_codes) + + codes = [] + for record in exp_codes: + codes += [ + { + "code": reverse_bits(record["code"], max_bits), + "length": record["length"], + "symbol": record["symbol"], + } + ] + return codes + + +async def test_huffman_codes(dut, clock, expected_codes): + WEIGHT_CODE_BUILDER_INST = ( + dut.ZstdDecoder.xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next_inst20 + ) + CODES_CHANNEL_NAME = "zstd_dec__code_builder_codes" + + codes_channel = xlschannel.XLSChannel( + WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME, dut.clk + ) + huffman_code_handshake = triggers.Event() + + codes = [] + block_cnt = 0 + packet_cnt = 0 + symbol_cnt = 0 + + def func(): + nonlocal codes + nonlocal symbol_cnt + nonlocal packet_cnt + nonlocal block_cnt + + assert block_cnt <= 32 + codes_data = getattr(WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME) + data = CodeBuilderOutput.from_int(codes_data.value) + + symbol_valid_array = fields_as_array(data, "symbol_valid", 8) + code_length_array = fields_as_array(data, "code_length", 8) + code_array = fields_as_array(data, "code", 8) + + for symbol_valid, code_length, code in zip( + symbol_valid_array, code_length_array, code_array + ): + if symbol_valid == 1: + codes += [{"symbol": symbol_cnt, "code": code, "length": code_length}] + symbol_cnt += 1 + packet_cnt += 1 + + if packet_cnt == 32: + assert codes == reverse_expected_huffman_codes(expected_codes[block_cnt]) + packet_cnt = 0 + symbol_cnt = 0 + block_cnt += 1 + codes = [] + + cocotb.start_soon( + set_handshake_event(dut.clk, codes_channel, huffman_code_handshake) + ) + cocotb.start_soon(get_handshake_event(dut, huffman_code_handshake, func)) + + +async def test_huffman_weights(dut, clock, expected_huffman_weights): + lookup_dec_resp_channel = xlschannel.XLSChannel( + dut.ZstdDecoder.xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next_inst21, + "zstd_dec__weights_dec_resp", + dut.clk, + ) + huffman_weights_resp_handshake = triggers.Event() + + block_cnt = 0 + + def func(): + nonlocal block_cnt + assert block_cnt <= len(expected_huffman_weights) + print_ram_contents( + dut.huffman_literals_weights_mem_ram_ram.mem, + f"WEIGHTS ({block_cnt})", + size=64, + ) + check_ram_contents( + dut.huffman_literals_weights_mem_ram_ram.mem, + expected_huffman_weights[block_cnt], + ) + block_cnt += 1 + + cocotb.start_soon( + set_handshake_event( + dut.clk, lookup_dec_resp_channel, huffman_weights_resp_handshake + ) + ) + cocotb.start_soon(get_handshake_event(dut, huffman_weights_resp_handshake, func)) + + +async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): + """Test decoder with zstd-compressed data provided in `encoded_file` + + The output of the decoder is compared with the output of decodercorpus + using the same input file. + """ + memory_bus = axi_buses["memory"] + + (unused_notify_channel, notify_monitor) = connect_xls_channel( + dut, NOTIFY_CHANNEL, NotifyStruct + ) + assert_notify = triggers.Event() + set_termination_event(notify_monitor, assert_notify, 1) + + mem_size = MAX_ENCODED_FRAME_SIZE_B + ibuf_addr = 0x0 + obuf_addr = mem_size // 2 + + await reset_dut(dut, 50) + + expected_decoded_frame = data_generator.DecompressFrame(encoded_file.read()) + reference_memory = SparseMemory(mem_size) + reference_memory.write(obuf_addr, expected_decoded_frame) + + # Initialise testbench memory with generated ZSTD frame + memory = AxiRamFromFile( + bus=memory_bus, clock=dut.clk, reset=dut.rst, path=encoded_file.name, size=mem_size + ) + + await configure_decoder(dut, cpu, ibuf_addr, obuf_addr) + output_monitor = AxiWMonitor(memory_bus.write.w, dut.clk, dut.rst) + await start_decoder(cpu) + decode_start = get_clock_time(clock) + await output_monitor.wait() + decode_first_packet = get_clock_time(clock) + await assert_notify.wait() + decode_end = get_clock_time(clock) + await wait_for_idle(cpu) + # Read decoded frame in chunks of AXI_DATA_W length + # Compare against frame decompressed with the reference library + expected_packet_count = ( + len(expected_decoded_frame) + (AXI_DATA_W_BYTES - 1) + ) // AXI_DATA_W_BYTES + for read_op in range(0, expected_packet_count): + addr = obuf_addr + (read_op * AXI_DATA_W_BYTES) + mem_contents = memory.read(addr, AXI_DATA_W_BYTES) + exp_mem_contents = reference_memory.read(addr, AXI_DATA_W_BYTES) + assert mem_contents == exp_mem_contents, ( + "{} bytes of memory contents at address {} " + "don't match the expected contents:\n" + "{}\nvs\n{}" + ).format( + AXI_DATA_W_BYTES, + hex(addr), + hex(int.from_bytes(mem_contents, byteorder="little")), + hex(int.from_bytes(exp_mem_contents, byteorder="little")), + ) + + latency = decode_first_packet - decode_start + throughput_repacketizer = expected_packet_count / (decode_end - decode_first_packet) + throughput_bytes = throughput_repacketizer * AXI_DATA_W_BYTES + print(f"Decoding latency: {latency} cycles") + print( + f"Decoding throughput: {throughput_bytes}B/cycle ({throughput_repacketizer} packets/cycle)" + ) + + await ClockCycles(dut.clk, 20) + + return (latency, throughput_bytes, throughput_repacketizer) + + +async def randomized_testing_routine( + dut, + test_cases=1, + block_type=data_generator.BlockType.RANDOM, + literal_type=data_generator.LiteralType.RANDOM, + expected_fse_lookups=None, + expected_fse_huffman_lookups=None, + expected_huffman_weights=None, + expected_huffman_codes=None, +): + (axi_buses, cpu, clock) = prepare_test_environment(dut) + measurements = [] + frame_id = 0 + seed = 2 # FIXME: Dehardcode + for test_case in range(test_cases): + if expected_fse_lookups is not None: + await test_fse_lookup_decoder(dut, clock, expected_fse_lookups) + if expected_fse_huffman_lookups is not None: + await test_fse_lookup_decoder_for_huffman( + dut, clock, expected_fse_huffman_lookups + ) + if expected_huffman_codes is not None: + await test_huffman_codes(dut, clock, expected_huffman_codes) + if expected_huffman_weights is not None: + await test_huffman_weights(dut, clock, expected_huffman_weights) + + # FIXME: use delete_on_close=False after moving to python 3.12 + with tempfile.NamedTemporaryFile(delete=False) as input_file: + # Generate ZSTD frame to temporary file + data_generator.GenerateFrame(seed, block_type, input_file.name, literal_type) + print( + f"\nusing randomly generated (seed={seed}) input file for decoder: {input_file.name}\n" + ) + measurements.append(await test_decoder(dut, axi_buses, cpu, clock, input_file)) + + print("Decoding {} ZSTD frames done".format(block_type.name)) + for measurement in measurements: + print( + f"Frame #{frame_id}: latency: {measurement[0]} cycles; throughput: {measurement[1]} B/cycle ({measurement[2]} packets/cycle)" + ) + frame_id += 1 + + +async def pregenerated_testing_routine( + dut, + pregenerated_path, + expected_fse_lookups=None, + expected_fse_huffman_lookups=None, + expected_huffman_weights=None, + expected_huffman_codes=None, +): + (axi_buses, cpu, clock) = prepare_test_environment(dut) + print( + f"\nusing pregenerated input file for decoder: {pregenerated_path}\n" + ) + + if expected_fse_lookups is not None: + await test_fse_lookup_decoder(dut, clock, expected_fse_lookups) + if expected_fse_huffman_lookups is not None: + await test_fse_lookup_decoder_for_huffman( + dut, clock, expected_fse_huffman_lookups + ) + if expected_huffman_codes is not None: + await test_huffman_codes(dut, clock, expected_huffman_codes) + if expected_huffman_weights is not None: + await test_huffman_weights(dut, clock, expected_huffman_weights) + + with open(pregenerated_path, 'rb') as input_file: + measurement = await test_decoder(dut, axi_buses, cpu, clock, input_file) + + print( + f"Frame #0: latency: {measurement[0]} cycles; throughput: {measurement[1]} B/cycle ({measurement[2]} packets/cycle)" + ) + + +def run_test(test_module, build_args=[], sim="icarus"): + toplevel = "zstd_dec_wrapper" + verilog_sources = [ + "xls/modules/zstd/zstd_dec.v", + "xls/modules/zstd/rtl/xls_fifo_wrapper.sv", + "xls/modules/zstd/rtl/zstd_dec_wrapper.sv", + "xls/modules/zstd/axi_crossbar_wrapper.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_rd.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_wr.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_addr.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_register_rd.v", + "external/com_github_alexforencich_verilog_axi/rtl/axi_register_wr.v", + "external/com_github_alexforencich_verilog_axi/rtl/arbiter.v", + "external/com_github_alexforencich_verilog_axi/rtl/priority_encoder.v", + "xls/modules/zstd/rtl/ram_1r1w.v", + ] + + cocotb_utils.run_test(toplevel, test_module, verilog_sources, build_args=build_args, sim=sim) From e2e831ae33133c8df69de01171a25edb41191be4 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Wed, 30 Jul 2025 12:44:48 +0200 Subject: [PATCH 034/159] modules: zstd: cocotb: add zstd cli --- xls/modules/zstd/BUILD | 1 - xls/modules/zstd/zstd_dec_cocotb_cli.py | 59 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 xls/modules/zstd/zstd_dec_cocotb_cli.py diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 539b3e9e32..7d125a3d90 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1870,7 +1870,6 @@ py_binary( name = "zstd_dec_cocotb_cli", srcs = ["zstd_dec_cocotb_cli.py"], env = { - "PYTHONUNBUFFERED": "1", "BUILD_WORKING_DIRECTORY": "sim_build", "PERL": "$(PERL)", }, diff --git a/xls/modules/zstd/zstd_dec_cocotb_cli.py b/xls/modules/zstd/zstd_dec_cocotb_cli.py new file mode 100644 index 0000000000..847dd31e1c --- /dev/null +++ b/xls/modules/zstd/zstd_dec_cocotb_cli.py @@ -0,0 +1,59 @@ +# Copyright 2025 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import sys +import cocotb +import os +import pathlib +from xls.modules.zstd.zstd_dec_cocotb_common import pregenerated_testing_routine, run_test +from multiprocessing import cpu_count + +@cocotb.test(timeout_time=int(os.getenv("ZSTD_DEC_COCOTB_CLI_TIMEOUT", "5000")), timeout_unit="ms") +async def zstd_cli_test(dut): + input_name = os.getenv("ZSTD_DEC_COCOTB_CLI_INPUT") + print("input_name: ", input_name) + await pregenerated_testing_routine(dut, input_name) + +def usage(): + print(f"usage: {os.path.basename(sys.argv[0])} /abs/path/to/input.zst [timeout_is_ms]") + sys.exit(1) + +if __name__ == "__main__": + help = "-h" in sys.argv or "--help" in sys.argv + bad_params = len(sys.argv) not in (2,3) + if bad_params or help: + usage() + + if not os.path.isabs(sys.argv[1]): + # bazel run changes the working dir to runfiles tree so relative paths won't work + print(f"error: '{sys.argv[1]}' is not absolute path") + usage() + + # cocotb doesn't perserve global vars nor sys.argv + # we work it around by passing arguments through env + os.environ["ZSTD_DEC_COCOTB_CLI_INPUT"] = sys.argv[1] + + if len(sys.argv) == 3: + os.environ["ZSTD_DEC_COCOTB_CLI_TIMEOUT"] = sys.argv[2] + + test_module = [pathlib.Path(__file__).stem] + run_test(test_module, build_args=[ + "-Wno-fatal", + "-Wwarn-ASSIGNIN", + "--trace-fst", # trace in more space-efficient format than vcd + "--trace-threads", "2", # 2 is maximum + "-O3", + "--threads", str(cpu_count() - 2) + ], sim="verilator") From 92ceaba8ac7d2842343268c62a7096300b5450cd Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Wed, 30 Jul 2025 12:45:57 +0200 Subject: [PATCH 035/159] modules: zstd: rtl: fix default ADDR_WIDTH computation --- xls/modules/zstd/rtl/ram_1r1w.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/rtl/ram_1r1w.v b/xls/modules/zstd/rtl/ram_1r1w.v index cbcbfe55f6..6d79c6f9fe 100644 --- a/xls/modules/zstd/rtl/ram_1r1w.v +++ b/xls/modules/zstd/rtl/ram_1r1w.v @@ -16,7 +16,7 @@ module ram_1r1w # ( parameter DATA_WIDTH = 4, parameter SIZE = 32, parameter NUM_PARTITIONS = 1, - parameter ADDR_WIDTH = $clog(SIZE), + parameter ADDR_WIDTH = $clog2(SIZE), parameter INIT_FILE = "" ) ( input wire clk, From 2ece3aee4d0e453157c906539c19193cbe45d341 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Wed, 30 Jul 2025 12:47:48 +0200 Subject: [PATCH 036/159] modules: zstd: add verilator dependency --- MODULE.bazel | 4 + MODULE.bazel.lock | 25 ++++-- dependency_support/pip_requirements_lock.txt | 73 ++++++++-------- .../rules_hdl/add_standalone_verilator.patch | 85 +++++++++++++++++++ dependency_support/rules_hdl/workspace.bzl | 1 + xls/modules/zstd/BUILD | 1 + 6 files changed, 146 insertions(+), 43 deletions(-) create mode 100644 dependency_support/rules_hdl/add_standalone_verilator.patch diff --git a/MODULE.bazel b/MODULE.bazel index 388a8efd26..5f076198d4 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -152,3 +152,7 @@ git_override( ) register_toolchains("//xls/common/toolchains:all") + +# Verilator support +bazel_dep(name = "aspect_bazel_lib", version = "2.19.4") +bazel_dep(name = "rules_perl", version = "0.4.2") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 38891b1c95..05b81e5ec1 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -35,7 +35,9 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/1.38.0/MODULE.bazel": "6307fec451ba9962c1c969eb516ebfe1e46528f7fa92e1c9ac8646bef4cdaa3f", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.40.3/MODULE.bazel": "668e6bcb4d957fc0e284316dba546b705c8d43c857f87119619ee83c4555b859", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "cb1ba9f9999ed0bc08600c221f532c1ddd8d217686b32ba7d45b0713b5131452", - "https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/source.json": "92494d5aa43b96665397dd13ee16023097470fa85e276b93674d62a244de47ee", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.4/MODULE.bazel": "d39e4b18e594d81c526d7cfc513e7ecfa8ca9eb5b61488d1d790faa94b34f2d9", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.4/source.json": "506fa924e19fd8a33d617e33a17e4fce845f9ff9acb3a2aa7cf7300650698705", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "66baf724dbae7aff4787bf2245cc188d50cb08e07789769730151c0943587c14", @@ -305,6 +307,8 @@ "https://bcr.bazel.build/modules/highs/1.9.0/source.json": "40f023f93f0bdcaeb14cfd3975c9b6f2f9a0315201791611ca203450f2b4c028", "https://bcr.bazel.build/modules/highwayhash/0.0.0-20240305-5ad3bf8/MODULE.bazel": "5c7f29d5bd70feff14b0f65b39584957e18e4a8d555e5a29a4c36019afbb44b9", "https://bcr.bazel.build/modules/highwayhash/0.0.0-20240305-5ad3bf8/source.json": "211c0937ef5f537da6c3c135d12e60927c71b380642e207e4a02b86d29c55e85", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", + "https://bcr.bazel.build/modules/jq.bzl/0.1.0/source.json": "746bf13cac0860f091df5e4911d0c593971cd8796b5ad4e809b2f8e133eee3d5", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", @@ -340,6 +344,8 @@ "https://bcr.bazel.build/modules/opentracing-cpp/1.6.0/source.json": "da1cb1add160f5e5074b7272e9db6fd8f1b3336c15032cd0a653af9d2f484aed", "https://bcr.bazel.build/modules/or-tools/9.12/MODULE.bazel": "9b3c0a7f08772f51b4083ccca0e69abf49b1488e2fd9078e535d8855cdda0cf2", "https://bcr.bazel.build/modules/or-tools/9.12/source.json": "98c1da7031be89e3f58e95a16784c1f5524c0ff6d8ea436ac6df4e9efe03458b", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", "https://bcr.bazel.build/modules/pcre2/10.43/MODULE.bazel": "08eaa025111bd0fedc14a8187c2905fa6ee4501fbe558193e9bf6cc3e2cdf23c", "https://bcr.bazel.build/modules/pcre2/10.43/source.json": "8b4149e707094f1d5b57df7216539c3415226e814085c4d960bd9f3d49581b88", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", @@ -505,8 +511,8 @@ "https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d", "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/MODULE.bazel": "b66eadebd10f1f1b25f52f95ab5213a57e82c37c3f656fcd9a57ad04d2264ce7", "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/source.json": "45bd343155bdfed2543f0e39b80ff3f6840efc31975da4b5795797f4c94147ad", - "https://bcr.bazel.build/modules/rules_perl/0.2.4/MODULE.bazel": "5f5af7be4bf5fb88d91af7469518f0fd2161718aefc606188f7cd51f436ca938", - "https://bcr.bazel.build/modules/rules_perl/0.2.4/source.json": "574317d6b3c7e4843fe611b76f15e62a1889949f5570702e1ee4ad335ea3c339", + "https://bcr.bazel.build/modules/rules_perl/0.4.2/MODULE.bazel": "ade797b135be13d4715f7bb045bb06593ea3b8604f71330981cda6e5f95e42d1", + "https://bcr.bazel.build/modules/rules_perl/0.4.2/source.json": "feb6b8996edcc944b5b82e6440c8104e5907f7eca7e0bf553c7c097d5181ac25", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", @@ -527,7 +533,8 @@ "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", "https://bcr.bazel.build/modules/rules_shell/0.4.0/MODULE.bazel": "0f8f11bb3cd11755f0b48c1de0bbcf62b4b34421023aa41a2fc74ef68d9584f0", - "https://bcr.bazel.build/modules/rules_shell/0.4.0/source.json": "1d7fa7f941cd41dc2704ba5b4edc2e2230eea1cc600d80bd2b65838204c50b95", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/1.18.0/MODULE.bazel": "a6aba73625d0dc64c7b4a1e831549b6e375fbddb9d2dde9d80c9de6ec45b24c9", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", @@ -555,6 +562,8 @@ "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", "https://bcr.bazel.build/modules/swig/4.3.0/MODULE.bazel": "51619e147172c5380869cc90460b1c7fecfe21d6f566e97bc7ecf61244bdc7b8", "https://bcr.bazel.build/modules/swig/4.3.0/source.json": "ea8dac67896e3a623cd92c48573a351c4bab1537f5aeb210c1c1e049994dd599", + "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", + "https://bcr.bazel.build/modules/tar.bzl/0.2.1/source.json": "600ac6ff61744667a439e7b814ae59c1f29632c3984fccf8000c64c9db8d7bb6", "https://bcr.bazel.build/modules/toolchains_llvm/1.4.0/MODULE.bazel": "05239402b7374293359c2f22806f420b75aa5d6f4b15a2eaa809a2c214d58b31", "https://bcr.bazel.build/modules/toolchains_llvm/1.4.0/source.json": "229a516d282b17a82be54c6e3ae220a1b750fb55a8495567e5c7a9d09423f3e2", "https://bcr.bazel.build/modules/upb/0.0.0-20211020-160625a/MODULE.bazel": "6cced416be2dc5b9c05efd5b997049ba795e5e4e6fafbe1624f4587767638928", @@ -567,6 +576,8 @@ "https://bcr.bazel.build/modules/xds/0.0.0-20240423-555b57e/source.json": "7227e1fcad55f3f3cab1a08691ecd753cb29cc6380a47bc650851be9f9ad6d20", "https://bcr.bazel.build/modules/xz/5.4.5.bcr.1/MODULE.bazel": "c037f75fa1b7e1ff15fbd15d807a8ce545e9b02f02df0a9777aa9aa7d8b268bb", "https://bcr.bazel.build/modules/xz/5.4.5.bcr.1/source.json": "766f28499a16fa9ed8dc94382d50e80ceda0d0ab80b79b7b104a67074ab10e1f", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", + "https://bcr.bazel.build/modules/yq.bzl/0.1.1/source.json": "2d2bad780a9f2b9195a4a370314d2c17ae95eaa745cefc2e12fbc49759b15aa3", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.2.13/MODULE.bazel": "aa6deb1b83c18ffecd940c4119aff9567cd0a671d7bba756741cb2ef043a29d5", @@ -670,7 +681,7 @@ }, "@@aspect_rules_esbuild~//esbuild:extensions.bzl%esbuild": { "general": { - "bzlTransitiveDigest": "8iOqbPY5ve3DvjzaI1mJZ8XTiJypN2PeWvcKOvmZLy8=", + "bzlTransitiveDigest": "ByrhO7EMZ3RIaW8ldVEqFBXoJ5ogN4qmIfPfPbMGdUI=", "usagesDigest": "iDVoyPxUeADmfK8ssoyG3Ehq1bj6p7A43LpEiE266os=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -2110,7 +2121,7 @@ "@@or-tools~//bazel/ortools_requirements.txt": "37e22395e78ef3572ab57b7717fd8f54851919bb73ca404f367536dc15a8e3eb", "@@pybind11_abseil~//pybind11_abseil/requirements/requirements_lock_3_11.txt": "7d1074311e9f32f25ca112fc86fbec98bc024d820a8dc00f94e8679a7e6b480c", "@@rules_python~//tools/publish/requirements_linux.txt": "8175b4c8df50ae2f22d1706961884beeb54e7da27bd2447018314a175981997d", - "@@//dependency_support/pip_requirements_lock.txt": "5108b11922afcb1c3b8abc232356cbff368ad481d6bb54472356427cba87f4fc", + "@@//dependency_support/pip_requirements_lock.txt": "faf7d628ea2b0131ac12e18c23ff0e24e2c9c83b47144356afaac95bf47c6f27", "@@or-tools~//bazel/notebook_requirements.txt": "ca78fad693f1b35eed8bb7c54e5ddf7ad255c4b9d94ce18efa55859759a8fb70", "@@protoc-gen-validate~//python/requirements.txt": "6a540bc029bbf3a5f78f9f9282bd30e2965c98eeb150f2cfc5facccbd8a98297", "@@pybind11_protobuf~//pybind11_protobuf/requirements/requirements_lock_3_10.txt": "afd6f9406f4e80a504f1575121a937f4c15388aec4a17393f0cb1ac9f09d18dd", @@ -14412,7 +14423,7 @@ "dep_template": "@xls_pip_deps//{name}:{target}", "python_interpreter_target": "@@rules_python~~python~python_3_11_host//:python", "repo": "xls_pip_deps_311", - "requirement": "cocotb==1.7.2 --hash=sha256:0c1687ac78141724b8529e029ee6299698ecaa8a2c431b744eeff487a4bb18de --hash=sha256:163e5262020cc21f6a0391fb4727c9ab3ecbf6ee12a1472c8f7320b3ba211a50 --hash=sha256:170cf4d01c4d7c6c5b141ffc1824e846a6c8adbed553a50984cd522c1dddb111 --hash=sha256:1851ac56eed7bb6c745aabfc0e417195cb4f08b5df50846c04eb77a868bfeaba --hash=sha256:1abffb36183b07469c490836c66d8b9e24fc1bec7c27356818618a6719fabd4b --hash=sha256:33be79f048f4072240668a079d2bcebd1a24611a0a1e55439b65ffa0ff077790 --hash=sha256:34ab1bf3f18476724dd4e21dbcc0e060e813eb502abe155b800084fb6945360c --hash=sha256:360019f74270661d14e9caa8103e740a070cb466ab08376a565ec0ef4c13dbbf --hash=sha256:37ddb79f4ab60d2d2dc5a9db5bf767d226eb4978fd15b84dfb968d31ab2fcda5 --hash=sha256:43f5af578803e5726b5c75421c0e35e54021ab423d3aa4efe930feb740d6479d --hash=sha256:4738f36b9730cc05b74ccba3648dba0455cf9f237abf822ef307a274a29474c2 --hash=sha256:4aa5d73ebdb59ef24cef36a1f8cca11dcecb3ee7b71a84df02751020bc67ea77 --hash=sha256:574d21501ff1a3d36889397cd58a18d102d0e40391aa7a0274b600d1cc4c7dc3 --hash=sha256:69f4e539dd308c9e169ab23135138ec397061b700f209803a6022ae9fbe08933 --hash=sha256:6f289ac00f4884046ec64db7006e47b1c857a36dcd2a80ea0873cbff00248368 --hash=sha256:707f795a17679b4653a50bd4094536a46fbfee5c6e3d951fac4320ee211ad13f --hash=sha256:7828e22946f128aa59cb9254de4037b99e3bd5a51fe8f590cf64a3141d742a37 --hash=sha256:82f694da656a699154b15ee28be3ac39c41a71d33985313deda12a3645f8b3db --hash=sha256:959892eb94bd0b3ff40e0fca51d33a3936416deb853e2bac4f7f766b40002650 --hash=sha256:a7ec6a2d212c27ec46bed17a15d60b7b29cd0f734f11cc16d2cb4d3f6136e133 --hash=sha256:a90c77f4bbfdf73aa16093dfe95c68af1a1ca685ebfa525f3f150eab252f6728 --hash=sha256:b288a59fa8dffc1cbc53105e71e2f8c82421081b17282e41319832654b309477 --hash=sha256:c41cc8d4ece57f5e26076cd12f1e11d464d7f118fdb74b958269535185d99a30 --hash=sha256:c8dce91d2a918ee63338d79b08e3d52f1d2797efd9c2bedd13c33d674f730db8 --hash=sha256:d26a8a40cea61f295be04b1164a5dd9ec873f13a39814ad00efec7fd899320e0 --hash=sha256:d80b3baafff1a8a91ac860023c448c603767bed502258160a5cb6029976fec4f --hash=sha256:dcf5354268f16d9e11e05cf3616172ca5ef503b45567f75ebd96a0bfdb9832d1 --hash=sha256:ded849360fb31746f1ba3a994f89c3bba2466ec2d0b4b5da0030645645f938d4 --hash=sha256:e03df73573aec261447602904bd66927eeb2f00dd24370dc9a57f47fd42c4d70 --hash=sha256:f97c2eb92cb68831f19b82ba0038ce40fa73c5edbffb7930745edac20c5358d1 --hash=sha256:fa8abed5260baf4306fbfb997c8789fe24bc229cd762b12d7dba0b9c20147b1d", + "requirement": "cocotb==1.9.0 --hash=sha256:02a58ef6c941114964096e7c039bdd4e67e63816cfd2f6a9af6a34cd92b00e8e --hash=sha256:0819794ef5e8fd14fee0b265933226cf600e85edc2f1a749b4d5f8fa2d31ce4e --hash=sha256:0ba35617a677ff65a1273411a3dfdfc5f587128ad8cb9e941ab0eb17ec8fb3e2 --hash=sha256:17556e3a23562f64d577d0eb117fe02e384aedee997b29497b5c395f5010ff82 --hash=sha256:19b4e27b53a16e0b9c4cc5227c7f9d4dccac06e431a4f937e9f5513350196333 --hash=sha256:1a0381ced5590a726032ba2265c6b70ac12cfb49edb152be86a081bb7d104751 --hash=sha256:1aff68cf77059448a9a3278079037e34b50c8c2aee466d984295fa7fe699d390 --hash=sha256:277281420fd6fc3002bb85d6bec497bd20ff3a3905d4b5f1301faf975f750ede --hash=sha256:2daf743320331615f4e8ffb877ab0b04e6f913b911bb11bf9dbc1d876d9c4220 --hash=sha256:2e9bcdbfba3e99c9297bd0d74ba781772d89d2c86e893980784ada252bd1a0f8 --hash=sha256:3058c977f9d4e1f6333d505947f34b9142910719f1d8631c40a151dd86bad727 --hash=sha256:5832d894419a9e8fe5c242e3ac86588e16e2cb379822dcb154bfec8544ae858e --hash=sha256:598b841ed0809e5c64d8c383b8035f6ace5a6f9013f680cdc6981221911c005d --hash=sha256:5a5c91027d7652aaf10e101743edd6b1e832039a19af75fca301275ef30f01d4 --hash=sha256:61418f619af72c8cca8de622785b4f4bfc17ace09981de6eb44feae560cf3bbb --hash=sha256:784c914c8df3fd79cfb148d2bcd17c4b2703c89af1278ed98773afb57ceea3e6 --hash=sha256:87a19d3012f505ba7fda37483b851ef0ca40290ad8a9b28a820b84f8574287bb --hash=sha256:89503f0749362d36b6fab8636710f1848943c21f9d488672921bac21e9edd29f --hash=sha256:89e5189fd393918c27af2daefdcb13df4d52fa761f065d5964d2c4ff5c0642fb --hash=sha256:8cb4b0edf8f0b47c3b604b461cb574fc75fd97efa893cbaf828f4f2f71cf459e --hash=sha256:94e884e16186899ad5b4d131c3f7ff0a2277e67ea0660754e8810a4bbf2d610e --hash=sha256:997dbca2a2cd933fd0a44d9fadeebc1e8a40701db15ea06f207811933dceb350 --hash=sha256:a7cea13cb2fe4f5ca735490846342885117778a73008a67ed9cac667aaaf3f0d --hash=sha256:a84edfbfa57dc6e16845a55feb0b4e1c8b6bbfa5ef1ab6768beba8d81e0546aa --hash=sha256:a95b5e5708a3629d319d2b655d11345cc7e97fea9bdc9bc1df7435926ac30966 --hash=sha256:aa6818c39ca1ce699e4bb1d84899c4f98c2d25c7671bd6c7beee3b1ee9d68834 --hash=sha256:ab99bf7e055780b57419d4133fd4dca9c72a03b766a3e2200552f10498eb8845 --hash=sha256:b966f5560a494fd99f95a1562f9326ca20c35bb118d4e6b50db41da8e4a6f718 --hash=sha256:bc44a7708a5a63d3059a622c2fb90831dc33534c3343e971f5a6c78905097baa --hash=sha256:c11e21d291ba2f889e33c21d76e9aec6ffdfb5666053dc34452666579daa675b --hash=sha256:c848de13583478d71cc91e528e17c051ca6a3b92e89d703ac5015f17cab1287b --hash=sha256:d944aa5509a0f0786d6f30554a2f8b1f229847f9ac9988879d7a05497739f668 --hash=sha256:f50862153e1364f6edeaef9d70505093549fa097e9b2555ea46d1e4f94ac3287 --hash=sha256:f74c598e230e1035103f6e3a97dd7a0e1bcacf7f3ea7481cd3bcde477b74e379 --hash=sha256:fcb81c6c37e11b0729768dd8e192a9cfb809778699ab1fe89f4d92ba0beb3092 --hash=sha256:ff2ddc8b304eb7076ceead2534a1b9828df771798fa9c2601ea983c86d23ec08", "timeout": 600000 } }, diff --git a/dependency_support/pip_requirements_lock.txt b/dependency_support/pip_requirements_lock.txt index c461d7d542..5cf3f143d9 100644 --- a/dependency_support/pip_requirements_lock.txt +++ b/dependency_support/pip_requirements_lock.txt @@ -14,42 +14,43 @@ click==8.1.3 \ # via # -r dependency_support/pip_requirements.in # flask -cocotb==1.7.2 \ - --hash=sha256:0c1687ac78141724b8529e029ee6299698ecaa8a2c431b744eeff487a4bb18de \ - --hash=sha256:163e5262020cc21f6a0391fb4727c9ab3ecbf6ee12a1472c8f7320b3ba211a50 \ - --hash=sha256:170cf4d01c4d7c6c5b141ffc1824e846a6c8adbed553a50984cd522c1dddb111 \ - --hash=sha256:1851ac56eed7bb6c745aabfc0e417195cb4f08b5df50846c04eb77a868bfeaba \ - --hash=sha256:1abffb36183b07469c490836c66d8b9e24fc1bec7c27356818618a6719fabd4b \ - --hash=sha256:33be79f048f4072240668a079d2bcebd1a24611a0a1e55439b65ffa0ff077790 \ - --hash=sha256:34ab1bf3f18476724dd4e21dbcc0e060e813eb502abe155b800084fb6945360c \ - --hash=sha256:360019f74270661d14e9caa8103e740a070cb466ab08376a565ec0ef4c13dbbf \ - --hash=sha256:37ddb79f4ab60d2d2dc5a9db5bf767d226eb4978fd15b84dfb968d31ab2fcda5 \ - --hash=sha256:43f5af578803e5726b5c75421c0e35e54021ab423d3aa4efe930feb740d6479d \ - --hash=sha256:4738f36b9730cc05b74ccba3648dba0455cf9f237abf822ef307a274a29474c2 \ - --hash=sha256:4aa5d73ebdb59ef24cef36a1f8cca11dcecb3ee7b71a84df02751020bc67ea77 \ - --hash=sha256:574d21501ff1a3d36889397cd58a18d102d0e40391aa7a0274b600d1cc4c7dc3 \ - --hash=sha256:69f4e539dd308c9e169ab23135138ec397061b700f209803a6022ae9fbe08933 \ - --hash=sha256:6f289ac00f4884046ec64db7006e47b1c857a36dcd2a80ea0873cbff00248368 \ - --hash=sha256:707f795a17679b4653a50bd4094536a46fbfee5c6e3d951fac4320ee211ad13f \ - --hash=sha256:7828e22946f128aa59cb9254de4037b99e3bd5a51fe8f590cf64a3141d742a37 \ - --hash=sha256:82f694da656a699154b15ee28be3ac39c41a71d33985313deda12a3645f8b3db \ - --hash=sha256:959892eb94bd0b3ff40e0fca51d33a3936416deb853e2bac4f7f766b40002650 \ - --hash=sha256:a7ec6a2d212c27ec46bed17a15d60b7b29cd0f734f11cc16d2cb4d3f6136e133 \ - --hash=sha256:a90c77f4bbfdf73aa16093dfe95c68af1a1ca685ebfa525f3f150eab252f6728 \ - --hash=sha256:b288a59fa8dffc1cbc53105e71e2f8c82421081b17282e41319832654b309477 \ - --hash=sha256:c41cc8d4ece57f5e26076cd12f1e11d464d7f118fdb74b958269535185d99a30 \ - --hash=sha256:c8dce91d2a918ee63338d79b08e3d52f1d2797efd9c2bedd13c33d674f730db8 \ - --hash=sha256:d26a8a40cea61f295be04b1164a5dd9ec873f13a39814ad00efec7fd899320e0 \ - --hash=sha256:d80b3baafff1a8a91ac860023c448c603767bed502258160a5cb6029976fec4f \ - --hash=sha256:dcf5354268f16d9e11e05cf3616172ca5ef503b45567f75ebd96a0bfdb9832d1 \ - --hash=sha256:ded849360fb31746f1ba3a994f89c3bba2466ec2d0b4b5da0030645645f938d4 \ - --hash=sha256:e03df73573aec261447602904bd66927eeb2f00dd24370dc9a57f47fd42c4d70 \ - --hash=sha256:f97c2eb92cb68831f19b82ba0038ce40fa73c5edbffb7930745edac20c5358d1 \ - --hash=sha256:fa8abed5260baf4306fbfb997c8789fe24bc229cd762b12d7dba0b9c20147b1d - # via - # -r dependency_support/pip_requirements.in - # cocotb-bus - # cocotbext-axi +cocotb==1.9.0 \ + --hash=sha256:02a58ef6c941114964096e7c039bdd4e67e63816cfd2f6a9af6a34cd92b00e8e \ + --hash=sha256:0819794ef5e8fd14fee0b265933226cf600e85edc2f1a749b4d5f8fa2d31ce4e \ + --hash=sha256:0ba35617a677ff65a1273411a3dfdfc5f587128ad8cb9e941ab0eb17ec8fb3e2 \ + --hash=sha256:17556e3a23562f64d577d0eb117fe02e384aedee997b29497b5c395f5010ff82 \ + --hash=sha256:19b4e27b53a16e0b9c4cc5227c7f9d4dccac06e431a4f937e9f5513350196333 \ + --hash=sha256:1a0381ced5590a726032ba2265c6b70ac12cfb49edb152be86a081bb7d104751 \ + --hash=sha256:1aff68cf77059448a9a3278079037e34b50c8c2aee466d984295fa7fe699d390 \ + --hash=sha256:277281420fd6fc3002bb85d6bec497bd20ff3a3905d4b5f1301faf975f750ede \ + --hash=sha256:2daf743320331615f4e8ffb877ab0b04e6f913b911bb11bf9dbc1d876d9c4220 \ + --hash=sha256:2e9bcdbfba3e99c9297bd0d74ba781772d89d2c86e893980784ada252bd1a0f8 \ + --hash=sha256:3058c977f9d4e1f6333d505947f34b9142910719f1d8631c40a151dd86bad727 \ + --hash=sha256:5832d894419a9e8fe5c242e3ac86588e16e2cb379822dcb154bfec8544ae858e \ + --hash=sha256:598b841ed0809e5c64d8c383b8035f6ace5a6f9013f680cdc6981221911c005d \ + --hash=sha256:5a5c91027d7652aaf10e101743edd6b1e832039a19af75fca301275ef30f01d4 \ + --hash=sha256:61418f619af72c8cca8de622785b4f4bfc17ace09981de6eb44feae560cf3bbb \ + --hash=sha256:784c914c8df3fd79cfb148d2bcd17c4b2703c89af1278ed98773afb57ceea3e6 \ + --hash=sha256:87a19d3012f505ba7fda37483b851ef0ca40290ad8a9b28a820b84f8574287bb \ + --hash=sha256:89503f0749362d36b6fab8636710f1848943c21f9d488672921bac21e9edd29f \ + --hash=sha256:89e5189fd393918c27af2daefdcb13df4d52fa761f065d5964d2c4ff5c0642fb \ + --hash=sha256:8cb4b0edf8f0b47c3b604b461cb574fc75fd97efa893cbaf828f4f2f71cf459e \ + --hash=sha256:94e884e16186899ad5b4d131c3f7ff0a2277e67ea0660754e8810a4bbf2d610e \ + --hash=sha256:997dbca2a2cd933fd0a44d9fadeebc1e8a40701db15ea06f207811933dceb350 \ + --hash=sha256:a7cea13cb2fe4f5ca735490846342885117778a73008a67ed9cac667aaaf3f0d \ + --hash=sha256:a84edfbfa57dc6e16845a55feb0b4e1c8b6bbfa5ef1ab6768beba8d81e0546aa \ + --hash=sha256:a95b5e5708a3629d319d2b655d11345cc7e97fea9bdc9bc1df7435926ac30966 \ + --hash=sha256:aa6818c39ca1ce699e4bb1d84899c4f98c2d25c7671bd6c7beee3b1ee9d68834 \ + --hash=sha256:ab99bf7e055780b57419d4133fd4dca9c72a03b766a3e2200552f10498eb8845 \ + --hash=sha256:b966f5560a494fd99f95a1562f9326ca20c35bb118d4e6b50db41da8e4a6f718 \ + --hash=sha256:bc44a7708a5a63d3059a622c2fb90831dc33534c3343e971f5a6c78905097baa \ + --hash=sha256:c11e21d291ba2f889e33c21d76e9aec6ffdfb5666053dc34452666579daa675b \ + --hash=sha256:c848de13583478d71cc91e528e17c051ca6a3b92e89d703ac5015f17cab1287b \ + --hash=sha256:d944aa5509a0f0786d6f30554a2f8b1f229847f9ac9988879d7a05497739f668 \ + --hash=sha256:f50862153e1364f6edeaef9d70505093549fa097e9b2555ea46d1e4f94ac3287 \ + --hash=sha256:f74c598e230e1035103f6e3a97dd7a0e1bcacf7f3ea7481cd3bcde477b74e379 \ + --hash=sha256:fcb81c6c37e11b0729768dd8e192a9cfb809778699ab1fe89f4d92ba0beb3092 \ + --hash=sha256:ff2ddc8b304eb7076ceead2534a1b9828df771798fa9c2601ea983c86d23ec08 cocotb-bus==0.2.1 \ --hash=sha256:a197aa4b0e0ad28469c8877b41b3fb2ec0206da9f491b9276d1578ce6dd8aa8d # via diff --git a/dependency_support/rules_hdl/add_standalone_verilator.patch b/dependency_support/rules_hdl/add_standalone_verilator.patch new file mode 100644 index 0000000000..321ad3f9bd --- /dev/null +++ b/dependency_support/rules_hdl/add_standalone_verilator.patch @@ -0,0 +1,85 @@ +--- dependency_support/verilator/verilator.BUILD.bazel ++++ dependency_support/verilator/verilator.BUILD.bazel +@@ -13,6 +13,7 @@ + # Original implementation by Kevin Kiningham (@kkiningh) in kkiningh/rules_verilator. + # Ported to bazel_rules_hdl by Stephen Tridgell (@stridge-cruxml) + ++load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") + load("@bazel_skylib//rules:write_file.bzl", "write_file") + load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") + load("@rules_hdl//dependency_support/com_github_westes_flex:flex.bzl", "genlex") +@@ -370,3 +371,74 @@ + visibility = ["//visibility:public"], + deps = [":verilator_libV3"], + ) ++ ++genrule( ++ name = "verilated_makefile", ++ srcs = ["include/verilated.mk.in"], ++ outs = ["include/verilated.mk"], ++ cmd = """ ++ cp "$(SRCS)" "$(OUTS)" ++ sed "s%@AR@%$(AR)%" "$(OUTS)" -i ++ sed "s%@CXX@%$(CC)%" "$(OUTS)" -i ++ sed "s%@LINK@%$(CC)%" "$(OUTS)" -i ++ sed "s%@PERL@%$(PERL)%" "$(OUTS)" -i ++ sed "s%@PYTHON3@%$(PYTHON3)%" "$(OUTS)" -i ++ sed "s%@OBJCACHE@%%" "$(OUTS)" -i ++ ++ if [ "$(C_COMPILER)" = "clang" ]; then ++ # Compiler option to put in front of filename to read precompiled header ++ sed "s%@CFG_CXXFLAGS_PCH_I@%-include-pch%" "$(OUTS)" -i ++ # Compiler's filename suffix for precompiled headers, .gch if clang, empty if GCC ++ sed "s%@CFG_GCH_IF_CLANG@%.gch%" "$(OUTS)" -i ++ else ++ sed "s%@CFG_CXXFLAGS_PCH_I@%-include%" "$(OUTS)" -i ++ sed "s%@CFG_GCH_IF_CLANG@%%" "$(OUTS)" -i ++ fi ++ ++ # Select language required to compile ++ sed "s%@CFG_CXXFLAGS_STD@%-x c++%" "$(OUTS)" -i ++ sed "s%@CFG_LDFLAGS_VERILATED@%-lstdc++%" "$(OUTS)" -i ++ ++ # Compiler flags to use to turn off unused and generated code warnings, such as -Wno-div-by-zero "$(OUTS)" -i ++ sed "s%@CFG_CXXFLAGS_NO_UNUSED@%-faligned-new -fcf-protection=none -Wno-bool-operation -Wno-shadow -Wno-sign-compare -Wno-subobject-linkage -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable%" "$(OUTS)" -i ++ # Linker libraries for multithreading ++ sed "s%@CFG_LDLIBS_THREADS@%-pthread -lpthread -latomic%" "$(OUTS)" -i ++ """, ++ toolchains = [ ++ "@bazel_tools//tools/cpp:current_cc_toolchain", ++ "@rules_python//python:current_py_toolchain", ++ "@rules_perl//:current_toolchain", ++ ], ++ visibility = ["//visibility:public"], ++) ++ ++filegroup( ++ name = "includes", ++ srcs = glob(["include/**"]), ++) ++ ++# The perl wrapper assumes that runtime resources are stored under $(realpath verilator)/../ ++# using perl binary would be problematic due to symlinks ++# potential alternative solution: write simplified perl wrapper that does not depend on realpath ++copy_to_bin( ++ name = "verilator_runtime_extras", ++ srcs = [ ++ "bin/verilator", ++ "bin/verilator_includer", ++ ":includes", ++ ], ++) ++ ++filegroup( ++ name = "standalone_verilator", ++ srcs = [ ++ ":verilated_config", ++ ":verilated_makefile", ++ ":verilator_executable", ++ ":verilator_runtime_extras", ++ "@bazel_tools//tools/cpp:current_cc_toolchain", ++ "@rules_perl//:current_toolchain", ++ "@rules_python//python:current_py_toolchain", ++ ], ++ visibility = ["//visibility:public"], ++) diff --git a/dependency_support/rules_hdl/workspace.bzl b/dependency_support/rules_hdl/workspace.bzl index 32a346fcba..34617c4f82 100644 --- a/dependency_support/rules_hdl/workspace.bzl +++ b/dependency_support/rules_hdl/workspace.bzl @@ -40,4 +40,5 @@ def repo(): urls = [ "https://github.com/hdl/bazel_rules_hdl/archive/%s.tar.gz" % git_hash, ], + patches = ["@//dependency_support/rules_hdl:add_standalone_verilator.patch"], ) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 7d125a3d90..e1ed21effc 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1790,6 +1790,7 @@ py_library( "@com_github_alexforencich_verilog_axi//:rtl/priority_encoder.v", "@com_icarus_iverilog//:iverilog", "@com_icarus_iverilog//:vvp", + "@verilator//:standalone_verilator", ], imports = ["."], tags = ["manual"], From dbd02528c755af57ec745c16996de62b1c11add9 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Mon, 29 Dec 2025 09:42:48 +0100 Subject: [PATCH 037/159] modules: zstd: dslx: huffman code builder: encode -> highbit --- xls/modules/zstd/huffman_code_builder.x | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/xls/modules/zstd/huffman_code_builder.x b/xls/modules/zstd/huffman_code_builder.x index 06b057212f..3eb8683508 100644 --- a/xls/modules/zstd/huffman_code_builder.x +++ b/xls/modules/zstd/huffman_code_builder.x @@ -93,6 +93,16 @@ struct WeightCodeBuilderState { huffman_base_codes: uN[MAX_WEIGHT][MAX_WEIGHT + u32:1] } +fn highbit(val: u32) -> u32 { + if val == u32:0 { + // val is zero and thus has no highbit, + // permit it and return some garbage result because it doesn't change anything in the algorithm outcome + u32:0 + } else { + u32:31 - clz(val) + } +} + pub proc WeightCodeBuilder // TODO: enable parametric expresion when they start working //proc WeightCodeBuilder< @@ -254,7 +264,7 @@ pub proc WeightCodeBuilder }; // compute max number of bits - let max_number_of_bits = encode(sum_of_weights_powers >> u32:1) as uN[WEIGHT_LOG]; + let max_number_of_bits = highbit(sum_of_weights_powers as u32 >> u32:1) as uN[WEIGHT_LOG]; let huffman_codes = match(state.fsm, advance_state) { (FSM::IDLE, _) => { From e9928b5fd736f1a76ced40c3b0e3616888a47674 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Mon, 29 Dec 2025 09:42:56 +0100 Subject: [PATCH 038/159] modules: zstd: dslx: fix second-to-last weight handling in huffman weights fse decoding --- xls/modules/zstd/huffman_weights_dec.x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index 86d1a5c7d0..77391ea077 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -804,7 +804,7 @@ pub proc HuffmanFseDecoder< if state.last_weight_is_odd { FSM::SEND_RAM_EVEN_RD_REQ } else { - FSM::SEND_RAM_ODD_RD_REQ + FSM::DECODE_LAST_WEIGHT } } } else { From 9b829812f7dade7ed044995834b11a78dcc0a36f Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Fri, 22 Aug 2025 16:19:53 +0200 Subject: [PATCH 039/159] modules: zstd: fse table creator: report start request --- xls/modules/zstd/fse_table_creator.x | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xls/modules/zstd/fse_table_creator.x b/xls/modules/zstd/fse_table_creator.x index 5e3cdcb5eb..ae9ce4058b 100644 --- a/xls/modules/zstd/fse_table_creator.x +++ b/xls/modules/zstd/fse_table_creator.x @@ -192,6 +192,9 @@ pub proc FseTableCreator< let receive_start = (state.status == Status::RECEIVE_START); let (tok1, fse_start_msg) = recv_if(tok0, fse_table_start_r, receive_start, zero!()); + if receive_start { + trace_fmt!("[FseTableCreator] received start: {}", fse_start_msg); + } else {}; let get_dpd_data = state.status == Status::TEST_NEGATIVE_PROB || state.status == Status::TEST_POSITIVE_PROB || From 711e5483ddd828a454b700c916e8ee18b50bd172 Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Fri, 22 Aug 2025 16:28:04 +0200 Subject: [PATCH 040/159] modules: zstd: fse table creator: fix order of token to shorten dependency paths --- xls/modules/zstd/fse_table_creator.x | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/xls/modules/zstd/fse_table_creator.x b/xls/modules/zstd/fse_table_creator.x index ae9ce4058b..6dcdca635f 100644 --- a/xls/modules/zstd/fse_table_creator.x +++ b/xls/modules/zstd/fse_table_creator.x @@ -240,7 +240,6 @@ pub proc FseTableCreator< data: checked_cast(u16:1), mask: TMP_RAM_REQ_MASK_ALL }); - let (tok5, _) = recv_if(tok5, tmp_wr_resp_r, handle_negative_prob_resp, TestRamWriteResp {}); let handle_positive_prob_write_state_desc = (state.status == Status::HANDLE_POSITIVE_PROB_WRITE_STATE_DESC); let addr = if handle_positive_prob_write_state_desc { @@ -255,7 +254,6 @@ pub proc FseTableCreator< mask: TMP_RAM_REQ_MASK_ALL } ); - let (tok6, _) = recv_if(tok6, tmp_wr_resp_r, handle_positive_prob_write_state_desc, TmpRamWriteResp {}); let inner_for_start_counting = state.status == Status::START_ITERATING_POS; let negative_proba_count = (u16:1 << state.accuracy_log) - state.high_threshold; @@ -285,34 +283,36 @@ pub proc FseTableCreator< let (tok4, _) = recv_if(tok4, tmp2_wr_resp_r, inner_for_write_sym, FseRamWriteResp {}); let last_for = state.status == Status::LAST_FOR; - let tok8 = send_if(tok0, tmp2_rd_req_s, last_for, + // tmp2 read and tmp2 write are sent in different states, so it's safe to use the same token here + let tok_tmp2_rd = send_if(tok0, tmp2_rd_req_s, last_for, Tmp2RamReadReq { addr: checked_cast(state.idx), mask: TMP2_RAM_REQ_MASK_ALL, } ); - let (tok8, fse_resp) = recv_if(tok8, tmp2_rd_resp_r, last_for, zero!()); + let (tok_tmp2_rd, fse_resp) = recv_if(tok_tmp2_rd, tmp2_rd_resp_r, last_for, zero!()); let fse_record_symbol = fse_resp.data; let get_state_desc = state.status == Status::GET_STATE_DESC; let symbol = state.curr_symbol; - let tok8 = send_if(tok8, tmp_rd_req_s, get_state_desc, + let tok_tmp_rd = send_if(tok0, tmp_rd_req_s, get_state_desc, TmpRamReadReq { addr: checked_cast(symbol), mask: TMP_RAM_REQ_MASK_ALL } ); - let (tok8, tmp_resp) = recv_if(tok8, tmp_rd_resp_r, get_state_desc, zero!()); + let (tok_tmp_rd, tmp_resp) = recv_if(tok_tmp_rd, tmp_rd_resp_r, get_state_desc, zero!()); let set_state_desc = state.status == Status::SET_STATE_DESC; - let tok9 = send_if(tok8, tmp_wr_req_s, set_state_desc, + let tok_tmp_wr = send_if(tok_tmp_rd, tmp_wr_req_s, set_state_desc, TmpRamWriteReq { addr: checked_cast(symbol), data: checked_cast(state.state_desc_for_symbol + u16:1), mask: TMP_RAM_REQ_MASK_ALL } ); - let (tok9, _) = recv_if(tok9, tmp_wr_resp_r, set_state_desc, TmpRamWriteResp {}); + + let (_, _) = recv_if(tok_tmp_wr, tmp_wr_resp_r, set_state_desc || handle_positive_prob_write_state_desc || handle_negative_prob_resp, TestRamWriteResp {}); let num_bits = state.accuracy_log - common::highest_set_bit(state.state_desc_for_symbol); let size = u16:1 << state.accuracy_log; @@ -330,8 +330,8 @@ pub proc FseTableCreator< data: checked_cast(complete_record_as_bits), mask: FSE_RAM_REQ_MASK_ALL }; - let tok10 = send_if(tok8, fse_wr_req_s, set_state_desc, fse_wr_req); - let (tok10, _) = recv_if(tok10, fse_wr_resp_r, set_state_desc, FseRamWriteResp {}); + let tok_fse_wr = send_if(tok0, fse_wr_req_s, set_state_desc, fse_wr_req); + let (tok_fse_wr, _) = recv_if(tok_fse_wr, fse_wr_resp_r, set_state_desc, FseRamWriteResp {}); let send_finish = state.status == Status::SEND_FINISH; let tok11 = send_if(tok0, fse_table_finish_s, send_finish, ()); From 2f0e1ac7760daa1ae09e2e6df91801eee79517ad Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Fri, 22 Aug 2025 16:13:58 +0200 Subject: [PATCH 041/159] modules: zstd: cocotb: report work time for various components --- xls/modules/zstd/zstd_dec_cocotb_common.py | 60 +++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index a820c65542..03d26d0f29 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -20,7 +20,7 @@ import cocotb from cocotb import triggers from cocotb.clock import Clock -from cocotb.triggers import RisingEdge, ClockCycles, Event +from cocotb.triggers import RisingEdge, ClockCycles, Event, Edge from cocotb.binary import BinaryValue from cocotb.utils import get_sim_time from cocotb_bus.scoreboard import Scoreboard @@ -413,6 +413,61 @@ def func(): cocotb.start_soon(get_handshake_event(dut, fse_lookup_resp_handshake, func)) +@cocotb.coroutine +async def await_state_cycle(clk, wire, func, startvals, endvals): + """Monitors a state signal and reports elapsed cycles between start and end states. + + This will continously observe a given state signal and after any of startvals, + and then endvals is matched with the value, report the elapsed cycles. + + For procs that have an FSM, use proper start state (the one used after IDLE) in startvals, + and a state that corresponds to sending the result back, or switching back to IDLE in endvals. + + Args: + wire: state signal to observe + func (Callable[[int], None]): Callback function to report the elapsed cycles + startvals (Iterable): values that define the start states + endvals (Iterable): values that define the end states + """ + while True: + while wire.value not in startvals: + await Edge(wire) + start = get_clock_time(clk) + while wire.value not in endvals: + await Edge(wire) + end = get_clock_time(clk) + func(end - start) + +async def report_fse_decoder_work(dut, clk): + fse_state = dut.ZstdDecoder.xls_modules_zstd_fse_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__FseDecoder_0__64_15_32_1_64_7_next_inst10.p2_____state_0__1 + start_states = [1] + end_states = [16] + + def report(value): + print(f'FSE Decoder finished after {value} cycles') + + cocotb.start_soon(await_state_cycle(clk, fse_state, report, start_states, end_states)) + +async def report_sequence_executor_work(dut, clk): + state = dut.ZstdDecoder.xls_modules_zstd_sequence_executor__ZstdDecoderInst__ZstdDecoder_0__SequenceExecutor_0__32_64_64_0_0_0_13_8192_65536_next_inst149.p2_____state_0__1 + start_states = [1, 2] + end_states = [0] + + def report(value): + print(f'Sequence executor finished after {value} cycles') + + cocotb.start_soon(await_state_cycle(clk, state, report, start_states, end_states)) + +async def report_fse_table_creator_work(dut, clk): + state = dut.ZstdDecoder.xls_modules_zstd_fse_table_creator__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__FseLookupDecoder_0__CompLookupDecoder_0__FseTableCreator_0__8_16_1_15_32_1_9_8_1_8_16_1_next_inst15.p3_____state_0__1 + start_states = [1] + end_states = [11] + + def report(value): + print(f'FSE table creator finished after {value} cycles') + + cocotb.start_soon(await_state_cycle(clk, state, report, start_states, end_states)) + def reverse_expected_huffman_codes(exp_codes): def reverse_bits(value, max_bits): bv = BinaryValue(value=value, n_bits=max_bits, bigEndian=False) @@ -641,6 +696,9 @@ async def pregenerated_testing_routine( print( f"\nusing pregenerated input file for decoder: {pregenerated_path}\n" ) + await report_fse_decoder_work(dut, clock) + await report_sequence_executor_work(dut, clock) + await report_fse_table_creator_work(dut, clock) if expected_fse_lookups is not None: await test_fse_lookup_decoder(dut, clock, expected_fse_lookups) From 3cc2c62466e0fc455ca5afb4cb7d40daa3188b87 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 2 Sep 2025 14:52:31 +0200 Subject: [PATCH 042/159] modules: zstd: cocotb: update signal names --- xls/modules/zstd/zstd_dec_cocotb_common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 03d26d0f29..304d39790e 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -346,7 +346,7 @@ def prepare_test_environment(dut): async def test_fse_lookup_decoder(dut, clock, expected_fse_lookups): lookup_dec_resp_channel = xlschannel.XLSChannel( - dut.ZstdDecoder.xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next_inst148, + dut.ZstdDecoder.xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next_inst149, "zstd_dec__flc_resp", dut.clk, ) @@ -449,7 +449,7 @@ def report(value): cocotb.start_soon(await_state_cycle(clk, fse_state, report, start_states, end_states)) async def report_sequence_executor_work(dut, clk): - state = dut.ZstdDecoder.xls_modules_zstd_sequence_executor__ZstdDecoderInst__ZstdDecoder_0__SequenceExecutor_0__32_64_64_0_0_0_13_8192_65536_next_inst149.p2_____state_0__1 + state = dut.ZstdDecoder.xls_modules_zstd_sequence_executor__ZstdDecoderInst__ZstdDecoder_0__SequenceExecutor_0__32_64_64_0_0_0_13_8192_65536_next_inst150.p2_____state_0__1 start_states = [1, 2] end_states = [0] @@ -459,7 +459,7 @@ def report(value): cocotb.start_soon(await_state_cycle(clk, state, report, start_states, end_states)) async def report_fse_table_creator_work(dut, clk): - state = dut.ZstdDecoder.xls_modules_zstd_fse_table_creator__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__FseLookupDecoder_0__CompLookupDecoder_0__FseTableCreator_0__8_16_1_15_32_1_9_8_1_8_16_1_next_inst15.p3_____state_0__1 + state = dut.ZstdDecoder.xls_modules_zstd_fse_table_creator__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__FseLookupDecoder_0__CompLookupDecoder_0__FseTableCreator_0__8_16_1_15_32_1_9_8_1_8_16_1_next_inst16.p3_____state_0__1 start_states = [1] end_states = [11] From 4948aa6fd205b27275629976b8c0dc7f57d3fb0d Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Fri, 5 Sep 2025 09:50:26 +0200 Subject: [PATCH 043/159] modules: zstd: update test files with max window size of 61440 --- xls/modules/zstd/data/comp_frame.zst | Bin 51 -> 51 bytes xls/modules/zstd/data/comp_frame_fse_comp.zst | Bin 66 -> 66 bytes xls/modules/zstd/data/comp_frame_huffman.zst | Bin 93 -> 93 bytes .../zstd/data/comp_frame_huffman_fse.zst | Bin 64 -> 64 bytes ...iterals_predefined_sequences_seed_299289.zst | Bin 60 -> 60 bytes ...iterals_predefined_sequences_seed_319146.zst | Bin 125 -> 125 bytes .../data/pregenerated_compressed_random_1.zst | Bin 505 -> 505 bytes .../data/pregenerated_compressed_random_2.zst | Bin 120 -> 120 bytes .../zstd/data/pregenerated_compressed_raw_1.zst | Bin 975 -> 975 bytes .../zstd/data/pregenerated_compressed_raw_2.zst | Bin 193 -> 193 bytes .../zstd/data/pregenerated_compressed_rle_1.zst | Bin 281 -> 281 bytes .../zstd/data/pregenerated_compressed_rle_2.zst | Bin 61 -> 61 bytes .../raw_literals_rle_sequences_seed_700216.zst | Bin 46 -> 46 bytes ...iterals_predefined_sequences_seed_436165.zst | Bin 38 -> 38 bytes .../data/rle_literals_rle_sequences_seed_2.zst | Bin 121 -> 121 bytes ...iterals_predefined_sequences_seed_408158.zst | Bin 93 -> 93 bytes ...ffman_literals_rle_sequences_seed_403927.zst | Bin 137 -> 137 bytes 17 files changed, 0 insertions(+), 0 deletions(-) diff --git a/xls/modules/zstd/data/comp_frame.zst b/xls/modules/zstd/data/comp_frame.zst index 800a7bdca91d4fbddaf33411a2402d7ef59ec7df..27ae5c886796d5e7f8b1d2f1adcbaf532e9c24c3 100644 GIT binary patch delta 12 TcmXpuX4BZJ|F=bdBAYG%7+?eK delta 12 TcmXpuX4BZJ|F^|`BAYG%7{CMt diff --git a/xls/modules/zstd/data/comp_frame_fse_comp.zst b/xls/modules/zstd/data/comp_frame_fse_comp.zst index f9cf4a5d1fee7bfb26a6a87f5549c28e44cf8b21..6a16ad5e5c227c108129130c9342b23429f5c443 100644 GIT binary patch delta 12 TcmZ>AV$;~F|F=bdBAX2W8OQ`0 delta 12 TcmZ>AV$;~F|F8VAI&D|F=bdBAXQe8JGkV delta 12 TcmZ>8VAI&D|F^||BAXQe8W;pL diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.zst index c19b07faf2a3950c60c7f74bab83753da37a2206..290a0209b6a67afe7168912a4b65aec815c0aaee 100644 GIT binary patch delta 12 TcmcDqVbj>E|F=bdBAXci88`$8 delta 12 TcmcDqVbj>E|F^|)BAXci8Gr;D diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.zst index f5b2d6c1575257a12d957e80f41279d3f037b442..418a99be2fb638739cde7131a0a9859c7218a6a6 100644 GIT binary patch delta 12 Tcmb=eWz*QI|M!UgM7DAOAdm$M delta 12 Tcmb=eWz*QI|M!UZM7DAOApiv@ diff --git a/xls/modules/zstd/data/pregenerated_compressed_random_1.zst b/xls/modules/zstd/data/pregenerated_compressed_random_1.zst index 8673b024e095702f99cd17a64ed181f5061b80f9..70650ba81a4c84d7e81bcda6e5eaecac692b4189 100644 GIT binary patch delta 15 Xcmey#{F9kYW2^q(Bl;WJJ~ILUHfsi^ delta 15 Xcmey#{F9kYW2^q(BaR!{J~ILUHpK?G diff --git a/xls/modules/zstd/data/pregenerated_compressed_random_2.zst b/xls/modules/zstd/data/pregenerated_compressed_random_2.zst index 014359109f9ab7dcc6a82d125f26aad14fcef42c..5e5bc05a935d70ca3db27d2bc3714b0e1b8ba579 100644 GIT binary patch delta 12 Tcmb=ZVAI&D|F=bdB3lsv9@qrD delta 12 Tcmb=ZVAI&D|F^|#B3lsv9`ppm diff --git a/xls/modules/zstd/data/pregenerated_compressed_raw_1.zst b/xls/modules/zstd/data/pregenerated_compressed_raw_1.zst index bea909ee30ae7aae91212d16fe49be9a48dd2ede..51d682f2ba7ef4d21c23b67f7d8b52c8c274d03c 100644 GIT binary patch delta 15 XcmX@lex998W2^q(Bl;WJPBH@kG3f>} delta 15 XcmX@lex998W2^q(BaR!{PBH@kGD8ML diff --git a/xls/modules/zstd/data/pregenerated_compressed_raw_2.zst b/xls/modules/zstd/data/pregenerated_compressed_raw_2.zst index 9d5d81af90d3366cd68a2de885fac7a7c69926aa..e5dba4e8de97a779a3595116dd4128aba477777a 100644 GIT binary patch delta 14 VcmX@ec#x4zW2^q(7X68Ay8$Q-1(^T< delta 14 VcmX@ec#x4zW2^q(7PE diff --git a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.zst b/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.zst index ddb555d1ec10bedcdae94e207d616c656badb7e6..e70a6a3c279df92c406a179431ac188930656caa 100644 GIT binary patch delta 12 Tcma!zWz*QI|M!UgM7D4M9gqa8 delta 12 Tcma!zWz*QI|M!UTM7D4M9iRlS diff --git a/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.zst b/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.zst index d5cfe6c706ba9b984dbb80eb547409ab0c657773..da9ed3c95a43933b3b6cd9996590c58c67dccbb5 100644 GIT binary patch delta 14 VcmeBV>||uq*sA}xMSmh&GXNqb1n~d> delta 14 VcmeBV>||uq*sA}x#cm>7GXNrC1poj5 From bfe1481c6004362f5a506ec677c5b42b131f5fa1 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Fri, 5 Sep 2025 11:44:39 +0200 Subject: [PATCH 044/159] modules: zstd: add patch for specifying max window log for decodecorpus generated data --- .../allow-specifying-max-window-log.patch | 68 +++++++++++++++++++ dependency_support/load_external.bzl | 4 ++ xls/modules/zstd/cocotb/data_generator.py | 5 ++ xls/modules/zstd/zstd_dec_cocotb_test.py | 2 + 4 files changed, 79 insertions(+) create mode 100644 dependency_support/com_github_facebook_zstd/allow-specifying-max-window-log.patch diff --git a/dependency_support/com_github_facebook_zstd/allow-specifying-max-window-log.patch b/dependency_support/com_github_facebook_zstd/allow-specifying-max-window-log.patch new file mode 100644 index 0000000000..988d501b37 --- /dev/null +++ b/dependency_support/com_github_facebook_zstd/allow-specifying-max-window-log.patch @@ -0,0 +1,68 @@ +From c052badd17fdb6751f3b5820ab6f7eab0cab196e Mon Sep 17 00:00:00 2001 +From: Mateusz Gancarz +Date: Fri, 5 Sep 2025 11:11:34 +0200 +Subject: [PATCH] decodecorpus: allow specifying max window log + +--- + tests/decodecorpus.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/tests/decodecorpus.c b/tests/decodecorpus.c +index f25e5dc7..085f7be2 100644 +--- a/tests/decodecorpus.c ++++ b/tests/decodecorpus.c +@@ -163,6 +163,7 @@ static double RAND_exp(U32* seed, double mean) + const char* BLOCK_TYPES[] = {"raw", "rle", "compressed"}; + + #define MAX_DECOMPRESSED_SIZE_LOG 20 ++#define MIN_WINDOW_LOG 10 + #define MAX_DECOMPRESSED_SIZE (1ULL << MAX_DECOMPRESSED_SIZE_LOG) + + #define MAX_WINDOW_LOG 22 /* Recommended support is 8MB, so limit to 4MB + mantissa */ +@@ -257,6 +258,7 @@ typedef enum { + * Global variables (set from command line) + *********************************************************/ + U32 g_maxDecompressedSizeLog = MAX_DECOMPRESSED_SIZE_LOG; /* <= 20 */ ++U32 g_maxWindowLog = MAX_WINDOW_LOG; /* 10 <= window log <= 22 */ + U32 g_maxBlockSize = ZSTD_BLOCKSIZE_MAX; /* <= 128 KB */ + + /*-******************************************************* +@@ -289,7 +291,7 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) + /* generate window size */ + { + /* Follow window algorithm from specification */ +- int const exponent = RAND(seed) % (MAX_WINDOW_LOG - 10); ++ int const exponent = g_maxWindowLog > 10 ? RAND(seed) % (g_maxWindowLog - 10) : 0; + int const mantissa = RAND(seed) % 8; + windowByte = (BYTE) ((exponent << 3) | mantissa); + fh.windowSize = (1U << (exponent + 10)); +@@ -1231,7 +1233,7 @@ static U32 generateCompressedBlock(U32 seed, frame_t* frame, dictInfo info) + while (!blockWritten) { + size_t cSize; + /* generate window size */ +- { int const exponent = RAND(&seed) % (MAX_WINDOW_LOG - 10); ++ { int const exponent = g_maxWindowLog > 10 ? RAND(&seed) % (g_maxWindowLog - 10) : 0; + int const mantissa = RAND(&seed) % 8; + frame->header.windowSize = (1U << (exponent + 10)); + frame->header.windowSize += (frame->header.windowSize / 8) * mantissa; +@@ -1807,6 +1809,7 @@ static void advancedUsage(const char* programName) + DISPLAY( " --max-block-size-log=# : max block size log, must be in range [2, 17]\n"); + DISPLAY( " --max-content-size-log=# : max content size log, must be <= 20\n"); + DISPLAY( " (this is ignored with gen-blocks)\n"); ++ DISPLAY( " --max-window-log=# : max window log, must be in range [10, 22]\n"); + DISPLAY( " --block-type=# : force certain block type (raw=0, rle=1, compressed=2)\n"); + DISPLAY( " --frame-header-only : dump only frame header\n"); + DISPLAY( " --no-magic : do not add magic number\n"); +@@ -1931,6 +1934,9 @@ int main(int argc, char** argv) + U32 value = readU32FromChar(&argument); + g_maxDecompressedSizeLog = + MIN(MAX_DECOMPRESSED_SIZE_LOG, value); ++ } else if (longCommandWArg(&argument, "max-window-log=")) { ++ U32 value = readU32FromChar(&argument); ++ g_maxWindowLog = MIN(MAX_WINDOW_LOG, MAX(MIN_WINDOW_LOG, value)); + } else if (longCommandWArg(&argument, "block-type=")) { + U32 value = readU32FromChar(&argument); + opts.blockType = malloc(sizeof(blockType_e)); +-- +2.39.5 + diff --git a/dependency_support/load_external.bzl b/dependency_support/load_external.bzl index 91467b3d9a..f68a46413c 100644 --- a/dependency_support/load_external.bzl +++ b/dependency_support/load_external.bzl @@ -76,6 +76,10 @@ def load_external_repositories(): http_archive( name = "zstd", sha256 = "9ace5a1b3c477048c6e034fe88d2abb5d1402ced199cae8e9eef32fdc32204df", + patches = [ + Label("@//dependency_support/com_github_facebook_zstd:allow-specifying-max-window-log.patch"), + ], + patch_args = ["-p1"], strip_prefix = "zstd-fdfb2aff39dc498372d8c9e5f2330b692fea9794", urls = ["https://github.com/facebook/zstd/archive/fdfb2aff39dc498372d8c9e5f2330b692fea9794.zip"], build_file = Label("//dependency_support/com_github_facebook_zstd:bundled.BUILD.bazel"), diff --git a/xls/modules/zstd/cocotb/data_generator.py b/xls/modules/zstd/cocotb/data_generator.py index d0c5cc0495..592f930771 100644 --- a/xls/modules/zstd/cocotb/data_generator.py +++ b/xls/modules/zstd/cocotb/data_generator.py @@ -90,6 +90,11 @@ def GenerateFrame(seed, btype, output_path, ltype=LiteralType.RANDOM): args.append("--content-size") # Test payloads up to 16KB args.append("--max-content-size-log=14") + # Window log 15 is our maximum allowed value, + # as then the max window size is equal to 61 440 bytes + # which fits into the zstd decoder history buffer + # with size of 65 536 bytes. + args.append("--max-window-log=15") args.append("-p" + output_path) args.append("-vvvvvvv") diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index 16923db637..1b9f9fcea9 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -549,6 +549,8 @@ async def test_decoder( # Generate ZSTD frame to temporary file data_generator.GenerateFrame(seed, block_type, encoded.name, literal_type) + assert check_decoder_compliance(encoded.name), (f"error: '{encoded.name}' is not suitable for the decoder parameters") + print( "\nusing" + (" pregenerated" if pregenerated else f" randomly generated (seed={seed})") From cd9d365ee1f7b09badb15507e60cdfb7991b56fd Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Wed, 3 Sep 2025 13:46:04 +0200 Subject: [PATCH 045/159] modules: zstd: cocotb: add an early check for required window size --- xls/modules/zstd/zstd_dec_cocotb_cli.py | 6 +++- xls/modules/zstd/zstd_dec_cocotb_common.py | 41 ++++++++++++++++++++++ xls/modules/zstd/zstd_dec_cocotb_test.py | 1 + 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_cli.py b/xls/modules/zstd/zstd_dec_cocotb_cli.py index 847dd31e1c..8da8bc3d82 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_cli.py +++ b/xls/modules/zstd/zstd_dec_cocotb_cli.py @@ -17,7 +17,7 @@ import cocotb import os import pathlib -from xls.modules.zstd.zstd_dec_cocotb_common import pregenerated_testing_routine, run_test +from xls.modules.zstd.zstd_dec_cocotb_common import pregenerated_testing_routine, run_test, check_decoder_compliance from multiprocessing import cpu_count @cocotb.test(timeout_time=int(os.getenv("ZSTD_DEC_COCOTB_CLI_TIMEOUT", "5000")), timeout_unit="ms") @@ -40,6 +40,10 @@ def usage(): # bazel run changes the working dir to runfiles tree so relative paths won't work print(f"error: '{sys.argv[1]}' is not absolute path") usage() + + if not check_decoder_compliance(sys.argv[1]): + print(f"error: '{sys.argv[1]}' is not suitable for the decoder parameters") + sys.exit(1) # cocotb doesn't perserve global vars nor sys.argv # we work it around by passing arguments through env diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 304d39790e..b8843a0186 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -147,6 +147,45 @@ class Status(enum.Enum): RUNNING = 0x1 +def check_decoder_compliance(file_path): + with open(file_path, mode='rb') as compressed_file: + # Unused magic number + compressed_file.seek(4) + + frame_header_descriptor = int.from_bytes(compressed_file.read(1)) + + # 6th bit of a frame header indicates if a window descriptor is present + SINGLE_SEGMENT_FLAG_MASK = 0b100000 + if frame_header_descriptor & SINGLE_SEGMENT_FLAG_MASK == 0: + # Calculate a required window_size for the encoded file + # and compare it with the ZSTD decoder history buffer size. + # Based on window_size calculation from: RFC 8878 + # https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor + window_descriptor = int.from_bytes(compressed_file.read(1)) + + MANTISSA_BITS = 0b111 + mantissa = window_descriptor & MANTISSA_BITS + + EXPONENT_BITS = 0b11111000 + exponent = (window_descriptor & EXPONENT_BITS) >> 3 + + window_log = 10 + exponent + window_base = 1 << window_log + window_add = (window_base / 8) * mantissa + window_size = int(window_base + window_add) + + # This value represent the size of the history buffer for the tested + # instance of the ZSTD decoder, and should be in line with the value + # of `INST_HB_SIZE_KB` declared in the zstd_dec.x file. + HISTORY_BUFFER_SIZE = 64 * 1024 # 64 KB + + if window_size > HISTORY_BUFFER_SIZE: + print(f"error: required window size greater than the actual history buffer: {window_size} > {HISTORY_BUFFER_SIZE}") + return False + + return True + + def check_ram_contents(mem, expected, name=""): for i, value in enumerate(expected): assert mem[i].value == value @@ -576,6 +615,8 @@ async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): The output of the decoder is compared with the output of decodercorpus using the same input file. """ + assert check_decoder_compliance(encoded_file.name), (f"error: '{encoded_file.name}' is not suitable for the decoder parameters") + memory_bus = axi_buses["memory"] (unused_notify_channel, notify_monitor) = connect_xls_channel( diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index 1b9f9fcea9..850e49d172 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -55,6 +55,7 @@ from xls.modules.zstd.cocotb.memory import AxiRamFromFile from xls.modules.zstd.cocotb.utils import run_test from xls.modules.zstd.cocotb import xlsstruct +from xls.modules.zstd.zstd_dec_cocotb_common import check_decoder_compliance AXI_DATA_W = 64 AXI_DATA_W_BYTES = AXI_DATA_W // 8 From a4f0bd42f4640136253af05257f2394b233e3052 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Mon, 14 Jul 2025 17:03:32 +0200 Subject: [PATCH 046/159] modules: zstd: cocotb: enable SystemVerilog asserts in zstd_dec_cocotb_cli NOTE: we only enable them in cli which is simulated in verilator, as iverilog does not support concurent asserts --- xls/modules/zstd/BUILD | 6 +++++- xls/modules/zstd/cocotb/utils.py | 5 +++++ xls/modules/zstd/zstd_dec_cocotb_cli.py | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index e1ed21effc..177ee41650 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1640,7 +1640,11 @@ ZSTD_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { xls_dslx_verilog( name = "zstd_dec_verilog", - codegen_args = ZSTD_DEC_CODEGEN_ARGS, + codegen_args = ZSTD_DEC_CODEGEN_ARGS | { + # enable asserts without turning whole SystemVerilog codegen on (as it causes issues with yosys and iverilog) + # NOTE: to avoid duplicated labels, we use them in errmsg rather than as actual verilog labels + "assert_format": "'assert property (@(posedge {clk}) disable iff ({rst}) {condition}) else $fatal(0, \"{message} (label: {label})\");'", + }, dslx_top = "ZstdDecoderInst", library = ":zstd_dec_dslx", tags = ["manual"], diff --git a/xls/modules/zstd/cocotb/utils.py b/xls/modules/zstd/cocotb/utils.py index 6b0abf64d0..a9f72e9b92 100644 --- a/xls/modules/zstd/cocotb/utils.py +++ b/xls/modules/zstd/cocotb/utils.py @@ -72,6 +72,11 @@ def run_test(toplevel, test_module, verilog_sources, timescale=("1ns", "1ps"), s build_dir = setup_verilator() if sim == "verilator" else setup_com_iverilog() runner = get_runner(sim) + if sim == "verilator": + defines = {"SIMULATION": "1", "ASSERT_ON": "1"} + else: # icarus does not support concurent assertion generated by xls + defines = {"SIMULATION": "1"} + runner.build( verilog_sources=verilog_sources, hdl_toplevel=toplevel, diff --git a/xls/modules/zstd/zstd_dec_cocotb_cli.py b/xls/modules/zstd/zstd_dec_cocotb_cli.py index 8da8bc3d82..f25b4ec3e6 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_cli.py +++ b/xls/modules/zstd/zstd_dec_cocotb_cli.py @@ -59,5 +59,6 @@ def usage(): "--trace-fst", # trace in more space-efficient format than vcd "--trace-threads", "2", # 2 is maximum "-O3", - "--threads", str(cpu_count() - 2) + "--threads", str(cpu_count() - 2), + "--assert", ], sim="verilator") From af76a71792a15adfc5e3cad8f512f9cde0ff759d Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Fri, 12 Sep 2025 12:41:48 +0200 Subject: [PATCH 047/159] modules: zstd: dec mux: bump MAX_ID The MAX_ID was set too low (ids can get bigger than that) resulting in failing assert --- xls/modules/zstd/dec_mux.x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/dec_mux.x b/xls/modules/zstd/dec_mux.x index 681b1b402d..bbd65c362f 100644 --- a/xls/modules/zstd/dec_mux.x +++ b/xls/modules/zstd/dec_mux.x @@ -28,7 +28,7 @@ type CopyOrMatchLength = common::CopyOrMatchLength; type SequenceExecutorMessageType = common::SequenceExecutorMessageType; type SequenceExecutorPacket = common::SequenceExecutorPacket; -const MAX_ID = common::DATA_WIDTH; +const MAX_ID = all_ones!(); const DATA_WIDTH = common::DATA_WIDTH; struct DecoderMuxState { From 27c0a3618b969983e0ab95f36d52162e245cfe41 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Fri, 5 Sep 2025 12:55:11 +0200 Subject: [PATCH 048/159] modules: zstd: cocotb: use zstd_dec_cocotb_common in zstd_dec_cocotb_test --- xls/modules/zstd/zstd_dec_cocotb_test.py | 882 ++--------------------- 1 file changed, 53 insertions(+), 829 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index 850e49d172..9b6f260730 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -12,624 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys -import math -import enum -import pathlib -import tempfile - import cocotb -from cocotb import triggers -from cocotb.clock import Clock -from cocotb.triggers import RisingEdge, ClockCycles, Event -from cocotb.binary import BinaryValue -from cocotb.utils import get_sim_time -from cocotb_bus.scoreboard import Scoreboard - -from cocotbext.axi import axi_channels -from cocotbext.axi.axi_master import AxiMaster -from cocotbext.axi.axi_channels import ( - AxiAWBus, - AxiWBus, - AxiWMonitor, - AxiBBus, - AxiWriteBus, - AxiARBus, - AxiRBus, - AxiReadBus, - AxiBus, - AxiBTransaction, - AxiBSource, - AxiBSink, - AxiBMonitor, - AxiRTransaction, - AxiRSource, - AxiRSink, - AxiRMonitor, -) -from cocotbext.axi.axi_ram import AxiRam -from cocotbext.axi.sparse_memory import SparseMemory - -import xls.modules.zstd.cocotb.channel as xlschannel +import pathlib from xls.modules.zstd.cocotb import data_generator -from xls.modules.zstd.cocotb.memory import AxiRamFromFile -from xls.modules.zstd.cocotb.utils import run_test -from xls.modules.zstd.cocotb import xlsstruct -from xls.modules.zstd.zstd_dec_cocotb_common import check_decoder_compliance - -AXI_DATA_W = 64 -AXI_DATA_W_BYTES = AXI_DATA_W // 8 -MAX_ENCODED_FRAME_SIZE_B = 2**32 -NOTIFY_CHANNEL = "notify" -RESET_CHANNEL = "reset" - -# Override default widths of AXI response signals -signal_widths = {"bresp": 3} -axi_channels.AxiBBus._signal_widths = signal_widths -axi_channels.AxiBTransaction._signal_widths = signal_widths -axi_channels.AxiBSource._signal_widths = signal_widths -axi_channels.AxiBSink._signal_widths = signal_widths -axi_channels.AxiBMonitor._signal_widths = signal_widths -signal_widths = {"rresp": 3, "rlast": 1} -axi_channels.AxiRBus._signal_widths = signal_widths -axi_channels.AxiRTransaction._signal_widths = signal_widths -axi_channels.AxiRSource._signal_widths = signal_widths -axi_channels.AxiRSink._signal_widths = signal_widths -axi_channels.AxiRMonitor._signal_widths = signal_widths - - -@xlsstruct.xls_dataclass -class NotifyStruct(xlsstruct.XLSStruct): - pass - - -SYMBOL_W = 8 -NUM_OF_BITS_W = 8 -BASE_W = 16 - - -@xlsstruct.xls_dataclass -class FseTableRecord(xlsstruct.XLSStruct): - base: BASE_W - num_of_bits: NUM_OF_BITS_W - symbol: SYMBOL_W - - -PARALLEL_ACCESS_WIDTH = 8 -MAX_WEIGHT = 11 -WEIGHT_LOG = math.ceil(math.log2(MAX_WEIGHT + 1)) -VALID_W = 1 - - -@xlsstruct.xls_dataclass -class CodeBuilderOutput(xlsstruct.XLSStruct): - symbol_valid_7: VALID_W - symbol_valid_6: VALID_W - symbol_valid_5: VALID_W - symbol_valid_4: VALID_W - symbol_valid_3: VALID_W - symbol_valid_2: VALID_W - symbol_valid_1: VALID_W - symbol_valid_0: VALID_W - code_length_7: WEIGHT_LOG - code_length_6: WEIGHT_LOG - code_length_5: WEIGHT_LOG - code_length_4: WEIGHT_LOG - code_length_3: WEIGHT_LOG - code_length_2: WEIGHT_LOG - code_length_1: WEIGHT_LOG - code_length_0: WEIGHT_LOG - code_7: MAX_WEIGHT - code_6: MAX_WEIGHT - code_5: MAX_WEIGHT - code_4: MAX_WEIGHT - code_3: MAX_WEIGHT - code_2: MAX_WEIGHT - code_1: MAX_WEIGHT - code_0: MAX_WEIGHT - - -class CSR(enum.Enum): - """ - Maps the offsets to the ZSTD Decoder registers. - """ - - STATUS = 0 - START = 1 - INPUTBUFFER = 2 - OUTPUTBUFFER = 3 - - -class Status(enum.Enum): - """ - Codes for the Status register. - """ - - IDLE = 0x0 - RUNNING = 0x1 - - -def check_ram_contents(mem, expected, name=""): - for i, value in enumerate(expected): - assert mem[i].value == value - - -def print_fse_ram_contents(mem, name="", size=None): - for i in range(size): - data = FseTableRecord.from_int(mem[i].value) - print(f"{name} [{i}]: {data}") - - -def print_ram_contents(mem, name="", size=None): - for i in range(size): - print(f"{name} [{i}]\t: {hex(mem[i].value)}") - - -def fields_as_array(data, prefix, count): - return [getattr(data, f"{prefix}_{i}") for i in range(count)] - - -def set_termination_event(monitor, event, transactions): - def terminate_cb(_): - if monitor.stats.received_transactions == transactions: - event.set() - - monitor.add_callback(terminate_cb) - - -@cocotb.coroutine -async def set_handshake_event(clk, channel, event): - while True: - await RisingEdge(clk) - if channel.rdy.value and channel.vld.value: - event.set() - - -@cocotb.coroutine -async def get_handshake_event(dut, event, func): - while True: - await event.wait() - func() - event.clear() - - -def connect_axi_read_bus(dut, name=""): - axi_ar = "axi_ar" - axi_r = "axi_r" - - if name: - name += "_" - - bus_axi_ar = axi_channels.AxiARBus.from_prefix(dut, name + axi_ar) - bus_axi_r = axi_channels.AxiRBus.from_prefix(dut, name + axi_r) - - return axi_channels.AxiReadBus(bus_axi_ar, bus_axi_r) - - -def connect_axi_write_bus(dut, name=""): - axi_aw = "axi_aw" - axi_w = "axi_w" - axi_b = "axi_b" - - if name: - name += "_" - - bus_axi_aw = axi_channels.AxiAWBus.from_prefix(dut, name + axi_aw) - bus_axi_w = axi_channels.AxiWBus.from_prefix(dut, name + axi_w) - bus_axi_b = axi_channels.AxiBBus.from_prefix(dut, name + axi_b) - - return axi_channels.AxiWriteBus(bus_axi_aw, bus_axi_w, bus_axi_b) - - -def connect_axi_bus(dut, name=""): - bus_axi_read = connect_axi_read_bus(dut, name) - bus_axi_write = connect_axi_write_bus(dut, name) - - return axi_channels.AxiBus(bus_axi_write, bus_axi_read) - - -async def csr_write(cpu, csr, data): - if isinstance(data, int): - data = data.to_bytes(AXI_DATA_W_BYTES, byteorder="little") - assert len(data) <= AXI_DATA_W_BYTES - await cpu.write(csr.value * AXI_DATA_W_BYTES, data) - - -async def csr_read(cpu, csr): - return await cpu.read(csr.value * AXI_DATA_W_BYTES, AXI_DATA_W_BYTES) - - -async def test_csr(dut): - - clock = Clock(dut.clk, 10, units="us") - cocotb.start_soon(clock.start()) - - await reset_dut(dut, 50) - - csr_bus = connect_axi_bus(dut, "csr") - - cpu = AxiMaster(csr_bus, dut.clk, dut.rst) - - await triggers.ClockCycles(dut.clk, 10) - i = 0 - for reg in CSR: - expected_src = bytearray.fromhex("0DF0AD8BEFBEADDE") - assert len(expected_src) >= AXI_DATA_W_BYTES - expected = expected_src[-AXI_DATA_W_BYTES:] - expected[0] += i - await csr_write(cpu, reg, expected) - read = await csr_read(cpu, reg) - assert ( - read.data == expected - ), "Expected data doesn't match contents of the {}".format(reg) - i += 1 - await triggers.ClockCycles(dut.clk, 10) - - -async def test_reset(dut): - clock = Clock(dut.clk, 10, units="us") - cocotb.start_soon(clock.start()) - - await reset_dut(dut, 50) - - csr_bus = connect_axi_bus(dut, "csr") - cpu = AxiMaster(csr_bus, dut.clk, dut.rst) - - await triggers.ClockCycles(dut.clk, 10) - await start_decoder(cpu) - timeout = 10 - status = await csr_read(cpu, CSR.STATUS) - while (int.from_bytes(status.data, byteorder="little") == Status.IDLE.value) & ( - timeout != 0 - ): - status = await csr_read(cpu, CSR.STATUS) - timeout -= 1 - assert timeout != 0 - - await reset_dut(dut, 50) - await wait_for_idle(cpu, 10) - - await triggers.ClockCycles(dut.clk, 10) - - -async def configure_decoder(dut, cpu, ibuf_addr, obuf_addr): - status = await csr_read(cpu, CSR.STATUS) - if int.from_bytes(status.data, byteorder="little") != Status.IDLE.value: - await reset_dut(dut, 50) - await csr_write(cpu, CSR.INPUTBUFFER, ibuf_addr) - await csr_write(cpu, CSR.OUTPUTBUFFER, obuf_addr) - - -async def start_decoder(cpu): - await csr_write(cpu, CSR.START, 0x1) - - -async def wait_for_idle(cpu, timeout=100): - status = await csr_read(cpu, CSR.STATUS) - while (int.from_bytes(status.data, byteorder="little") != Status.IDLE.value) & ( - timeout != 0 - ): - status = await csr_read(cpu, CSR.STATUS) - timeout -= 1 - assert timeout != 0 - - -async def reset_dut(dut, rst_len=10): - dut.rst.setimmediatevalue(0) - await triggers.ClockCycles(dut.clk, rst_len) - dut.rst.setimmediatevalue(1) - await triggers.ClockCycles(dut.clk, rst_len) - dut.rst.setimmediatevalue(0) - - -def get_clock_time(clock: Clock): - return get_sim_time(units="step") / clock.period - - -def connect_xls_channel(dut, channel_name, xls_struct): - channel = xlschannel.XLSChannel(dut, channel_name, dut.clk, start_now=True) - monitor = xlschannel.XLSChannelMonitor(dut, channel_name, dut.clk, xls_struct) - - return (channel, monitor) - - -def prepare_test_environment(dut): - clock = Clock(dut.clk, 10, units="us") - cocotb.start_soon(clock.start()) - - memory_bus = connect_axi_bus(dut, "memory") - csr_bus = connect_axi_bus(dut, "csr") - axi_buses = {"memory": memory_bus, "csr": csr_bus} - - cpu = AxiMaster(csr_bus, dut.clk, dut.rst) - - return (axi_buses, cpu, clock) - - -async def test_fse_lookup_decoder(dut, clock, expected_fse_lookups): - lookup_dec_resp_channel = xlschannel.XLSChannel( - dut.ZstdDecoder.xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next_inst149, - "zstd_dec__flc_resp", - dut.clk, - ) - fse_lookup_resp_handshake = triggers.Event() - - block_cnt = 0 - - def func(): - nonlocal block_cnt - assert block_cnt <= len(expected_fse_lookups) - print_fse_ram_contents( - dut.ll_fse_ram.mem, "LL", size=len(expected_fse_lookups[block_cnt]["ll"]) - ) - print_fse_ram_contents( - dut.ml_fse_ram.mem, "ML", size=len(expected_fse_lookups[block_cnt]["ml"]) - ) - print_fse_ram_contents( - dut.of_fse_ram.mem, "OF", size=len(expected_fse_lookups[block_cnt]["of"]) - ) - check_ram_contents( - dut.ll_fse_ram.mem, [x.value for x in expected_fse_lookups[block_cnt]["ll"]] - ) - check_ram_contents( - dut.ml_fse_ram.mem, [x.value for x in expected_fse_lookups[block_cnt]["ml"]] - ) - check_ram_contents( - dut.of_fse_ram.mem, [x.value for x in expected_fse_lookups[block_cnt]["of"]] - ) - block_cnt += 1 - - cocotb.start_soon( - set_handshake_event(dut.clk, lookup_dec_resp_channel, fse_lookup_resp_handshake) - ) - cocotb.start_soon(get_handshake_event(dut, fse_lookup_resp_handshake, func)) - - -async def test_fse_lookup_decoder_for_huffman(dut, clock, expected_fse_lookups): - lookup_dec_resp_channel = xlschannel.XLSChannel( - dut.ZstdDecoder.xls_modules_zstd_comp_lookup_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__HuffmanFseWeightsDecoder_0__CompLookupDecoder_0__64_8_16_1_15_8_32_1_7_9_8_1_8_16_1_next_inst5, - "zstd_dec__fse_table_finish__1", - dut.clk, - ) - fse_lookup_resp_handshake = triggers.Event() - - block_cnt = 0 - - def func(): - nonlocal block_cnt - assert block_cnt <= len(expected_fse_lookups) - print_fse_ram_contents( - dut.huffman_literals_weights_fse_ram_ram.mem, - f"HUFMMAN ({block_cnt})", - size=len(expected_fse_lookups[block_cnt]), - ) - check_ram_contents( - dut.huffman_literals_weights_fse_ram_ram.mem, - [x.value for x in expected_fse_lookups[block_cnt]], - ) - block_cnt += 1 - - cocotb.start_soon( - set_handshake_event(dut.clk, lookup_dec_resp_channel, fse_lookup_resp_handshake) - ) - cocotb.start_soon(get_handshake_event(dut, fse_lookup_resp_handshake, func)) - - -def reverse_expected_huffman_codes(exp_codes): - def reverse_bits(value, max_bits): - bv = BinaryValue(value=value, n_bits=max_bits, bigEndian=False) - return int(BinaryValue(value=bv.binstr[::-1], n_bits=max_bits, bigEndian=False)) - - max_bits = max(d["length"] for d in exp_codes) - - codes = [] - for record in exp_codes: - codes += [ - { - "code": reverse_bits(record["code"], max_bits), - "length": record["length"], - "symbol": record["symbol"], - } - ] - return codes - - -async def test_huffman_codes(dut, clock, expected_codes): - WEIGHT_CODE_BUILDER_INST = ( - dut.ZstdDecoder.xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next_inst20 - ) - CODES_CHANNEL_NAME = "zstd_dec__code_builder_codes" - - codes_channel = xlschannel.XLSChannel( - WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME, dut.clk - ) - huffman_code_handshake = triggers.Event() - - codes = [] - block_cnt = 0 - packet_cnt = 0 - symbol_cnt = 0 - - def func(): - nonlocal codes - nonlocal symbol_cnt - nonlocal packet_cnt - nonlocal block_cnt - - assert block_cnt <= 32 - codes_data = getattr(WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME) - data = CodeBuilderOutput.from_int(codes_data.value) - - symbol_valid_array = fields_as_array(data, "symbol_valid", 8) - code_length_array = fields_as_array(data, "code_length", 8) - code_array = fields_as_array(data, "code", 8) - - for symbol_valid, code_length, code in zip( - symbol_valid_array, code_length_array, code_array - ): - if symbol_valid == 1: - codes += [{"symbol": symbol_cnt, "code": code, "length": code_length}] - symbol_cnt += 1 - packet_cnt += 1 - - if packet_cnt == 32: - assert codes == reverse_expected_huffman_codes(expected_codes[block_cnt]) - packet_cnt = 0 - symbol_cnt = 0 - block_cnt += 1 - codes = [] - - cocotb.start_soon( - set_handshake_event(dut.clk, codes_channel, huffman_code_handshake) - ) - cocotb.start_soon(get_handshake_event(dut, huffman_code_handshake, func)) - - -async def test_huffman_weights(dut, clock, expected_huffman_weights): - lookup_dec_resp_channel = xlschannel.XLSChannel( - dut.ZstdDecoder.xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next_inst21, - "zstd_dec__weights_dec_resp", - dut.clk, - ) - huffman_weights_resp_handshake = triggers.Event() - - block_cnt = 0 - - def func(): - nonlocal block_cnt - assert block_cnt <= len(expected_huffman_weights) - print_ram_contents( - dut.huffman_literals_weights_mem_ram_ram.mem, - f"WEIGHTS ({block_cnt})", - size=64, - ) - check_ram_contents( - dut.huffman_literals_weights_mem_ram_ram.mem, - expected_huffman_weights[block_cnt], - ) - block_cnt += 1 - - cocotb.start_soon( - set_handshake_event( - dut.clk, lookup_dec_resp_channel, huffman_weights_resp_handshake - ) - ) - cocotb.start_soon(get_handshake_event(dut, huffman_weights_resp_handshake, func)) - - -async def test_decoder( - dut, seed, block_type, literal_type, axi_buses, cpu, clock, pregenerated -): - """Test decoder with zstd-compressed data - - if a file name is provided in `pregenerated`, use it as input - otherwise generate a random file using (seed, block_type, literal_type) - - The output of the decoder is compared with the output of decodercorpus - using the same input file. - """ - memory_bus = axi_buses["memory"] - - (unused_notify_channel, notify_monitor) = connect_xls_channel( - dut, NOTIFY_CHANNEL, NotifyStruct - ) - assert_notify = triggers.Event() - set_termination_event(notify_monitor, assert_notify, 1) - - mem_size = MAX_ENCODED_FRAME_SIZE_B - ibuf_addr = 0x0 - obuf_addr = mem_size // 2 - - # TODO if pregenerated is used, - # block_type, literal_type and seed aren't used. Handle this better. - if pregenerated: - encoded = open(pregenerated, "rb") - else: - # FIXME: use delete_on_close=False after moving to python 3.12 - encoded = tempfile.NamedTemporaryFile(delete=False) - # Generate ZSTD frame to temporary file - data_generator.GenerateFrame(seed, block_type, encoded.name, literal_type) - - assert check_decoder_compliance(encoded.name), (f"error: '{encoded.name}' is not suitable for the decoder parameters") - - print( - "\nusing" - + (" pregenerated" if pregenerated else f" randomly generated (seed={seed})") - + f" input file for decoder: {encoded.name}\n" - ) - - await reset_dut(dut, 50) - - expected_decoded_frame = data_generator.DecompressFrame(encoded.read()) - encoded.close() - reference_memory = SparseMemory(mem_size) - reference_memory.write(obuf_addr, expected_decoded_frame) - - # Initialise testbench memory with generated ZSTD frame - memory = AxiRamFromFile( - bus=memory_bus, clock=dut.clk, reset=dut.rst, path=encoded.name, size=mem_size - ) - - await configure_decoder(dut, cpu, ibuf_addr, obuf_addr) - output_monitor = AxiWMonitor(memory_bus.write.w, dut.clk, dut.rst) - await start_decoder(cpu) - decode_start = get_clock_time(clock) - await output_monitor.wait() - decode_first_packet = get_clock_time(clock) - await assert_notify.wait() - decode_end = get_clock_time(clock) - await wait_for_idle(cpu) - # Read decoded frame in chunks of AXI_DATA_W length - # Compare against frame decompressed with the reference library - expected_packet_count = ( - len(expected_decoded_frame) + (AXI_DATA_W_BYTES - 1) - ) // AXI_DATA_W_BYTES - for read_op in range(0, expected_packet_count): - addr = obuf_addr + (read_op * AXI_DATA_W_BYTES) - mem_contents = memory.read(addr, AXI_DATA_W_BYTES) - exp_mem_contents = reference_memory.read(addr, AXI_DATA_W_BYTES) - assert mem_contents == exp_mem_contents, ( - "{} bytes of memory contents at address {} " - "don't match the expected contents:\n" - "{}\nvs\n{}" - ).format( - AXI_DATA_W_BYTES, - hex(addr), - hex(int.from_bytes(mem_contents, byteorder="little")), - hex(int.from_bytes(exp_mem_contents, byteorder="little")), - ) - await ClockCycles(dut.clk, 20) - - -async def testing_routine( - dut, - test_cases=1, - block_type=data_generator.BlockType.RANDOM, - literal_type=data_generator.LiteralType.RANDOM, - pregenerated=None, - expected_fse_lookups=None, - expected_fse_huffman_lookups=None, - expected_huffman_weights=None, - expected_huffman_codes=None, -): - (axi_buses, cpu, clock) = prepare_test_environment(dut) - frame_id = 0 - for test_case in range(test_cases): - if expected_fse_lookups is not None: - await test_fse_lookup_decoder(dut, clock, expected_fse_lookups) - if expected_fse_huffman_lookups is not None: - await test_fse_lookup_decoder_for_huffman( - dut, clock, expected_fse_huffman_lookups - ) - if expected_huffman_codes is not None: - await test_huffman_codes(dut, clock, expected_huffman_codes) - if expected_huffman_weights is not None: - await test_huffman_weights(dut, clock, expected_huffman_weights) - await test_decoder( - dut, 2, block_type, literal_type, axi_buses, cpu, clock, pregenerated - ) - print("Decoding {} ZSTD frames done".format(block_type.name)) - +from xls.modules.zstd.zstd_dec_cocotb_common import ( + randomized_testing_routine, + pregenerated_testing_routine, + run_test, + test_csr, + test_reset, + FseTableRecord +) @cocotb.test(timeout_time=50, timeout_unit="ms") async def zstd_csr_test(dut): @@ -645,14 +38,14 @@ async def zstd_reset_test(dut): async def zstd_raw_frames_test(dut): test_cases = 5 block_type = data_generator.BlockType.RAW - await testing_routine(dut, test_cases, block_type) + await randomized_testing_routine(dut, test_cases, block_type) @cocotb.test(timeout_time=500, timeout_unit="ms") async def zstd_rle_frames_test(dut): test_cases = 5 block_type = data_generator.BlockType.RLE - await testing_routine(dut, test_cases, block_type) + await randomized_testing_routine(dut, test_cases, block_type) # Tests with pregenerated inputs @@ -671,45 +64,31 @@ async def zstd_rle_frames_test(dut): @cocotb.test(timeout_time=2000, timeout_unit="ms") async def pregenerated_compressed_raw_1(dut): input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_raw_1.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RAW - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=2000, timeout_unit="ms") async def pregenerated_compressed_raw_2(dut): input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_raw_2.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RAW - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=2000, timeout_unit="ms") async def pregenerated_compressed_rle_1(dut): input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_rle_1.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RLE - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def pregenerated_compressed_rle_2(dut): input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_rle_2.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RLE - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=1000, timeout_unit="ms") async def pregenerated_compressed_random_1(dut): input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_random_1.zst" test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_huffman_weights = [ [ @@ -938,11 +317,8 @@ async def pregenerated_compressed_random_1(dut): ], ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, @@ -953,10 +329,7 @@ async def pregenerated_compressed_random_1(dut): @cocotb.test(timeout_time=2000, timeout_unit="ms") async def pregenerated_compressed_random_2(dut): input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_random_2.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) # Tests with predefined FSE tables and Huffman-encoded literals @@ -968,9 +341,6 @@ async def fse_huffman_literals_predefined_sequences_seed_107958(dut): PREGENERATED_FILES_DIR + "fse_huffman_literals_predefined_sequences_seed_107958.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_huffman_weights = [ [ @@ -1046,11 +416,8 @@ async def fse_huffman_literals_predefined_sequences_seed_107958(dut): ] ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_huffman_weights=expected_huffman_weights, expected_fse_huffman_lookups=expected_fse_huffman_lookups, @@ -1063,10 +430,7 @@ async def fse_huffman_literals_predefined_sequences_seed_204626(dut): PREGENERATED_FILES_DIR + "fse_huffman_literals_predefined_sequences_seed_204626.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=350, timeout_unit="ms") @@ -1075,10 +439,7 @@ async def fse_huffman_literals_predefined_sequences_seed_210872(dut): PREGENERATED_FILES_DIR + "fse_huffman_literals_predefined_sequences_seed_210872.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=350, timeout_unit="ms") @@ -1087,10 +448,7 @@ async def fse_huffman_literals_predefined_sequences_seed_299289(dut): PREGENERATED_FILES_DIR + "fse_huffman_literals_predefined_sequences_seed_299289.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=350, timeout_unit="ms") @@ -1099,9 +457,6 @@ async def fse_huffman_literals_predefined_sequences_seed_319146(dut): PREGENERATED_FILES_DIR + "fse_huffman_literals_predefined_sequences_seed_319146.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_huffman_codes = [ [ @@ -1139,11 +494,8 @@ async def fse_huffman_literals_predefined_sequences_seed_319146(dut): ] ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_huffman_codes=expected_huffman_codes, ) @@ -1155,9 +507,6 @@ async def fse_huffman_literals_predefined_sequences_seed_331938(dut): PREGENERATED_FILES_DIR + "fse_huffman_literals_predefined_sequences_seed_331938.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_huffman_codes = [ [ @@ -1182,11 +531,8 @@ async def fse_huffman_literals_predefined_sequences_seed_331938(dut): ] ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_huffman_codes=expected_huffman_codes, ) @@ -1198,10 +544,7 @@ async def fse_huffman_literals_predefined_sequences_seed_333824(dut): PREGENERATED_FILES_DIR + "fse_huffman_literals_predefined_sequences_seed_333824.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) # Test cases crated manually to allow working with small sizes of inputs. @@ -1210,19 +553,13 @@ async def fse_huffman_literals_predefined_sequences_seed_333824(dut): @cocotb.test(timeout_time=200, timeout_unit="ms") async def pregenerated_compressed_minimal(dut): input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_minimal.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") async def pregenerated_uncompressed(dut): input_name = PREGENERATED_FILES_DIR + "pregenerated_uncompressed.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) # Test cases with predefined FSE tables and RAW/RLE literals @@ -1233,10 +570,7 @@ async def rle_literals_predefined_sequences_seed_406229(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_406229.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1244,10 +578,7 @@ async def rle_literals_predefined_sequences_seed_411034(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_411034.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1255,10 +586,7 @@ async def rle_literals_predefined_sequences_seed_413015(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_413015.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1266,10 +594,7 @@ async def rle_literals_predefined_sequences_seed_436165(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_436165.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1277,10 +602,7 @@ async def rle_literals_predefined_sequences_seed_464057(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_464057.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1288,10 +610,7 @@ async def rle_literals_predefined_sequences_seed_466803(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_466803.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1299,10 +618,7 @@ async def raw_literals_predefined_sequences_seed_422473(dut): input_name = ( PREGENERATED_FILES_DIR + "raw_literals_predefined_sequences_seed_422473.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1310,10 +626,7 @@ async def raw_literals_predefined_sequences_seed_436965(dut): input_name = ( PREGENERATED_FILES_DIR + "raw_literals_predefined_sequences_seed_436965.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1321,10 +634,7 @@ async def raw_literals_predefined_sequences_seed_462302(dut): input_name = ( PREGENERATED_FILES_DIR + "raw_literals_predefined_sequences_seed_462302.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1332,10 +642,7 @@ async def rle_raw_literals_predefined_sequences_seed_408158(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_raw_literals_predefined_sequences_seed_408158.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") @@ -1343,10 +650,7 @@ async def rle_raw_literals_predefined_sequences_seed_499212(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_raw_literals_predefined_sequences_seed_499212.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) # Tests with inputs that correspond to the values in arrays defined in @@ -1356,18 +660,12 @@ async def rle_raw_literals_predefined_sequences_seed_499212(dut): @cocotb.test(timeout_time=200, timeout_unit="ms") async def comp_frame(dut): input_name = PREGENERATED_FILES_DIR + "comp_frame.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=500, timeout_unit="ms") async def comp_frame_fse_comp(dut): input_name = PREGENERATED_FILES_DIR + "comp_frame_fse_comp.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_fse_lookups = [ { @@ -1572,11 +870,8 @@ async def comp_frame_fse_comp(dut): } ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_fse_lookups=expected_fse_lookups, ) @@ -1585,28 +880,19 @@ async def comp_frame_fse_comp(dut): @cocotb.test(timeout_time=200, timeout_unit="ms") async def comp_frame_fse_repeated(dut): input_name = PREGENERATED_FILES_DIR + "comp_frame_fse_repeated.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") async def comp_frame_huffman(dut): input_name = PREGENERATED_FILES_DIR + "comp_frame_huffman.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") async def comp_frame_huffman_fse(dut): input_name = PREGENERATED_FILES_DIR + "comp_frame_huffman_fse.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=1000, timeout_unit="ms") @@ -1614,19 +900,13 @@ async def raw_literals_compressed_sequences_seed_903062(dut): input_name = ( PREGENERATED_FILES_DIR + "raw_literals_compressed_sequences_seed_903062.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") async def raw_literals_rle_sequences_seed_700216(dut): input_name = PREGENERATED_FILES_DIR + "raw_literals_rle_sequences_seed_700216.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=1000, timeout_unit="ms") @@ -1634,19 +914,13 @@ async def rle_literals_compressed_sequences_seed_701326(dut): input_name = ( PREGENERATED_FILES_DIR + "rle_literals_compressed_sequences_seed_701326.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=200, timeout_unit="ms") async def rle_literals_rle_sequences_seed_2(dut): input_name = PREGENERATED_FILES_DIR + "rle_literals_rle_sequences_seed_2.zst" - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM - await testing_routine(dut, test_cases, block_type, literal_type, input_name) + await pregenerated_testing_routine(dut, input_name) @cocotb.test(timeout_time=2000, timeout_unit="ms") @@ -1655,9 +929,6 @@ async def treeless_huffman_literals_compressed_sequences_seed_400077(dut): PREGENERATED_FILES_DIR + "treeless_huffman_literals_compressed_sequences_seed_400077.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_huffman_weights = [ [ @@ -1736,11 +1007,8 @@ async def treeless_huffman_literals_compressed_sequences_seed_400077(dut): ] ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, @@ -1756,9 +1024,6 @@ async def treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400 PREGENERATED_FILES_DIR + "treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_huffman_weights = [ [ @@ -1788,11 +1053,8 @@ async def treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400 ] ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, @@ -1807,9 +1069,6 @@ async def treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400 PREGENERATED_FILES_DIR + "treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_huffman_weights = [ [ @@ -1839,11 +1098,8 @@ async def treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400 ] ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, @@ -1856,9 +1112,6 @@ async def treeless_huffman_literals_rle_sequences_seed_403927(dut): PREGENERATED_FILES_DIR + "treeless_huffman_literals_rle_sequences_seed_403927.zst" ) - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RANDOM expected_huffman_weights = [ [ @@ -1951,11 +1204,8 @@ async def treeless_huffman_literals_rle_sequences_seed_403927(dut): ] ] - await testing_routine( + await pregenerated_testing_routine( dut, - test_cases, - block_type, - literal_type, input_name, expected_huffman_codes=expected_huffman_codes, expected_huffman_weights=expected_huffman_weights, @@ -1971,34 +1221,8 @@ async def zstd_compressed_frames_test(dut): test_cases = 1 block_type = data_generator.BlockType.COMPRESSED literal_type = data_generator.LiteralType.RAW - await testing_routine(dut, test_cases, block_type, literal_type) + await randomized_testing_routine(dut, test_cases, block_type, literal_type) if __name__ == "__main__": - sys.path.append(str(pathlib.Path(__file__).parent)) - with tempfile.NamedTemporaryFile(mode="w") as modified_zstd_verilog: - toplevel = "zstd_dec_wrapper" - test_module = [pathlib.Path(__file__).stem] - verilog_sources = [ - modified_zstd_verilog.name, - "xls/modules/zstd/rtl/xls_fifo_wrapper.sv", - "xls/modules/zstd/rtl/zstd_dec_wrapper.sv", - "xls/modules/zstd/axi_crossbar_wrapper.v", - "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar.v", - "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_rd.v", - "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_wr.v", - "external/com_github_alexforencich_verilog_axi/rtl/axi_crossbar_addr.v", - "external/com_github_alexforencich_verilog_axi/rtl/axi_register_rd.v", - "external/com_github_alexforencich_verilog_axi/rtl/axi_register_wr.v", - "external/com_github_alexforencich_verilog_axi/rtl/arbiter.v", - "external/com_github_alexforencich_verilog_axi/rtl/priority_encoder.v", - "xls/modules/zstd/rtl/ram_1r1w.v", - ] - - with open("xls/modules/zstd/zstd_dec.v") as zstd_verilog: - modified_content = zstd_verilog.read().replace( - "__xls_modules_zstd", "xls_modules_zstd" - ) - modified_zstd_verilog.write(modified_content) - - modified_zstd_verilog.flush() # - run_test(toplevel, test_module, verilog_sources) + test_module = [pathlib.Path(__file__).stem] + run_test(test_module, sim="icarus") From ceee081395891c2a2abf97f9ea95ef02c0e81e0e Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Tue, 2 Sep 2025 18:19:25 +0200 Subject: [PATCH 049/159] modules: zstd: cocotb: fail decoder test early on first mismatch --- xls/modules/zstd/zstd_dec_cocotb_common.py | 57 +++++++++++++--------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index b8843a0186..1b0e6840d4 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -16,6 +16,7 @@ import math import enum import tempfile +import sys import cocotb from cocotb import triggers @@ -608,6 +609,32 @@ def func(): ) cocotb.start_soon(get_handshake_event(dut, huffman_weights_resp_handshake, func)) +async def check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock): + # Read decoded frame in chunks of AXI_DATA_W length + # Compare against frame decompressed with the reference library + current_addr = obuf_addr + decode_start = get_clock_time(clock) + await output_monitor.wait() + decode_first_packet = get_clock_time(clock) + + for read_op in range(0, expected_packet_count): + current_addr = obuf_addr + (read_op * AXI_DATA_W_BYTES) + exp_mem_contents = int.from_bytes(reference_memory.read(current_addr, AXI_DATA_W_BYTES), byteorder="little") + mem_contents = (await output_monitor.recv()).wdata + assert mem_contents == exp_mem_contents, ( + "{} bytes of memory contents at address {} " + "don't match the expected contents:\n" + "{}\nvs\n{}" + ).format( + AXI_DATA_W_BYTES, + hex(current_addr), + hex(mem_contents), + hex(exp_mem_contents), + ) + print(f'[cocotb] Got correct packet (addr: {hex(current_addr)}, data: {hex(mem_contents)}, clk: {get_clock_time(clock)})', file=sys.stderr) + + decode_last_packet = get_clock_time(clock) + return (decode_start, decode_first_packet, decode_last_packet) async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): """Test decoder with zstd-compressed data provided in `encoded_file` @@ -634,6 +661,9 @@ async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): expected_decoded_frame = data_generator.DecompressFrame(encoded_file.read()) reference_memory = SparseMemory(mem_size) reference_memory.write(obuf_addr, expected_decoded_frame) + expected_packet_count = ( + len(expected_decoded_frame) + (AXI_DATA_W_BYTES - 1) + ) // AXI_DATA_W_BYTES # Initialise testbench memory with generated ZSTD frame memory = AxiRamFromFile( @@ -643,31 +673,12 @@ async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): await configure_decoder(dut, cpu, ibuf_addr, obuf_addr) output_monitor = AxiWMonitor(memory_bus.write.w, dut.clk, dut.rst) await start_decoder(cpu) - decode_start = get_clock_time(clock) - await output_monitor.wait() - decode_first_packet = get_clock_time(clock) + + decode_times = await check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock) + (decode_start, decode_first_packet, decode_last_packet) = decode_times await assert_notify.wait() - decode_end = get_clock_time(clock) await wait_for_idle(cpu) - # Read decoded frame in chunks of AXI_DATA_W length - # Compare against frame decompressed with the reference library - expected_packet_count = ( - len(expected_decoded_frame) + (AXI_DATA_W_BYTES - 1) - ) // AXI_DATA_W_BYTES - for read_op in range(0, expected_packet_count): - addr = obuf_addr + (read_op * AXI_DATA_W_BYTES) - mem_contents = memory.read(addr, AXI_DATA_W_BYTES) - exp_mem_contents = reference_memory.read(addr, AXI_DATA_W_BYTES) - assert mem_contents == exp_mem_contents, ( - "{} bytes of memory contents at address {} " - "don't match the expected contents:\n" - "{}\nvs\n{}" - ).format( - AXI_DATA_W_BYTES, - hex(addr), - hex(int.from_bytes(mem_contents, byteorder="little")), - hex(int.from_bytes(exp_mem_contents, byteorder="little")), - ) + decode_end = get_clock_time(clock) latency = decode_first_packet - decode_start throughput_repacketizer = expected_packet_count / (decode_end - decode_first_packet) From 392a7377a777b1a981fdec77a825bef6967f8e86 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Wed, 3 Sep 2025 19:00:03 +0200 Subject: [PATCH 050/159] modules: zstd: cocotb: fail early on decoder errors --- xls/modules/zstd/zstd_dec_cocotb_common.py | 45 +++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 1b0e6840d4..529040d142 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -146,6 +146,17 @@ class Status(enum.Enum): IDLE = 0x0 RUNNING = 0x1 + READ_CONFIG_OK = 2 + FRAME_HEADER_OK = 3 + FRAME_HEADER_CORRUPTED = 4 + FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE = 5 + BLOCK_HEADER_OK = 6 + BLOCK_HEADER_CORRUPTED = 7 + BLOCK_HEADER_MEMORY_ACCESS_ERROR = 8 + RAW_BLOCK_OK = 9 + RAW_BLOCK_ERROR = 10 + RLE_BLOCK_OK = 11 + CMP_BLOCK_OK = 12 def check_decoder_compliance(file_path): @@ -609,6 +620,27 @@ def func(): ) cocotb.start_soon(get_handshake_event(dut, huffman_weights_resp_handshake, func)) + +async def check_status(dut, cpu, timeout=100): + (unused_notify_channel, notify_monitor) = connect_xls_channel( + dut, NOTIFY_CHANNEL, NotifyStruct + ) + notify_event = triggers.Event() + set_termination_event(notify_monitor, notify_event, 1) + + await notify_event.wait() + + status = None + while timeout != 0 and status != Status.IDLE: + status_reg = await csr_read(cpu, CSR.STATUS) + status = Status(int.from_bytes(status_reg.data, byteorder="little")) + timeout -= 1 + + assert status == Status.IDLE, ( + f"Decoder finished with non-idle status: {status.name}" + ) + + async def check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock): # Read decoded frame in chunks of AXI_DATA_W length # Compare against frame decompressed with the reference library @@ -646,12 +678,6 @@ async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): memory_bus = axi_buses["memory"] - (unused_notify_channel, notify_monitor) = connect_xls_channel( - dut, NOTIFY_CHANNEL, NotifyStruct - ) - assert_notify = triggers.Event() - set_termination_event(notify_monitor, assert_notify, 1) - mem_size = MAX_ENCODED_FRAME_SIZE_B ibuf_addr = 0x0 obuf_addr = mem_size // 2 @@ -674,10 +700,11 @@ async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): output_monitor = AxiWMonitor(memory_bus.write.w, dut.clk, dut.rst) await start_decoder(cpu) - decode_times = await check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock) + check_status_thread = await cocotb.start(check_status(dut,cpu)) + check_output_thread = await cocotb.start(check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock)) + decode_times = await check_output_thread (decode_start, decode_first_packet, decode_last_packet) = decode_times - await assert_notify.wait() - await wait_for_idle(cpu) + await check_status_thread decode_end = get_clock_time(clock) latency = decode_first_packet - decode_start From 79c5b204c6ea7da4c922557969ddcbbc2d8da465 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Thu, 4 Sep 2025 14:40:36 +0200 Subject: [PATCH 051/159] modules: zstd: use a history buffer size directly for the frame header parsing --- xls/modules/zstd/BUILD | 2 +- xls/modules/zstd/common.x | 7 +++ xls/modules/zstd/data/comp_frame.x | 2 +- xls/modules/zstd/data/comp_frame_fse_comp.x | 2 +- xls/modules/zstd/data/comp_frame_huffman.x | 2 +- .../zstd/data/comp_frame_huffman_fse.x | 2 +- xls/modules/zstd/frame_header.x | 45 ++++++++----------- xls/modules/zstd/frame_header_dec.x | 21 +++++---- xls/modules/zstd/literals_buffer.x | 2 +- xls/modules/zstd/literals_decoder.x | 2 +- xls/modules/zstd/sequence_executor.x | 2 +- xls/modules/zstd/zstd_dec.x | 17 ++++--- xls/modules/zstd/zstd_dec_test.x | 8 ++-- 13 files changed, 54 insertions(+), 60 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 177ee41650..c118f1b7cb 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -392,7 +392,7 @@ xls_benchmark_ir( name = "frame_header_dec_opt_ir_benchmark", src = ":frame_header_dec_verilog.opt.ir", benchmark_ir_args = FRAME_HEADER_DEC_CODEGEN_ARGS | { - "top": "__frame_header_dec__FrameHeaderDecoderInst__FrameHeaderDecoder_0__16_32_30_5_next", + "top": "__frame_header_dec__FrameHeaderDecoderInst__FrameHeaderDecoder_0__16_32_7864320_5_next", "pipeline_stages": "10", }, tags = ["manual"], diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 640682b8e4..7e55a76c34 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -21,7 +21,14 @@ pub const MAX_ID = u32::MAX; pub const SYMBOL_WIDTH = u32:8; pub const BLOCK_SIZE_WIDTH = u32:21; pub const OFFSET_WIDTH = u32:22; + +// u32 here is sufficient for the maximum size of the history buffer +// as RFC 8878 states that 3.75 TB is the maximum allowed value for +// a window size and storing this value in KB units allows it to be +// saved in a 32-bit unsigned integer (2^32 * 2^10 > 3.75 TB) +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.1.2-6 pub const HISTORY_BUFFER_SIZE_KB = u32:64; + pub const BUFFER_WIDTH = u32:128; pub const MAX_BLOCK_SIZE_KB = u32:64; diff --git a/xls/modules/zstd/data/comp_frame.x b/xls/modules/zstd/data/comp_frame.x index 9d0c1a0f4c..dec43eab0b 100644 --- a/xls/modules/zstd/data/comp_frame.x +++ b/xls/modules/zstd/data/comp_frame.x @@ -21,7 +21,7 @@ pub const FRAMES:DataArray< >[1] = [DataArray<64, 50>{ length: u32:51, array_length: u32:7, - data: uN[64][50]:[uN[64]:0x00504784fd2fb528, uN[64]:0xcf95700001150000, uN[64]:0xe17d50b989ac93c4, uN[64]:0x0daf000895a6e608, uN[64]:0xb96010b86f7602a4, uN[64]:0x05b0e051238666e8, uN[64]:0x8470e3, uN[64]:0, ...] + data: uN[64][50]:[uN[64]:0x00502f84fd2fb528, uN[64]:0xcf95700001150000, uN[64]:0xe17d50b989ac93c4, uN[64]:0x0daf000895a6e608, uN[64]:0xb96010b86f7602a4, uN[64]:0x05b0e051238666e8, uN[64]:0x8470e3, uN[64]:0, ...] }]; pub const DECOMPRESSED_FRAMES:DataArray< u32:64, diff --git a/xls/modules/zstd/data/comp_frame_fse_comp.x b/xls/modules/zstd/data/comp_frame_fse_comp.x index faf3ff66e2..dd2427d39c 100644 --- a/xls/modules/zstd/data/comp_frame_fse_comp.x +++ b/xls/modules/zstd/data/comp_frame_fse_comp.x @@ -21,7 +21,7 @@ pub const FRAMES:DataArray< >[1] = [DataArray<64, 50>{ length: u32:66, array_length: u32:9, - data: uN[64][50]:[uN[64]:0x00545084fd2fb528, uN[64]:0x4236d000018d0000, uN[64]:0x1d98357537f4050f, uN[64]:0x8d92b5aed6d7791b, uN[64]:0x51538ed729019574, uN[64]:0x701101fb8611a803, uN[64]:0x8acfff857107d159, uN[64]:0x548604b38e0a63fd, uN[64]:0xc551, uN[64]:0, ...] + data: uN[64][50]:[uN[64]:0x00542f84fd2fb528, uN[64]:0x4236d000018d0000, uN[64]:0x1d98357537f4050f, uN[64]:0x8d92b5aed6d7791b, uN[64]:0x51538ed729019574, uN[64]:0x701101fb8611a803, uN[64]:0x8acfff857107d159, uN[64]:0x548604b38e0a63fd, uN[64]:0xc551, uN[64]:0, ...] }]; pub const DECOMPRESSED_FRAMES:DataArray< u32:64, diff --git a/xls/modules/zstd/data/comp_frame_huffman.x b/xls/modules/zstd/data/comp_frame_huffman.x index b41ddf275f..c4ce570089 100644 --- a/xls/modules/zstd/data/comp_frame_huffman.x +++ b/xls/modules/zstd/data/comp_frame_huffman.x @@ -21,7 +21,7 @@ pub const FRAMES:DataArray< >[1] = [DataArray<64, 50>{ length: u32:93, array_length: u32:12, - data: uN[64][50]:[uN[64]:0x00704484fd2fb528, uN[64]:0xac033a0002650000, uN[64]:0x1111111111118e00, uN[64]:0x0007000700071011, uN[64]:0x131a053a5606874c, uN[64]:0x93b7146cb45c3584, uN[64]:0x06499215949aa275, uN[64]:0x0132000c0126fd3b, uN[64]:0x15a7b54443de03b8, uN[64]:0x5da6a9b37c005000, uN[64]:0x4e0200656960219d, uN[64]:0x912a65cf0b, uN[64]:0, ...] + data: uN[64][50]:[uN[64]:0x00702f84fd2fb528, uN[64]:0xac033a0002650000, uN[64]:0x1111111111118e00, uN[64]:0x0007000700071011, uN[64]:0x131a053a5606874c, uN[64]:0x93b7146cb45c3584, uN[64]:0x06499215949aa275, uN[64]:0x0132000c0126fd3b, uN[64]:0x15a7b54443de03b8, uN[64]:0x5da6a9b37c005000, uN[64]:0x4e0200656960219d, uN[64]:0x912a65cf0b, uN[64]:0, ...] }]; pub const DECOMPRESSED_FRAMES:DataArray< u32:64, diff --git a/xls/modules/zstd/data/comp_frame_huffman_fse.x b/xls/modules/zstd/data/comp_frame_huffman_fse.x index c1e78b95cb..781657ec31 100644 --- a/xls/modules/zstd/data/comp_frame_huffman_fse.x +++ b/xls/modules/zstd/data/comp_frame_huffman_fse.x @@ -22,7 +22,7 @@ pub const FRAMES:DataArray< >[1] = [DataArray<64, 50>{ length: u32:64, array_length: u32:8, - data: uN[64][50]:[uN[64]:0x007e4f84fd2fb528, uN[64]:0x00068e00017d0000, uN[64]:0xd5764f39f0080008, uN[64]:0x04000400045c4f40, uN[64]:0xcfefff3e7fefff00, uN[64]:0x5dff77afbdffef3f, uN[64]:0x1de190b0000301fb, uN[64]:0x807e83a8084e0c21, uN[64]:0, ...] + data: uN[64][50]:[uN[64]:0x007e2f84fd2fb528, uN[64]:0x00068e00017d0000, uN[64]:0xd5764f39f0080008, uN[64]:0x04000400045c4f40, uN[64]:0xcfefff3e7fefff00, uN[64]:0x5dff77afbdffef3f, uN[64]:0x1de190b0000301fb, uN[64]:0x807e83a8084e0c21, uN[64]:0, ...] }]; pub const DECOMPRESSED_FRAMES:DataArray< u32:64, diff --git a/xls/modules/zstd/frame_header.x b/xls/modules/zstd/frame_header.x index 8159f1e47e..fab943ad5a 100644 --- a/xls/modules/zstd/frame_header.x +++ b/xls/modules/zstd/frame_header.x @@ -425,27 +425,17 @@ fn test_parse_frame_content_size() { assert_eq(frame_content_size, u64:0x0); } -// Calculate maximal accepted window_size for given WINDOW_LOG_MAX and return whether given -// window_size should be accepted or discarded. -// Based on window_size calculation from: RFC 8878 -// https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor -fn window_size_valid(window_size: WindowSize) -> bool { - let max_window_size = (WindowSize:1 << WINDOW_LOG_MAX) + (((WindowSize:1 << WINDOW_LOG_MAX) >> WindowSize:3) * MAX_MANTISSA); - - window_size <= max_window_size -} - // Parses a Buffer with data and extracts Frame_Header information. The buffer // is assumed to contain a valid Frame_Header The function returns FrameHeaderResult // with BufferResult that contains outcome of the operations on the Buffer, // FrameHeader with the extracted frame header if the parsing was successful, // and the status of the operation in FrameHeaderStatus. On failure, the returned // buffer is the same as the input buffer. -// WINDOW_LOG_MAX is the base 2 logarithm used for calculating the maximal allowed -// window_size. Frame header parsing function must discard all frames that -// have window_size above the maximal allowed window_size. +// WINDOW_SIZE_MAX is the maximal window size allowed by the decoder and the +// frame header parsing function must discard all frames that +// requires a window size above the maximal allowed window size. // CAPACITY is the buffer capacity -pub fn parse_frame_header(buffer: Buffer) -> FrameHeaderResult { +pub fn parse_frame_header(buffer: Buffer) -> FrameHeaderResult { trace_fmt!("parse_frame_header: ==== Parsing ==== \n"); trace_fmt!("parse_frame_header: initial buffer: {:#x}", buffer); @@ -539,7 +529,7 @@ pub fn parse_frame_header(buffer: Buf buffer: zero!(), header: zero!(), } - } else if (!window_size_valid(header.window_size)) { + } else if (header.window_size > WINDOW_SIZE_MAX) { trace_fmt!("parse_frame_header: frame discarded: window_size to big: {}", header.window_size); FrameHeaderResult { status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, @@ -553,12 +543,13 @@ pub fn parse_frame_header(buffer: Buf // The largest allowed WindowLog for DSLX tests pub const TEST_WINDOW_LOG_MAX = WindowSize:22; +pub const TEST_WINDOW_SIZE_MAX = calc_max_window_size(); #[test] fn test_parse_frame_header() { // normal cases let buffer = Buffer { content: bits[128]:0x1234567890ABCDEF_CAFE_09_C2, length: u32:96 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::OK, buffer: Buffer { @@ -575,7 +566,7 @@ fn test_parse_frame_header() { // SingleSegmentFlag is set and FrameContentSize is bigger than accepted window_size let buffer = Buffer { content: bits[128]:0x1234567890ABCDEF_CAFE_E2, length: u32:88 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, @@ -583,7 +574,7 @@ fn test_parse_frame_header() { }); let buffer = Buffer { content: bits[128]:0xaa20, length: u32:16 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::OK, buffer: Buffer { @@ -600,7 +591,7 @@ fn test_parse_frame_header() { // when buffer is too short let buffer = Buffer { content: bits[128]:0x0, length: u32:0 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::NO_ENOUGH_DATA, buffer: buffer, @@ -608,7 +599,7 @@ fn test_parse_frame_header() { }); let buffer = Buffer { content: bits[128]:0xC2, length: u32:8 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::NO_ENOUGH_DATA, buffer: buffer, @@ -616,7 +607,7 @@ fn test_parse_frame_header() { }); let buffer = Buffer { content: bits[128]:0x09_C2, length: u32:16 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::NO_ENOUGH_DATA, buffer: buffer, @@ -624,7 +615,7 @@ fn test_parse_frame_header() { }); let buffer = Buffer { content: bits[128]:0x1234_09_C2, length: u32:32 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::NO_ENOUGH_DATA, buffer: buffer, @@ -632,7 +623,7 @@ fn test_parse_frame_header() { }); let buffer = Buffer { content: bits[128]:0x1234_09_C2, length: u32:32 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::NO_ENOUGH_DATA, buffer: buffer, @@ -641,7 +632,7 @@ fn test_parse_frame_header() { // when frame header descriptor is corrupted let buffer = Buffer { content: bits[128]:0x1234567890ABCDEF_1234_09_CA, length: u32:96 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::CORRUPTED, buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, @@ -651,7 +642,7 @@ fn test_parse_frame_header() { // Frame Header is discarded because Window size required by frame is too big for given decoder // configuration let buffer = Buffer { content: bits[128]:0xd310, length: u32:16 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, @@ -661,7 +652,7 @@ fn test_parse_frame_header() { // Frame Header is discarded because Frame Content Size required by frame is too big for given decoder // configuration let buffer = Buffer { content: bits[128]:0xf45b5b5b0db1, length: u32:48 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, @@ -676,7 +667,7 @@ fn test_parse_frame_header() { // Frame Header is discarded because Frame Content Size required by frame is too big (above 64bits) for given decoder // configuration let buffer = Buffer { content: bits[128]:0xc0659db6813a16b33f3da53a79e4, length: u32:112 }; - let frame_header_result = parse_frame_header(buffer); + let frame_header_result = parse_frame_header(buffer); assert_eq(frame_header_result, FrameHeaderResult { status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, diff --git a/xls/modules/zstd/frame_header_dec.x b/xls/modules/zstd/frame_header_dec.x index 8647435996..3b8e8ede18 100644 --- a/xls/modules/zstd/frame_header_dec.x +++ b/xls/modules/zstd/frame_header_dec.x @@ -180,15 +180,12 @@ fn test_frame_content_size_exists() { assert_eq(frame_content_size_exists(desc), true); } - -// Calculate maximal accepted window_size for given WINDOW_LOG_MAX and return whether given -// window_size should be accepted or discarded. +// Calculate maximal accepted window_size for given WINDOW_LOG_MAX // Based on window_size calculation from: RFC 8878 // https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor -fn window_size_valid(window_size: WindowSize) -> bool { - let max_window_size = (WindowSize:1 << WINDOW_LOG_MAX) + (((WindowSize:1 << WINDOW_LOG_MAX) >> WindowSize:3) * MAX_MANTISSA); - - window_size <= max_window_size +pub fn calc_max_window_size() -> WindowSize { + const MAX_MANTISSA = WindowSize:0b111; + (WindowSize:1 << WINDOW_LOG_MAX) + (((WindowSize:1 << WINDOW_LOG_MAX) >> WindowSize:3) * MAX_MANTISSA) } @@ -381,7 +378,7 @@ struct FrameHeaderDecoderState { } pub proc FrameHeaderDecoder< - WINDOW_LOG_MAX: u32, + WINDOW_SIZE_MAX: WindowSize, DATA_W: u32, ADDR_W: u32, XFERS_FOR_HEADER: u32 = {((MAX_MAGIC_PLUS_HEADER_LEN * u32:8) / DATA_W) + u32:1}, @@ -430,7 +427,7 @@ pub proc FrameHeaderDecoder< let status = if (!header_ok || !magic_number_ok) { FrameHeaderDecoderStatus::CORRUPTED - } else if (!window_size_valid(decoded_header.window_size)) { + } else if (decoded_header.window_size > WINDOW_SIZE_MAX) { FrameHeaderDecoderStatus::UNSUPPORTED_WINDOW_SIZE } else { FrameHeaderDecoderStatus::OKAY @@ -473,6 +470,7 @@ pub proc FrameHeaderDecoder< // The largest allowed WindowLog for DSLX tests pub const TEST_WINDOW_LOG_MAX = u32:22; +pub const TEST_WINDOW_SIZE_MAX = calc_max_window_size(); pub const TEST_DATA_W = u32:32; pub const TEST_ADDR_W = u32:16; pub const TEST_XFERS_FOR_HEADER = ((MAX_MAGIC_PLUS_HEADER_LEN * u32:8) / TEST_DATA_W) + u32:1; @@ -497,7 +495,7 @@ proc FrameHeaderDecoderTest { let (reader_resp_s, reader_resp_r) = chan("reader_resp"); let (decode_req_s, decode_req_r) = chan("decode_req"); let (decode_resp_s, decode_resp_r) = chan("decode_resp"); - spawn FrameHeaderDecoder( + spawn FrameHeaderDecoder( reader_req_s, reader_resp_r, decode_req_r, @@ -636,6 +634,7 @@ proc FrameHeaderDecoderTest { // https://github.com/facebook/zstd/blob/v1.4.7/lib/decompress/zstd_decompress.c#L296 // Use only in C++ tests when comparing DSLX ZSTD Decoder with libzstd pub const TEST_WINDOW_LOG_MAX_LIBZSTD = u32:30; +pub const TEST_WINDOW_SIZE_MAX_LIBZSTD = calc_max_window_size(); proc FrameHeaderDecoderInst { type Req = FrameHeaderDecoderReq; @@ -655,7 +654,7 @@ proc FrameHeaderDecoderInst { decode_req_r: chan in, decode_resp_s: chan out, ) { - spawn FrameHeaderDecoder( + spawn FrameHeaderDecoder( reader_req_s, reader_resp_r, decode_req_r, diff --git a/xls/modules/zstd/literals_buffer.x b/xls/modules/zstd/literals_buffer.x index 38318fecf1..fb756e383d 100644 --- a/xls/modules/zstd/literals_buffer.x +++ b/xls/modules/zstd/literals_buffer.x @@ -882,7 +882,7 @@ pub proc LiteralsBuffer< next (state: ()) { } } -const INST_HISTORY_BUFFER_SIZE_KB = u32:64; +const INST_HISTORY_BUFFER_SIZE_KB = common::HISTORY_BUFFER_SIZE_KB; const INST_RAM_ADDR_WIDTH = parallel_rams::ram_addr_width(INST_HISTORY_BUFFER_SIZE_KB); const INST_RAM_NUM_PARTITIONS = RAM_NUM_PARTITIONS; const INST_RAM_DATA_WIDTH = RAM_DATA_WIDTH; diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x index d9c58ae478..bf0942109d 100644 --- a/xls/modules/zstd/literals_decoder.x +++ b/xls/modules/zstd/literals_decoder.x @@ -911,7 +911,7 @@ pub proc LiteralsDecoder< next (state: ()) { } } -const ZSTD_HISTORY_BUFFER_SIZE_KB: u32 = u32:64; +const ZSTD_HISTORY_BUFFER_SIZE_KB: u32 = common::HISTORY_BUFFER_SIZE_KB; const ZSTD_RAM_ADDR_WIDTH: u32 = parallel_rams::ram_addr_width(ZSTD_HISTORY_BUFFER_SIZE_KB); const INST_AXI_DATA_W:u32 = u32:64; const INST_AXI_ID_W:u32 = u32:4; diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index bcea49e1ca..ddcc9ae069 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -524,7 +524,7 @@ pub proc SequenceExecutor("fh_req"); let (fh_resp_s, fh_resp_r) = chan("fh_resp"); - spawn frame_header_dec::FrameHeaderDecoder( + spawn frame_header_dec::FrameHeaderDecoder( fh_mem_rd_req_s, fh_mem_rd_resp_r, fh_req_r, fh_resp_s, ); @@ -1418,7 +1418,7 @@ pub proc ZstdDecoder< let (seq_exec_looped_s, seq_exec_looped_r) = chan("seq_exec_looped"); let (output_mem_wr_data_in_s, output_mem_wr_data_in_r) = chan("output_mem_wr_data_in"); - spawn sequence_executor::SequenceExecutor( + spawn sequence_executor::SequenceExecutor( seq_exec_input_r, output_mem_wr_data_in_s, seq_exec_looped_r, seq_exec_looped_s, ram_rd_req_0_s, ram_rd_req_1_s, ram_rd_req_2_s, ram_rd_req_3_s, @@ -1462,11 +1462,10 @@ const INST_AXI_ADDR_W = u32:32; const INST_AXI_ID_W = u32:4; const INST_AXI_DEST_W = u32:4; const INST_REGS_N = u32:16; -const INST_WINDOW_LOG_MAX = u32:30; const INST_HB_ADDR_W = sequence_executor::ZSTD_RAM_ADDR_WIDTH; const INST_HB_DATA_W = sequence_executor::RAM_DATA_WIDTH; const INST_HB_NUM_PARTITIONS = sequence_executor::RAM_NUM_PARTITIONS; -const INST_HB_SIZE_KB = sequence_executor::ZSTD_HISTORY_BUFFER_SIZE_KB; +const INST_HB_SIZE_B = common::HISTORY_BUFFER_SIZE_KB as u64 * u64:1024; const INST_LOG2_REGS_N = std::clog2(INST_REGS_N); const INST_AXI_DATA_W_DIV8 = INST_AXI_DATA_W / u32:8; @@ -1539,7 +1538,7 @@ const INST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE = INST_HUFFMAN_WEIGHTS_T const INST_HUFFMAN_WEIGHTS_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions( INST_HUFFMAN_WEIGHTS_TMP2_RAM_WORD_PARTITION_SIZE, INST_HUFFMAN_WEIGHTS_TMP2_RAM_DATA_W); -const INST_HISTORY_BUFFER_SIZE_KB = u32:64; +const INST_HISTORY_BUFFER_SIZE_KB = common::HISTORY_BUFFER_SIZE_KB; const INST_AXI_CHAN_N = u32:11; // Literals buffer memory parameters @@ -1857,8 +1856,8 @@ proc ZstdDecoderInst { ) { spawn ZstdDecoder< INST_AXI_DATA_W, INST_AXI_ADDR_W, INST_AXI_ID_W, INST_AXI_DEST_W, - INST_REGS_N, INST_WINDOW_LOG_MAX, - INST_HB_ADDR_W, INST_HB_DATA_W, INST_HB_NUM_PARTITIONS, INST_HB_SIZE_KB, + INST_REGS_N, + INST_HB_ADDR_W, INST_HB_DATA_W, INST_HB_NUM_PARTITIONS, INST_HB_SIZE_B, INST_DPD_RAM_ADDR_W, INST_DPD_RAM_DATA_W, INST_DPD_RAM_NUM_PARTITIONS, INST_TMP_RAM_ADDR_W, INST_TMP_RAM_DATA_W, INST_TMP_RAM_NUM_PARTITIONS, diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 854bfa06e2..15976c5092 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -35,8 +35,6 @@ import xls.modules.zstd.data.comp_frame_fse_comp; import xls.modules.zstd.data.comp_frame_fse_repeated; import xls.modules.zstd.data.comp_frame; -const TEST_WINDOW_LOG_MAX = u32:30; - const TEST_AXI_DATA_W = u32:64; const TEST_AXI_ADDR_W = u32:32; const TEST_AXI_ID_W = u32:8; @@ -50,7 +48,7 @@ const TEST_HB_RAM_N = u32:8; const TEST_HB_ADDR_W = sequence_executor::ZSTD_RAM_ADDR_WIDTH; const TEST_HB_DATA_W = sequence_executor::RAM_DATA_WIDTH; const TEST_HB_NUM_PARTITIONS = sequence_executor::RAM_NUM_PARTITIONS; -const TEST_HB_SIZE_KB = sequence_executor::ZSTD_HISTORY_BUFFER_SIZE_KB; +const TEST_HB_SIZE_B = sequence_executor::ZSTD_HISTORY_BUFFER_SIZE_KB as u64 * u64:1024; const TEST_HB_RAM_SIZE = sequence_executor::ZSTD_RAM_SIZE; const TEST_HB_RAM_WORD_PARTITION_SIZE = sequence_executor::RAM_WORD_PARTITION_SIZE; const TEST_HB_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = sequence_executor::TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR; @@ -600,8 +598,8 @@ proc ZstdDecoderTester { spawn zstd_dec::ZstdDecoder< TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_ID_W, TEST_AXI_DEST_W, - TEST_REGS_N, TEST_WINDOW_LOG_MAX, - TEST_HB_ADDR_W, TEST_HB_DATA_W, TEST_HB_NUM_PARTITIONS, TEST_HB_SIZE_KB, + TEST_REGS_N, + TEST_HB_ADDR_W, TEST_HB_DATA_W, TEST_HB_NUM_PARTITIONS, TEST_HB_SIZE_B, TEST_DPD_RAM_ADDR_W, TEST_DPD_RAM_DATA_W, TEST_DPD_RAM_NUM_PARTITIONS, TEST_TMP_RAM_ADDR_W, TEST_TMP_RAM_DATA_W, TEST_TMP_RAM_NUM_PARTITIONS, From 72149318aea9f459b312c003dfd6d9ef2be61852 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Tue, 16 Sep 2025 11:16:30 +0200 Subject: [PATCH 052/159] modules: zstd: dslx tests: check Csr status for errors --- xls/modules/zstd/zstd_dec.x | 2 +- xls/modules/zstd/zstd_dec_test.x | 75 ++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index 3ad3c37ec3..de9387f1ea 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -58,7 +58,7 @@ enum ZstdDecoderInternalFsm: u4 { INVALID = 15, } -enum ZstdDecoderStatus: u5 { +pub enum ZstdDecoderStatus: u5 { IDLE = 0, RUNNING = 1, READ_CONFIG_OK = 2, diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 15976c5092..7f4a75902c 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -259,7 +259,7 @@ proc ZstdDecoderTester { type LitBufRamWrResp = ram::WriteResp; start_r: chan<()> in; - finished_s: chan<()> out; + finished_s: chan out; csr_axi_aw_s: chan out; csr_axi_w_s: chan out; @@ -319,7 +319,7 @@ proc ZstdDecoderTester { init {} - config(start_r: chan<()> in, finished_s: chan<()> out) { + config(start_r: chan<()> in, finished_s: chan out) { let (csr_axi_aw_s, csr_axi_aw_r) = chan("csr_axi_aw"); let (csr_axi_w_s, csr_axi_w_r) = chan("csr_axi_w"); @@ -871,6 +871,35 @@ proc ZstdDecoderTester { trace_fmt!("ZstdDecTest: Handle AXI Write transaction #{}", axi_transaction); let (tok, axi_aw) = recv(tok, output_axi_aw_r); trace_fmt!("ZstdDecTest: Received AXI AW: {:#x}", axi_aw); + + let tok = send(tok, csr_axi_ar_s, axi::AxiAr { + addr: csr_addr(zstd_dec::Csr::STATUS), + id: uN[TEST_AXI_ID_W]:0, + size: axi::AxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: axi::AxiAxBurst::FIXED, + cache: axi::AxiArCache::DEV_BUF, + prot: u3:0, + qos: u4:0, + region: u4:0 + }); + let (tok, csr_response) = recv(tok, csr_axi_r_r); + trace_fmt!("ZstdDecTest: Received Csr status {:#x}", csr_response); + + let csr_status = csr_response.data as u5; + let valid_state = match (csr_status as zstd_dec::ZstdDecoderStatus) { + zstd_dec::ZstdDecoderStatus::FRAME_HEADER_CORRUPTED => false, + zstd_dec::ZstdDecoderStatus::FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE => false, + zstd_dec::ZstdDecoderStatus::BLOCK_HEADER_CORRUPTED => false, + zstd_dec::ZstdDecoderStatus::BLOCK_HEADER_MEMORY_ACCESS_ERROR => false, + zstd_dec::ZstdDecoderStatus::RAW_BLOCK_ERROR => false, + _ => true + }; + + if !valid_state { + send(tok, finished_s, false); + } else {}; + let (tok, internal_output_memory, internal_output_memory_id, internal_transfered_bytes) = for (axi_transfer, (tok, out_mem, out_mem_id, transf_bytes)): (u32, (token, uN[TEST_AXI_DATA_W][TEST_MOCK_OUTPUT_RAM_SIZE], u32, u32)) @@ -916,7 +945,7 @@ proc ZstdDecoderTester { tok }(tok); - send(tok, finished_s, ()); + send(tok, finished_s, true); } } @@ -924,7 +953,7 @@ proc ZstdDecoderTester { proc RawLiteralsPredefinedSequencesTest { terminator: chan out; start_s: chan<()> out; - finished_r: chan<()> in; + finished_r: chan in; init {} @@ -933,7 +962,7 @@ proc RawLiteralsPredefinedSequencesTest { const DECOMPRESSED_FRAMES = comp_frame::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan<()>("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -941,8 +970,8 @@ proc RawLiteralsPredefinedSequencesTest { next(state: ()) { let tok = send(join(), start_s, ()); - let (tok, _) = recv(tok, finished_r); - send(tok, terminator, true); + let (tok, result) = recv(tok, finished_r); + send(tok, terminator, result); } } @@ -950,7 +979,7 @@ proc RawLiteralsPredefinedSequencesTest { proc RleLiteralsRepeatedSequencesTest { terminator: chan out; start_s: chan<()> out; - finished_r: chan<()> in; + finished_r: chan in; init {} @@ -960,7 +989,7 @@ proc RleLiteralsRepeatedSequencesTest { const DECOMPRESSED_FRAMES = comp_frame_fse_repeated::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan<()>("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -968,8 +997,8 @@ proc RleLiteralsRepeatedSequencesTest { next(state: ()) { let tok = send(join(), start_s, ()); - let (tok, _) = recv(tok, finished_r); - send(tok, terminator, true); + let (tok, result) = recv(tok, finished_r); + send(tok, terminator, result); } } @@ -981,7 +1010,7 @@ proc RleLiteralsRepeatedSequencesTest { proc RawHuffmanLiteralsPredefinedSequencesTest_skip { terminator: chan out; start_s: chan<()> out; - finished_r: chan<()> in; + finished_r: chan in; init {} @@ -990,7 +1019,7 @@ proc RawHuffmanLiteralsPredefinedSequencesTest_skip { const DECOMPRESSED_FRAMES = comp_frame_huffman::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan<()>("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -998,8 +1027,8 @@ proc RawHuffmanLiteralsPredefinedSequencesTest_skip { next(state: ()) { let tok = send(join(), start_s, ()); - let (tok, _) = recv(tok, finished_r); - send(tok, terminator, true); + let (tok, result) = recv(tok, finished_r); + send(tok, terminator, result); } } @@ -1007,7 +1036,7 @@ proc RawHuffmanLiteralsPredefinedSequencesTest_skip { proc FseHuffmanLiteralsPredefinedSequencesTest_skip { terminator: chan out; start_s: chan<()> out; - finished_r: chan<()> in; + finished_r: chan in; init {} @@ -1016,7 +1045,7 @@ proc FseHuffmanLiteralsPredefinedSequencesTest_skip { const DECOMPRESSED_FRAMES = comp_frame_huffman_fse::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan<()>("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -1024,8 +1053,8 @@ proc FseHuffmanLiteralsPredefinedSequencesTest_skip { next(state: ()) { let tok = send(join(), start_s, ()); - let (tok, _) = recv(tok, finished_r); - send(tok, terminator, true); + let (tok, result) = recv(tok, finished_r); + send(tok, terminator, result); } } @@ -1033,7 +1062,7 @@ proc FseHuffmanLiteralsPredefinedSequencesTest_skip { proc RawHuffmanLiteralsCompressedSequencesTest_skip { terminator: chan out; start_s: chan<()> out; - finished_r: chan<()> in; + finished_r: chan in; init {} @@ -1043,7 +1072,7 @@ proc RawHuffmanLiteralsCompressedSequencesTest_skip { const DECOMPRESSED_FRAMES = comp_frame_fse_comp::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan<()>("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -1051,7 +1080,7 @@ proc RawHuffmanLiteralsCompressedSequencesTest_skip { next(state: ()) { let tok = send(join(), start_s, ()); - let (tok, _) = recv(tok, finished_r); - send(tok, terminator, true); + let (tok, result) = recv(tok, finished_r); + send(tok, terminator, result); } } From 18f5b6cdc525c9b98dd10db23bb3be096c2396ad Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Tue, 16 Sep 2025 11:37:51 +0200 Subject: [PATCH 053/159] modules: zstd: dslx tests: add e2e test for unsupported window size --- xls/modules/zstd/BUILD | 1 + .../zstd/data/comp_frame_unsupported_window.x | 36 ++++++++++ xls/modules/zstd/zstd_dec_test.x | 70 +++++++++++++------ 3 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 xls/modules/zstd/data/comp_frame_unsupported_window.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index c118f1b7cb..d7474c88ae 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1470,6 +1470,7 @@ xls_dslx_test( size = "enormous", srcs = [ "data/comp_frame.x", + "data/comp_frame_unsupported_window.x", "data/comp_frame_fse_comp.x", "data/comp_frame_fse_repeated.x", "data/comp_frame_huffman.x", diff --git a/xls/modules/zstd/data/comp_frame_unsupported_window.x b/xls/modules/zstd/data/comp_frame_unsupported_window.x new file mode 100644 index 0000000000..c98a50cb61 --- /dev/null +++ b/xls/modules/zstd/data/comp_frame_unsupported_window.x @@ -0,0 +1,36 @@ +// Copyright 2025 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import xls.modules.zstd.common; +type DataArray = common::DataArray; + +// Same data as comp_frame.x, but with too large required window size +// for current ZSTD decoder parameters: 491520 vs 65536 bytes. + +pub const FRAMES:DataArray< + u32:64, + u32:50 +>[1] = [DataArray<64, 50>{ + length: u32:51, + array_length: u32:7, + data: uN[64][50]:[uN[64]:0x00504784fd2fb528, uN[64]:0xcf95700001150000, uN[64]:0xe17d50b989ac93c4, uN[64]:0x0daf000895a6e608, uN[64]:0xb96010b86f7602a4, uN[64]:0x05b0e051238666e8, uN[64]:0x8470e3, uN[64]:0, ...] +}]; +pub const DECOMPRESSED_FRAMES:DataArray< + u32:64, + u32:50 +>[1] = [DataArray<64, 50>{ + length: u32:80, + array_length: u32:10, + data: uN[64][50]:[uN[64]:0xc4c4cf95cf95cf95, uN[64]:0x93c4c4c4c4c4c4c4, uN[64]:0xacc493c493c493c4, uN[64]:0xc493c493c493c489, uN[64]:0x93c493c489acc493, uN[64]:0x08e17d50b9c493c4, uN[64]:0xc4c4c4cf9595a6e6, uN[64]:0x93c493c4c4c4c4c4, uN[64]:0xc489acc493c493c4, uN[64]:0xc493c493c493c493, uN[64]:0, ...] +}]; diff --git a/xls/modules/zstd/zstd_dec_test.x b/xls/modules/zstd/zstd_dec_test.x index 7f4a75902c..1e97154a62 100644 --- a/xls/modules/zstd/zstd_dec_test.x +++ b/xls/modules/zstd/zstd_dec_test.x @@ -34,6 +34,7 @@ import xls.modules.zstd.data.comp_frame_huffman_fse; import xls.modules.zstd.data.comp_frame_fse_comp; import xls.modules.zstd.data.comp_frame_fse_repeated; import xls.modules.zstd.data.comp_frame; +import xls.modules.zstd.data.comp_frame_unsupported_window; const TEST_AXI_DATA_W = u32:64; const TEST_AXI_ADDR_W = u32:32; @@ -259,7 +260,7 @@ proc ZstdDecoderTester { type LitBufRamWrResp = ram::WriteResp; start_r: chan<()> in; - finished_s: chan out; + finished_s: chan out; csr_axi_aw_s: chan out; csr_axi_w_s: chan out; @@ -319,7 +320,7 @@ proc ZstdDecoderTester { init {} - config(start_r: chan<()> in, finished_s: chan out) { + config(start_r: chan<()> in, finished_s: chan out) { let (csr_axi_aw_s, csr_axi_aw_r) = chan("csr_axi_aw"); let (csr_axi_w_s, csr_axi_w_r) = chan("csr_axi_w"); @@ -886,8 +887,9 @@ proc ZstdDecoderTester { let (tok, csr_response) = recv(tok, csr_axi_r_r); trace_fmt!("ZstdDecTest: Received Csr status {:#x}", csr_response); - let csr_status = csr_response.data as u5; - let valid_state = match (csr_status as zstd_dec::ZstdDecoderStatus) { + let csr_status_data = csr_response.data as u5; + let csr_status = csr_status_data as zstd_dec::ZstdDecoderStatus; + let valid_state = match (csr_status) { zstd_dec::ZstdDecoderStatus::FRAME_HEADER_CORRUPTED => false, zstd_dec::ZstdDecoderStatus::FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE => false, zstd_dec::ZstdDecoderStatus::BLOCK_HEADER_CORRUPTED => false, @@ -897,7 +899,7 @@ proc ZstdDecoderTester { }; if !valid_state { - send(tok, finished_s, false); + send(tok, finished_s, csr_status); } else {}; let (tok, internal_output_memory, internal_output_memory_id, internal_transfered_bytes) = @@ -945,7 +947,33 @@ proc ZstdDecoderTester { tok }(tok); - send(tok, finished_s, true); + send(tok, finished_s, zstd_dec::ZstdDecoderStatus::IDLE); + } +} + +#[test_proc] +proc UnsupportedWindowSizeTest { + terminator: chan out; + start_s: chan<()> out; + finished_r: chan in; + + init {} + + config (terminator: chan out,) { + const FRAMES = comp_frame_unsupported_window::FRAMES; + const DECOMPRESSED_FRAMES = comp_frame_unsupported_window::DECOMPRESSED_FRAMES; + + let (start_s, start_r) = chan<()>("start"); + let (finished_s, finished_r) = chan("finished"); + + spawn ZstdDecoderTester(start_r, finished_s); + (terminator, start_s, finished_r) + } + + next(state: ()) { + let tok = send(join(), start_s, ()); + let (tok, result) = recv(tok, finished_r); + send(tok, terminator, result == zstd_dec::ZstdDecoderStatus::FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE); } } @@ -953,7 +981,7 @@ proc ZstdDecoderTester { proc RawLiteralsPredefinedSequencesTest { terminator: chan out; start_s: chan<()> out; - finished_r: chan in; + finished_r: chan in; init {} @@ -962,7 +990,7 @@ proc RawLiteralsPredefinedSequencesTest { const DECOMPRESSED_FRAMES = comp_frame::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -971,7 +999,7 @@ proc RawLiteralsPredefinedSequencesTest { next(state: ()) { let tok = send(join(), start_s, ()); let (tok, result) = recv(tok, finished_r); - send(tok, terminator, result); + send(tok, terminator, result == zstd_dec::ZstdDecoderStatus::IDLE); } } @@ -979,7 +1007,7 @@ proc RawLiteralsPredefinedSequencesTest { proc RleLiteralsRepeatedSequencesTest { terminator: chan out; start_s: chan<()> out; - finished_r: chan in; + finished_r: chan in; init {} @@ -989,7 +1017,7 @@ proc RleLiteralsRepeatedSequencesTest { const DECOMPRESSED_FRAMES = comp_frame_fse_repeated::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -998,7 +1026,7 @@ proc RleLiteralsRepeatedSequencesTest { next(state: ()) { let tok = send(join(), start_s, ()); let (tok, result) = recv(tok, finished_r); - send(tok, terminator, result); + send(tok, terminator, result == zstd_dec::ZstdDecoderStatus::IDLE); } } @@ -1010,7 +1038,7 @@ proc RleLiteralsRepeatedSequencesTest { proc RawHuffmanLiteralsPredefinedSequencesTest_skip { terminator: chan out; start_s: chan<()> out; - finished_r: chan in; + finished_r: chan in; init {} @@ -1019,7 +1047,7 @@ proc RawHuffmanLiteralsPredefinedSequencesTest_skip { const DECOMPRESSED_FRAMES = comp_frame_huffman::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -1028,7 +1056,7 @@ proc RawHuffmanLiteralsPredefinedSequencesTest_skip { next(state: ()) { let tok = send(join(), start_s, ()); let (tok, result) = recv(tok, finished_r); - send(tok, terminator, result); + send(tok, terminator, result == zstd_dec::ZstdDecoderStatus::IDLE); } } @@ -1036,7 +1064,7 @@ proc RawHuffmanLiteralsPredefinedSequencesTest_skip { proc FseHuffmanLiteralsPredefinedSequencesTest_skip { terminator: chan out; start_s: chan<()> out; - finished_r: chan in; + finished_r: chan in; init {} @@ -1045,7 +1073,7 @@ proc FseHuffmanLiteralsPredefinedSequencesTest_skip { const DECOMPRESSED_FRAMES = comp_frame_huffman_fse::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -1054,7 +1082,7 @@ proc FseHuffmanLiteralsPredefinedSequencesTest_skip { next(state: ()) { let tok = send(join(), start_s, ()); let (tok, result) = recv(tok, finished_r); - send(tok, terminator, result); + send(tok, terminator, result == zstd_dec::ZstdDecoderStatus::IDLE); } } @@ -1062,7 +1090,7 @@ proc FseHuffmanLiteralsPredefinedSequencesTest_skip { proc RawHuffmanLiteralsCompressedSequencesTest_skip { terminator: chan out; start_s: chan<()> out; - finished_r: chan in; + finished_r: chan in; init {} @@ -1072,7 +1100,7 @@ proc RawHuffmanLiteralsCompressedSequencesTest_skip { const DECOMPRESSED_FRAMES = comp_frame_fse_comp::DECOMPRESSED_FRAMES; let (start_s, start_r) = chan<()>("start"); - let (finished_s, finished_r) = chan("finished"); + let (finished_s, finished_r) = chan("finished"); spawn ZstdDecoderTester(start_r, finished_s); (terminator, start_s, finished_r) @@ -1081,6 +1109,6 @@ proc RawHuffmanLiteralsCompressedSequencesTest_skip { next(state: ()) { let tok = send(join(), start_s, ()); let (tok, result) = recv(tok, finished_r); - send(tok, terminator, result); + send(tok, terminator, result == zstd_dec::ZstdDecoderStatus::IDLE); } } From a26f4caa980ebf2ee6e125e718a42cf325c7fb43 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Tue, 16 Sep 2025 12:03:38 +0200 Subject: [PATCH 054/159] modules: zstd: cocotb: add separate testing routine for expecting error status --- xls/modules/zstd/zstd_dec_cocotb_common.py | 65 ++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 529040d142..79d4239083 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -719,6 +719,56 @@ async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): return (latency, throughput_bytes, throughput_repacketizer) +async def wait_for_status(cpu, timeout=100): + status = None + while timeout != 0 and status != Status.IDLE: + status_reg = await csr_read(cpu, CSR.STATUS) + status = Status(int.from_bytes(status_reg.data, byteorder="little")) + timeout -= 1 + + return status + +async def expect_status(dut, cpu, expected_status, timeout=100): + (unused_notify_channel, notify_monitor) = connect_xls_channel( + dut, NOTIFY_CHANNEL, NotifyStruct + ) + notify_event = triggers.Event() + set_termination_event(notify_monitor, notify_event, 1) + await notify_event.wait() + + status = await wait_for_status(cpu) + assert status == expected_status, ( + f"Decoder finished with non-expected status: {status.name}" + ) + +async def test_decoder_status(dut, axi_buses, cpu, clock, encoded_file, expected_status): + """Test decoder with zstd-compressed data provided in `encoded_file` + + The output error of the decoder is compared with provided expected error. + """ + + memory_bus = axi_buses["memory"] + + mem_size = MAX_ENCODED_FRAME_SIZE_B + ibuf_addr = 0x0 + obuf_addr = mem_size // 2 + + await reset_dut(dut, 50) + + expected_decoded_frame = data_generator.DecompressFrame(encoded_file.read()) + reference_memory = SparseMemory(mem_size) + reference_memory.write(obuf_addr, expected_decoded_frame) + + # Initialise testbench memory with generated ZSTD frame + memory = AxiRamFromFile( + bus=memory_bus, clock=dut.clk, reset=dut.rst, path=encoded_file.name, size=mem_size + ) + + await configure_decoder(dut, cpu, ibuf_addr, obuf_addr) + await start_decoder(cpu) + + check_status_thread = await cocotb.start(expect_status(dut, cpu, expected_status)) + await check_status_thread async def randomized_testing_routine( dut, @@ -798,6 +848,21 @@ async def pregenerated_testing_routine( ) +async def test_expected_status( + dut, + pregenerated_path, + expected_status +): + (axi_buses, cpu, clock) = prepare_test_environment(dut) + print(f"\nusing pregenerated input file for decoder: {pregenerated_path}\n") + await report_fse_decoder_work(dut, clock) + await report_sequence_executor_work(dut, clock) + await report_fse_table_creator_work(dut, clock) + + with open(pregenerated_path, 'rb') as input_file: + measurement = await test_decoder_status(dut, axi_buses, cpu, clock, input_file, expected_status) + + def run_test(test_module, build_args=[], sim="icarus"): toplevel = "zstd_dec_wrapper" verilog_sources = [ From 49b60034b2b75ed99ffa1e0ddc9f1781c7d30f4e Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Tue, 16 Sep 2025 14:11:09 +0200 Subject: [PATCH 055/159] modules: zstd: cocotb: add script for analyzing failing decoder testcases --- xls/modules/zstd/BUILD | 9 + xls/modules/zstd/zstd_dec_cocotb_common.py | 38 +- xls/modules/zstd/zstd_test_debugger.py | 743 +++++++++++++++++++++ 3 files changed, 777 insertions(+), 13 deletions(-) create mode 100644 xls/modules/zstd/zstd_test_debugger.py diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index d7474c88ae..f6bd3ca450 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1428,6 +1428,14 @@ py_binary( ], ) +py_binary( + name = "zstd_test_debugger", + srcs = ["zstd_test_debugger.py"], + main = "zstd_test_debugger.py", + tags = ["manual"], + visibility = ["//xls:xls_users"], +) + genrule( name = "zstd_test_frames_generate", srcs = [], @@ -1782,6 +1790,7 @@ py_library( ":axi_crossbar_wrapper.v", ":patch_zstd_dec", ":zstd_dec_xx_fse_default", + "//xls/modules/zstd:zstd_test_debugger", "//xls/modules/zstd/rtl:ram_1r1w.v", "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", "//xls/modules/zstd/rtl:zstd_dec_wrapper.sv", diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 79d4239083..405f3b4a6f 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -50,8 +50,11 @@ from cocotbext.axi.axi_ram import AxiRam from cocotbext.axi.sparse_memory import SparseMemory +from pathlib import Path + import xls.modules.zstd.cocotb.channel as xlschannel import xls.modules.zstd.cocotb.utils as cocotb_utils +from xls.modules.zstd.zstd_test_debugger import debug_file from xls.modules.zstd.cocotb import data_generator from xls.modules.zstd.cocotb.memory import AxiRamFromFile from xls.modules.zstd.cocotb import xlsstruct @@ -641,7 +644,7 @@ async def check_status(dut, cpu, timeout=100): ) -async def check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock): +async def check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock, encoded_file): # Read decoded frame in chunks of AXI_DATA_W length # Compare against frame decompressed with the reference library current_addr = obuf_addr @@ -650,19 +653,28 @@ async def check_output(expected_packet_count, memory, reference_memory, output_m decode_first_packet = get_clock_time(clock) for read_op in range(0, expected_packet_count): - current_addr = obuf_addr + (read_op * AXI_DATA_W_BYTES) + decoded_bytes_index = (read_op * AXI_DATA_W_BYTES) + current_addr = obuf_addr + decoded_bytes_index exp_mem_contents = int.from_bytes(reference_memory.read(current_addr, AXI_DATA_W_BYTES), byteorder="little") mem_contents = (await output_monitor.recv()).wdata - assert mem_contents == exp_mem_contents, ( - "{} bytes of memory contents at address {} " - "don't match the expected contents:\n" - "{}\nvs\n{}" - ).format( - AXI_DATA_W_BYTES, - hex(current_addr), - hex(mem_contents), - hex(exp_mem_contents), - ) + if mem_contents != exp_mem_contents: + incorrect_byte_index = 0 + for byte_index in range(AXI_DATA_W_BYTES): + if (mem_contents >> (byte_index * 8) & 0xFF) != (exp_mem_contents >> (byte_index * 8) & 0xFF): + incorrect_byte_index = decoded_bytes_index + 7 - incorrect_byte_index + break + + debug_file(encoded_file, incorrect_byte_index, Path(encoded_file).name) + assert False, ( + "{} bytes of memory contents at address {} " + "don't match the expected contents:\n" + "{}\nvs\n{}" + ).format( + AXI_DATA_W_BYTES, + hex(current_addr), + hex(mem_contents), + hex(exp_mem_contents), + ) print(f'[cocotb] Got correct packet (addr: {hex(current_addr)}, data: {hex(mem_contents)}, clk: {get_clock_time(clock)})', file=sys.stderr) decode_last_packet = get_clock_time(clock) @@ -701,7 +713,7 @@ async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): await start_decoder(cpu) check_status_thread = await cocotb.start(check_status(dut,cpu)) - check_output_thread = await cocotb.start(check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock)) + check_output_thread = await cocotb.start(check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock, encoded_file.name)) decode_times = await check_output_thread (decode_start, decode_first_packet, decode_last_packet) = decode_times await check_status_thread diff --git a/xls/modules/zstd/zstd_test_debugger.py b/xls/modules/zstd/zstd_test_debugger.py new file mode 100644 index 0000000000..95c26a49ad --- /dev/null +++ b/xls/modules/zstd/zstd_test_debugger.py @@ -0,0 +1,743 @@ +# Copyright 2025 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""ZSTD test debugger for cocotb tests. + +This module iterates over given ZSTD-encoded file and finds +the executed sequence which is decoded into the nth byte +specified with `--byte` flag. It skips decoding literals +and executing sequences, because it only cares about how +much data is decoded in each block and from which sequence +comes given decoded byte. + +For ZSTD reference, see https://www.rfc-editor.org/rfc/rfc8878.html +""" + +import argparse +import pathlib +import sys +from enum import Enum + +class BlockType(Enum): + RAW_BLOCK = 0 + RLE_BLOCK = 1 + COMPRESSED_BLOCK = 2 + +class BlockHeader: + def __init__(self, last_block, block_type, block_content_size): + self.last_block = last_block + self.block_type = block_type + self.block_content_size = block_content_size + +class LiteralsBlockType(Enum): + RAW_LITERALS_BLOCK = 0 + RLE_LITERALS_BLOCK = 1 + COMPRESSED_LITERALS_BLOCK = 2 + TREELESS_LITERALS_BLOCK = 3 + +class LiteralsSectionHeader: + def __init__(self, block_type, regenerated_size, compressed_size = 0, has_four_streams = False): + self.block_type = block_type + self.regenerated_size = regenerated_size + self.compressed_size = compressed_size + self.has_four_streams = has_four_streams + +def read_magic_number(encoded_file): + magic_number = int.from_bytes(encoded_file.read(4)) + print(f"Magic number: {hex(magic_number)}") + +def read_frame_header(encoded_file): + frame_header_descriptor = int.from_bytes(encoded_file.read(1)) + print(f"Frame header descriptor: {hex(frame_header_descriptor)}") + + # 6th bit of a frame header indicates if a window descriptor is present + SINGLE_SEGMENT_FLAG_MASK = 0b100000 + if frame_header_descriptor & SINGLE_SEGMENT_FLAG_MASK == 0: + # Calculate a required window_size for the encoded file + # and compare it with the ZSTD decoder history buffer size. + # Based on window_size calculation from: RFC 8878 + # https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor + window_descriptor = int.from_bytes(encoded_file.read(1)) + + MANTISSA_BITS = 0b111 + mantissa = window_descriptor & MANTISSA_BITS + + EXPONENT_BITS = 0b11111000 + exponent = (window_descriptor & EXPONENT_BITS) >> 3 + + window_log = 10 + exponent + window_base = 1 << window_log + window_add = (window_base / 8) * mantissa + window_size = int(window_base + window_add) + print(f"Required window size: {window_size} bytes") + + DICTIONARY_ID_MASK = 0b11 + if frame_header_descriptor & DICTIONARY_ID_MASK != 0: + dictionary_id_size = 2 ** ((frame_header_descriptor & DICTIONARY_ID_MASK) - 1) + dictionary_id = encoded_file.read(dictionary_id_size) + print(f"Dictionary ID: {dictionary_id.hex()}") + + frame_content_size_flag = frame_header_descriptor >> 6 + if frame_content_size_flag == 0: + fcs_field_size = (frame_header_descriptor & SINGLE_SEGMENT_FLAG_MASK) != 0 + else: + fcs_field_size = 2 ** frame_content_size_flag + + if fcs_field_size != 0: + frame_content_size = int.from_bytes(encoded_file.read(fcs_field_size), byteorder="little") + print(f"Frame content used bytes: {fcs_field_size}") + print(f"Frame content size: {frame_content_size}") + +def read_block_header(encoded_file, block_starting_byte_index): + encoded_file.seek(block_starting_byte_index) + block_header_bytes = encoded_file.read(3) + block_header = int.from_bytes(block_header_bytes, byteorder="little") + + LAST_BLOCK_FLAG_MASK = 0b1 + last_block_flag = block_header & LAST_BLOCK_FLAG_MASK + + BLOCK_TYPE_FLAG_MASK = 0b110 + block_type_flag = (block_header & BLOCK_TYPE_FLAG_MASK) >> 1 + + block_size = block_header >> 3 + block_content_size = block_size + if block_type_flag == BlockType.RLE_BLOCK: + block_content_size = 1 + + return BlockHeader(last_block_flag, block_type_flag, block_content_size) + +def find_block_containing_byte_index(encoded_file, byte_index): + current_byte_index = encoded_file.tell() + while current_byte_index < byte_index: + last_block_starting_byte_index = current_byte_index + block_header = read_block_header(encoded_file, current_byte_index) + print(f"Block at {last_block_starting_byte_index}:") + print(f" Last block flag: {block_header.last_block}") + print(f" Block content type: {block_header.block_type}") + print(f" Block content size: {block_header.block_content_size}") + current_byte_index = encoded_file.tell() + block_header.block_content_size + encoded_file.seek(current_byte_index) + + print(f"Block containing index is located at position {last_block_starting_byte_index}") + + return last_block_starting_byte_index + +def read_literals_section_header(encoded_file): + literal_section_header = encoded_file.read(1) + + LITERALS_BLOCK_TYPE_MASK = 0b11 + literals_block_type = LiteralsBlockType(literal_section_header[0] & LITERALS_BLOCK_TYPE_MASK) + + size_format = (literal_section_header[0] >> 2) & 0b11 + if (literals_block_type == LiteralsBlockType.RAW_LITERALS_BLOCK or + literals_block_type == LiteralsBlockType.RLE_LITERALS_BLOCK): + + if size_format == 0b00 or size_format == 0b10: + regenerated_size = literal_section_header[0] >> 3 + + return LiteralsSectionHeader(literals_block_type, regenerated_size) + + if size_format == 0b01: + literal_section_header_second_byte = encoded_file.read(1) + + regenerated_size = (literal_section_header[0] >> 4) + (literal_section_header_second_byte[0] << 4) + + return LiteralsSectionHeader(literals_block_type, regenerated_size) + + if size_format == 0b11: + literal_section_header_remaining_bytes = encoded_file.read(2) + + regenerated_size = ((literal_section_header[0] >> 4) + + (literal_section_header_remaining_bytes[0] << 4) + + (literal_section_header_remaining_bytes[1] << 12)) + + return LiteralsSectionHeader(literals_block_type, regenerated_size) + + if (literals_block_type == LiteralsBlockType.COMPRESSED_LITERALS_BLOCK or + literals_block_type == LiteralsBlockType.TREELESS_LITERALS_BLOCK): + + if size_format == 0b00 or size_format == 0b01: + literal_section_header_remaining_bytes = encoded_file.read(2) + + regenerated_size = (literal_section_header[0] >> 4) + ((literal_section_header_remaining_bytes[0] & 0b111111) << 4) + compressed_size = (literal_section_header_remaining_bytes[0] >> 6) + (literal_section_header_remaining_bytes[1] << 2) + has_four_streams = size_format == 0b01 + + return LiteralsSectionHeader(literals_block_type, regenerated_size, compressed_size, has_four_streams) + + if size_format == 0b10: + literal_section_header_remaining_bytes = encoded_file.read(3) + + regenerated_size = ((literal_section_header[0] >> 4) + + (literal_section_header_remaining_bytes[0] << 4) + + ((literal_section_header_remaining_bytes[1] & 0b11) << 12)) + compressed_size = ((literal_section_header_remaining_bytes[1] >> 2) + + (literal_section_header_remaining_bytes[2] << 6)) + has_four_streams = True + + return LiteralsSectionHeader(literals_block_type, regenerated_size, compressed_size, has_four_streams) + + if size_format == 0b11: + literal_section_header_remaining_bytes = encoded_file.read(4) + + regenerated_size = ((literal_section_header[0] >> 4) + + (literal_section_header_remaining_bytes[0] << 4) + + ((literal_section_header_remaining_bytes[1] & 0b111111) << 12)) + compressed_size = ((literal_section_header_remaining_bytes[1] >> 6) + + (literal_section_header_remaining_bytes[2] << 2) + + (literal_section_header_remaining_bytes[3] << 10)) + has_four_streams = True + + return LiteralsSectionHeader(literals_block_type, regenerated_size, compressed_size, has_four_streams) + +class SymbolCompressionMode(Enum): + PREDEFINED_MODE = 0 + RLE_MODE = 1 + FSE_COMPRESSED_MODE = 2 + REPEAT_MODE = 3 + +# The predefined FSE distribution tables for PREDEFINED_MODE +# https://datatracker.ietf.org/doc/html/rfc8878#name-default-distributions +LITERALS_LENGTH_DEFAULT_DIST = [ + 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1, -1, -1, -1, -1] +MATCH_LENGTHS_DEFAULT_DIST = [ + 1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1] +OFFSET_CODES_DEFAULT_DIST = [ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1] + +class SequencesSectionHeader: + def __init__(self, num_of_sequences = 0, symbol_compression_modes = 0): + self.num_of_sequences = num_of_sequences + + self.literal_lengths_mode = SymbolCompressionMode(symbol_compression_modes >> 6) + self.offsets_mode = SymbolCompressionMode((symbol_compression_modes >> 4) & 0b11) + self.match_lengths_mode = SymbolCompressionMode((symbol_compression_modes >> 2) & 0b11) + +def read_sequences_section_header(encoded_file): + byte0 = encoded_file.read(1)[0] + if byte0 == 0: + return SequencesSectionHeader() + + num_of_sequences = 0 + if byte0 < 128: + num_of_sequences = byte0 + elif byte0 < 255: + byte1 = encoded_file.read(1)[0] + num_of_sequences = ((byte0 - 128) << 8) + byte1 + elif byte0 == 255: + remaining_bytes = encoded_file.read(2) + byte1 = remaining_bytes[0] + byte2 = remaining_bytes[1] + num_of_sequences = byte1 + (byte2 << 8) + 0x7F00 + + symbol_compression_modes = encoded_file.read(1)[0] + + return SequencesSectionHeader(num_of_sequences, symbol_compression_modes) + +class BitReader: + def __init__(self, file): + self.file = file + self.byte_pos = file.tell() + self.current_byte = None + self.bitpos = 0 + + def read_bits(self, n): + if self.current_byte == None: + self.current_byte = self.file.read(1)[0] + + result = 0 + bits_read = 0 + while bits_read < n: + remaining_bits_in_byte = 8 - self.bitpos + bits_to_take = min(n - bits_read, remaining_bits_in_byte) + mask = (1 << bits_to_take) - 1 + bits = (self.current_byte >> self.bitpos) & mask + result |= bits << bits_read + bits_read += bits_to_take + self.bitpos += bits_to_take + if self.bitpos == 8: + self.current_byte = self.file.read(1)[0] + self.byte_pos += 1 + self.bitpos = 0 + + return result + + def rewind_bits(self, n): + self.bitpos -= n + while self.bitpos < 0: + self.byte_pos -= 1 + self.bitpos += 8 + + self.file.seek(self.byte_pos) + self.current_byte = self.file.read(1)[0] + + def read_previous_bits(self, n): + self.rewind_bits(n) + bits = self.read_bits(n) + self.rewind_bits(n) + return bits + +# Explanation of FSE tables can be found here +# https://datatracker.ietf.org/doc/html/rfc8878#name-fse +class FSETable(): + def __init__(self, accuracy_log, num_symbols, symbol_frequencies): + self.accuracy_log = accuracy_log + + table_size = 1 << accuracy_log + self.symbols = [0] * table_size + self.num_of_bits = [0] * table_size + self.baseline = [0] * table_size + self.current_state = 0 + + MAX_SYMBOLS = 255 + state = [0] * MAX_SYMBOLS + + # Allocate positions for 'less than 1' probability symbols starting at the end + # https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1-12 + last_element_index = table_size + for symbol_index in range(num_symbols): + if symbol_frequencies[symbol_index] == -1: + last_element_index -= 1 + self.symbols[last_element_index] = symbol_index + state[symbol_index] = 1 + + symbol_table_position = 0 + symbol_table_position_step = (table_size >> 1) + (table_size >> 3) + 3 + table_size_mask = table_size - 1 + for symbol_index in range(num_symbols): + # Skip if already occupied by less than 1 + # https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1-15 + if (symbol_frequencies[symbol_index] <= 0): + continue + + state[symbol_index] = symbol_frequencies[symbol_index] + + # A symbol occupies as many positions as its probability + # https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1-18 + for index in range(symbol_frequencies[symbol_index]): + self.symbols[symbol_table_position] = symbol_index + + # Calculating symbol table position + # https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1-15 + symbol_table_position = (symbol_table_position + symbol_table_position_step) & table_size_mask + while symbol_table_position >= last_element_index: + symbol_table_position = (symbol_table_position + symbol_table_position_step) & table_size_mask + + # Assigning symbols baselines + # https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1-19 + for index in range(table_size): + symbol = self.symbols[index] + state_value = state[symbol] + state[symbol] += 1 + self.num_of_bits[index] = accuracy_log - highest_set_bit(state_value) + self.baseline[index] = (state_value << self.num_of_bits[index]) - table_size + + def update_table_state(self, bit_reader: BitReader): + # Updating FSE table state + # https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1-21 + bits = self.num_of_bits[self.current_state] + value = bit_reader.read_previous_bits(bits) + self.current_state = self.baseline[self.current_state] + value + + def get_symbol(self): + return self.symbols[self.current_state] + +class RLETable(): + def __init__(self, symbol): + self.symbol = symbol + self.accuracy_log = 0 + + def update_table_state(self, bit_reader: BitReader): + self.nop = 0 + + def get_symbol(self): + return self.symbol + +def highest_set_bit(num): + if num == 0: + return -1 + + return num.bit_length() - 1 + +def fill_mask(num_of_bits): + return (1 << num_of_bits) - 1 + +def use_less_bits_if_needed(bits, + decoded_value, + bit_reader, + remaining_probabilities): + # Find if the value can be stored with lower num of bits + # https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1-4 + lower_bits = bits - 1 + lower_bits_mask = fill_mask(lower_bits) + cutoff = fill_mask(bits) - remaining_probabilities - 1 + low = decoded_value & lower_bits_mask + if low < cutoff: + bit_reader.rewind_bits(1) + return low + elif decoded_value > lower_bits_mask: + return decoded_value - cutoff + + return decoded_value + +# Reading FSE table +def read_fse_table(encoded_file): + bit_reader = BitReader(encoded_file) + accuracy_log = bit_reader.read_bits(4) + 5 + + remaining_probabilities = 1 << accuracy_log + MAX_SYMBOLS = 255 + distribution_table = [0] * MAX_SYMBOLS + current_symbol = 0 + while remaining_probabilities > 0: + # Read value for symbol with highest possible num of bits + bits = highest_set_bit(remaining_probabilities + 1) + 1 + decoded_value = bit_reader.read_bits(bits) + decoded_value = use_less_bits_if_needed(bits, decoded_value, bit_reader, remaining_probabilities) + + symbol_probability = decoded_value - 1 + distribution_table[current_symbol] = symbol_probability + remaining_probabilities -= abs(symbol_probability) + + current_symbol += 1 + + # Handle 0 value probabilities + # https://datatracker.ietf.org/doc/html/rfc8878#section-4.1.1-7 + if symbol_probability == 0: + repeat = bit_reader.read_bits(2) + + while repeat > 0: + for _ in range(repeat): + distribution_table[current_symbol] = 0 + current_symbol += 1 + + if repeat == 3: + repeat = bit_reader.read_bits(2) + else: + repeat = 0 + + # Reset encoded file reader by skipping over remaining bits + if bit_reader.bitpos == 0: + encoded_file.seek(encoded_file.tell() - 1) + + return FSETable(accuracy_log, current_symbol, distribution_table) + +# Sequences baselines and extra bits +# https://datatracker.ietf.org/doc/html/rfc8878#name-sequence-codes-for-lengths- +LITERALS_LENGTH_BASELINES = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24, 28, + 32, 40, 48, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536] +LITERALS_LENGTH_NUM_OF_BITS = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] +MATCH_LENGTH_BASELINES = [ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, + 131, 259, 515, 1027, 2051, 4099, 8195, 16387, 32771, 65539] +MATCH_LENGTH_NUM_OF_BITS = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] + +class DecodedSequence(): + def __init__(self, literal_length, offset_length, match_length): + self.literal_length = literal_length + self.offset_length = offset_length + self.match_length = match_length + + def __str__(self): + return f"LL:{self.literal_length}, OF:{self.offset_length}, ML:{self.match_length}" + +def decode_sequence(bit_reader: BitReader, + literal_lengths_fse_table, + offsets_fse_table, + match_lengths_fse_table, + last_sequence): + of_code = offsets_fse_table.get_symbol() + ll_code = literal_lengths_fse_table.get_symbol() + ml_code = match_lengths_fse_table.get_symbol() + + offset = (1 << of_code) + bit_reader.read_previous_bits(of_code) + match_length = MATCH_LENGTH_BASELINES[ml_code] + bit_reader.read_previous_bits(MATCH_LENGTH_NUM_OF_BITS[ml_code]) + literal_length = LITERALS_LENGTH_BASELINES[ll_code] + bit_reader.read_previous_bits(LITERALS_LENGTH_NUM_OF_BITS[ll_code]) + + if not last_sequence: + literal_lengths_fse_table.update_table_state(bit_reader) + match_lengths_fse_table.update_table_state(bit_reader) + offsets_fse_table.update_table_state(bit_reader) + + return DecodedSequence(literal_length, offset, match_length) + +def decode_sequences(encoded_file, + num_sequences, + last_block_byte_index, + literal_lengths_fse_table, + offsets_fse_table, + match_lengths_fse_table): + encoded_file.seek(last_block_byte_index) + last_block_byte = encoded_file.read(1)[0] + encoded_file.seek(last_block_byte_index) + bit_reader = BitReader(encoded_file) + padding = highest_set_bit(last_block_byte) + if padding > 0: + bit_reader.read_bits(padding) + + literal_lengths_fse_table.current_state = bit_reader.read_previous_bits(literal_lengths_fse_table.accuracy_log) + offsets_fse_table.current_state = bit_reader.read_previous_bits(offsets_fse_table.accuracy_log) + match_lengths_fse_table.current_state = bit_reader.read_previous_bits(match_lengths_fse_table.accuracy_log) + + decoded_sequences = [] + for sequence in range(num_sequences): + decoded_sequences.append(decode_sequence(bit_reader, literal_lengths_fse_table, offsets_fse_table, match_lengths_fse_table, sequence == (num_sequences - 1))) + + return decoded_sequences + +# Calculating repeat offsets +# https://datatracker.ietf.org/doc/html/rfc8878#name-repeat-offsets +repeat_offsets = [1, 4, 8] +def handle_repeated_offsets(decoded_sequence): + global repeat_offsets + offset = -1 + + if decoded_sequence.offset_length > 3: + # Simple case where we subtract 3 from the offset + # https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.4-4.1 + offset = decoded_sequence.offset_length - 3 + + repeat_offsets[2] = repeat_offsets[1] + repeat_offsets[1] = repeat_offsets[0] + repeat_offsets[0] = offset + elif decoded_sequence.literal_length != 0: + # Rotate repeated offsets + # https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.5-8 + if decoded_sequence.offset_length == 1: + offset == repeat_offsets[0] + else: + if decoded_sequence.offset_length == 3: + repeat_offsets[2] = repeat_offsets[1] + + repeat_offsets[1] = repeat_offsets[0] + repeat_offsets[0] = offset + else: + # Special case when LL is 0, then repeat offsets are shifted by one + # https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.5-3 + offset_index = decoded_sequence.offset_length + + if offset_index < 3: + offset = repeat_offsets[offset_index] + else: + # 'an offset_value of 3 means Repeated_Offset1 - 1_byte' + offset = repeat_offsets[0] - 1 + + if offset_index > 1: + repeat_offsets[2] = repeat_offsets[1] + + repeat_offsets[1] = repeat_offsets[0] + repeat_offsets[0] = offset + + decoded_sequence.offset_length = offset + + +literal_lengths_fse_table = None +offsets_fse_table = None +match_lengths_fse_table = None +block_cnt = 0 + +def debug_block(encoded_file, current_decoded_data_size, searched_for_byte_index, test_name): + global block_cnt + global literal_lengths_fse_table + global offsets_fse_table + global match_lengths_fse_table + + block_starting_byte_index = encoded_file.tell() + block_header = read_block_header(encoded_file, block_starting_byte_index) + + print(f"Debugging {block_cnt} block at {block_starting_byte_index}") + print("Block:") + print(f" Block content size: {block_header.block_content_size}") + last_block_byte_index = encoded_file.tell() + block_header.block_content_size - 1 + print(f" Block last byte: {last_block_byte_index}") + + current_byte_index = encoded_file.tell() + literals_section_header = read_literals_section_header(encoded_file) + literals_section_header_size = encoded_file.tell() - current_byte_index + + print(f" Literals header size: {literals_section_header_size}") + print(f" Literals block type: {literals_section_header.block_type}") + print(f" Compressed size: {literals_section_header.compressed_size}") + print(f" Has four streams: {literals_section_header.has_four_streams}") + + literals_section_content_size = 0 + if literals_section_header.block_type == LiteralsBlockType.RAW_LITERALS_BLOCK: + literals_section_content_size = literals_section_header.regenerated_size + literals_block = encoded_file.read(literals_section_content_size) + + if literals_section_header.block_type == LiteralsBlockType.RLE_LITERALS_BLOCK: + print(f" Literals block contains a single byte which should be repeated {literals_section_header.regenerated_size} times") + literals_section_content_size = 1 + literals_block = encoded_file.read(literals_section_content_size) + + if literals_section_header.block_type == LiteralsBlockType.COMPRESSED_LITERALS_BLOCK: + literals_section_content_size = literals_section_header.compressed_size + literals_block = encoded_file.read(literals_section_content_size) + + if literals_section_header.block_type == LiteralsBlockType.TREELESS_LITERALS_BLOCK: + literals_section_content_size = literals_section_header.compressed_size + literals_block = encoded_file.read(literals_section_content_size) + + print(f" Literals section content size {literals_section_content_size}") + + sequences_block_content_size = block_header.block_content_size - literals_section_content_size - literals_section_header_size + print(f" Sequences block content size: {sequences_block_content_size}") + + sequences_section_header = read_sequences_section_header(encoded_file) + print(f" Num of sequences: {sequences_section_header.num_of_sequences}") + print(f" Literal lengths mode: {sequences_section_header.literal_lengths_mode}") + print(f" Offsets mode: {sequences_section_header.offsets_mode}") + print(f" Match lengths mode: {sequences_section_header.match_lengths_mode}") + + if sequences_section_header.num_of_sequences == 0: + return literals_section_header.regenerated_size + + # Order of values matches: literal lengths, offsets and match lengths + # Default distribution lengths and accuracies used by the PREDEFINED_MODE + # https://datatracker.ietf.org/doc/html/rfc8878#name-default-distributions + # LL OF ML + # Lengths: 36 29 53 + # Accuracies: 6 5 6 + + if sequences_section_header.literal_lengths_mode == SymbolCompressionMode.PREDEFINED_MODE: + literal_lengths_fse_table = FSETable(6, 36, LITERALS_LENGTH_DEFAULT_DIST) + elif sequences_section_header.literal_lengths_mode == SymbolCompressionMode.RLE_MODE: + literal_lengths_fse_table = RLETable(encoded_file.read(1)[0]) + elif sequences_section_header.literal_lengths_mode == SymbolCompressionMode.FSE_COMPRESSED_MODE: + literal_lengths_fse_table = read_fse_table(encoded_file) + + if sequences_section_header.offsets_mode == SymbolCompressionMode.PREDEFINED_MODE: + offsets_fse_table = FSETable(5, 29, OFFSET_CODES_DEFAULT_DIST) + elif sequences_section_header.offsets_mode == SymbolCompressionMode.RLE_MODE: + offsets_fse_table = RLETable(encoded_file.read(1)[0]) + elif sequences_section_header.offsets_mode == SymbolCompressionMode.FSE_COMPRESSED_MODE: + offsets_fse_table = read_fse_table(encoded_file) + + if sequences_section_header.match_lengths_mode == SymbolCompressionMode.PREDEFINED_MODE: + match_lengths_fse_table = FSETable(6, 53, MATCH_LENGTHS_DEFAULT_DIST) + elif sequences_section_header.match_lengths_mode == SymbolCompressionMode.RLE_MODE: + match_lengths_fse_table = RLETable(encoded_file.read(1)[0]) + elif sequences_section_header.match_lengths_mode == SymbolCompressionMode.FSE_COMPRESSED_MODE: + match_lengths_fse_table = read_fse_table(encoded_file) + + print(f" LL accuracy log: {literal_lengths_fse_table.accuracy_log}") + print(f" OF accuracy log: {offsets_fse_table.accuracy_log}") + print(f" ML accuracy log: {match_lengths_fse_table.accuracy_log}") + + decoded_sequences = decode_sequences(encoded_file, + sequences_section_header.num_of_sequences, + last_block_byte_index, + literal_lengths_fse_table, + offsets_fse_table, + match_lengths_fse_table) + + total_literal_lengths = 0 + total_match_lengths = 0 + for (index, decoded_sequence) in enumerate(decoded_sequences): + + comes_from_match = False + comes_from_literal = False + if current_decoded_data_size + decoded_sequence.match_length >= searched_for_byte_index: + comes_from_match = True + elif current_decoded_data_size + decoded_sequence.match_length + decoded_sequence.literal_length >= searched_for_byte_index: + comes_from_literal = True + + total_literal_lengths += decoded_sequence.literal_length + total_match_lengths += decoded_sequence.match_length + current_decoded_data_size += decoded_sequence.literal_length + decoded_sequence.match_length + + handle_repeated_offsets(decoded_sequence) + + if current_decoded_data_size >= searched_for_byte_index: + insert_vertical_separation() + + if comes_from_match: + origin = "history match" + elif comes_from_literal: + origin = "literal copying" + else: + origin = "" + + print(f"** Byte {searched_for_byte_index} comes from the block {block_cnt} from sequence {index} from the block at byte index {block_starting_byte_index}") + print(f"** The copied value comes from {origin} part of the sequence: ") + print(f"** LL: {decoded_sequence.literal_length} OF: {decoded_sequence.offset_length} ML: {decoded_sequence.match_length} (after handling repeated offsets)") + print(f"|| {test_name:>15} | 0x{searched_for_byte_index:016X} | {block_cnt:>15} | {index:>20} | {str(decoded_sequence):>30} | {origin:>15} ||\n") + + block_cnt += 1 + return (literals_section_header.regenerated_size + total_literal_lengths + total_match_lengths, True) + + decoded_content_size = literals_section_header.regenerated_size + total_match_lengths + + print(f" Total match lengths: {total_match_lengths}") + print(f" Literals size: {literals_section_header.regenerated_size}") + print(f" Decoded content size: {decoded_content_size}") + + encoded_file.seek(last_block_byte_index + 1) + + block_cnt += 1 + return (decoded_content_size, block_header.last_block) + +def insert_vertical_separation(): + print() + +def debug_file(file_path, byte_index, test_name): + with open(file_path, "rb") as encoded_file: + read_magic_number(encoded_file) + read_frame_header(encoded_file) + insert_vertical_separation() + + total_decoded_content_size = 0 + finished = False + while not finished: + print(f"Currently decoded {total_decoded_content_size} bytes of data") + decoded_content_size, finished = debug_block(encoded_file, total_decoded_content_size, byte_index, test_name) + total_decoded_content_size += decoded_content_size + finished = finished or total_decoded_content_size >= byte_index + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--file", + help="Filename of the tested ZSTD encoded file", + type=pathlib.Path, + required=True + ) + parser.add_argument( + "--name", + help="Test name", + default="name" + ) + parser.add_argument( + "--byte", + help="Byte which information will be printed", + type=int, + required=True + ) + args = parser.parse_args() + debug_file(args.file, args.byte, args.name) + +if __name__ == "__main__": + main() From 3769ce80a9a107cc622827e123eb2eedaf994620 Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Tue, 23 Sep 2025 15:05:02 +0200 Subject: [PATCH 056/159] modules: zstd: cocotb: extract FseTableRecord --- xls/modules/zstd/cocotb/BUILD | 8 ++++++++ xls/modules/zstd/cocotb/fse_table_record.py | 12 ++++++++++++ xls/modules/zstd/zstd_dec_cocotb_common.py | 1 + 3 files changed, 21 insertions(+) create mode 100644 xls/modules/zstd/cocotb/fse_table_record.py diff --git a/xls/modules/zstd/cocotb/BUILD b/xls/modules/zstd/cocotb/BUILD index 6c08efa697..738fcc7833 100644 --- a/xls/modules/zstd/cocotb/BUILD +++ b/xls/modules/zstd/cocotb/BUILD @@ -74,3 +74,11 @@ py_library( "@zstd//:zstd_cli", ], ) + +py_library( + name = "fse_table_record", + srcs = ["fse_table_record.py"], + deps = [ + ":xlsstruct" + ] +) diff --git a/xls/modules/zstd/cocotb/fse_table_record.py b/xls/modules/zstd/cocotb/fse_table_record.py new file mode 100644 index 0000000000..eaf35f789a --- /dev/null +++ b/xls/modules/zstd/cocotb/fse_table_record.py @@ -0,0 +1,12 @@ +from xls.modules.zstd.cocotb import xlsstruct + + +SYMBOL_W = 8 +NUM_OF_BITS_W = 8 +BASE_W = 16 + +@xlsstruct.xls_dataclass +class FseTableRecord(xlsstruct.XLSStruct): + base: BASE_W + num_of_bits: NUM_OF_BITS_W + symbol: SYMBOL_W diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 405f3b4a6f..e9193281dd 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -90,6 +90,7 @@ class NotifyStruct(xlsstruct.XLSStruct): BASE_W = 16 +# TODO replace this with fse_table_record from ./cocotb/ @xlsstruct.xls_dataclass class FseTableRecord(xlsstruct.XLSStruct): base: BASE_W From 1637218cc7a141f6051a237df9653166ef00e047 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 24 Sep 2025 17:58:30 +0200 Subject: [PATCH 057/159] modules: zstd: cocotb: add detailed cocotb test Co-authored-by: Wojciech Sipak Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 12 + xls/modules/zstd/cocotb/channel.py | 11 + xls/modules/zstd/rtl/zstd_dec_wrapper.sv | 72 +-- xls/modules/zstd/zstd_dec_cocotb_cli.py | 5 +- xls/modules/zstd/zstd_dec_cocotb_common.py | 15 +- xls/modules/zstd/zstd_dec_detailed_test.py | 679 +++++++++++++++++++++ 6 files changed, 748 insertions(+), 46 deletions(-) create mode 100644 xls/modules/zstd/zstd_dec_detailed_test.py diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f6bd3ca450..a0a2914a6d 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1500,6 +1500,7 @@ ZSTD_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { "worst_case_throughput": "6", # "flop_inputs_kind": "skid", # "flop_outputs_kind": "skid", + "streaming_channel_data_suffix": "_data", "materialize_internal_fifos": "false", # TODO: remove once this option works with loopback channels "ram_configurations": ",".join([ ",".join([ @@ -1881,6 +1882,16 @@ py_test( toolchains=["@rules_perl//:current_toolchain"], ) +py_library( + name = "zstd_dec_detailed_test", + srcs = ["zstd_dec_detailed_test.py"], + deps = [ + requirement("cocotb"), + "//xls/modules/zstd:zstd_dec_cocotb_common", + ], + toolchains=["@rules_perl//:current_toolchain"], +) + py_binary( name = "zstd_dec_cocotb_cli", srcs = ["zstd_dec_cocotb_cli.py"], @@ -1893,6 +1904,7 @@ py_binary( visibility = ["//xls:xls_users"], deps = [ requirement("cocotb"), + ":zstd_dec_detailed_test", "//xls/modules/zstd:zstd_dec_cocotb_common", ], toolchains=["@rules_perl//:current_toolchain"], diff --git a/xls/modules/zstd/cocotb/channel.py b/xls/modules/zstd/cocotb/channel.py index 6b6e546e86..01b84b54f0 100644 --- a/xls/modules/zstd/cocotb/channel.py +++ b/xls/modules/zstd/cocotb/channel.py @@ -44,6 +44,17 @@ def __init__(self, entity, name, clk, *, start_now=False, **kwargs: Any): if start_now: self.start_recv_loop() + @cocotb.coroutine + async def recv(self): + while True: + await RisingEdge(self.clk) + if self.rdy.value and self.vld.value: + return self.data.value + + @cocotb.coroutine + async def recv_as(self, xlsstruct_type): + return xlsstruct_type.from_int(await self.recv()) + @cocotb.coroutine async def recv_channel(self): """Cocotb coroutine that acts as a proc receiving data from a channel.""" diff --git a/xls/modules/zstd/rtl/zstd_dec_wrapper.sv b/xls/modules/zstd/rtl/zstd_dec_wrapper.sv index 79a6199810..fb79e089ec 100644 --- a/xls/modules/zstd/rtl/zstd_dec_wrapper.sv +++ b/xls/modules/zstd/rtl/zstd_dec_wrapper.sv @@ -1861,80 +1861,80 @@ module zstd_dec_wrapper #( .clk(clk), .rst(rst), - .zstd_dec__axi_ram_ar_s__0(zstd_dec__axi_ram_ar_s__0), + .zstd_dec__axi_ram_ar_s__0_data(zstd_dec__axi_ram_ar_s__0), .zstd_dec__axi_ram_ar_s__0_vld(zstd_dec__axi_ram_ar_s__0_vld), .zstd_dec__axi_ram_ar_s__0_rdy(zstd_dec__axi_ram_ar_s__0_rdy), - .zstd_dec__axi_ram_r_r__0(zstd_dec__axi_ram_r_r__0), + .zstd_dec__axi_ram_r_r__0_data(zstd_dec__axi_ram_r_r__0), .zstd_dec__axi_ram_r_r__0_vld(zstd_dec__axi_ram_r_r__0_vld), .zstd_dec__axi_ram_r_r__0_rdy(zstd_dec__axi_ram_r_r__0_rdy), - .zstd_dec__axi_ram_ar_s__1(zstd_dec__axi_ram_ar_s__1), + .zstd_dec__axi_ram_ar_s__1_data(zstd_dec__axi_ram_ar_s__1), .zstd_dec__axi_ram_ar_s__1_vld(zstd_dec__axi_ram_ar_s__1_vld), .zstd_dec__axi_ram_ar_s__1_rdy(zstd_dec__axi_ram_ar_s__1_rdy), - .zstd_dec__axi_ram_r_r__1(zstd_dec__axi_ram_r_r__1), + .zstd_dec__axi_ram_r_r__1_data(zstd_dec__axi_ram_r_r__1), .zstd_dec__axi_ram_r_r__1_vld(zstd_dec__axi_ram_r_r__1_vld), .zstd_dec__axi_ram_r_r__1_rdy(zstd_dec__axi_ram_r_r__1_rdy), - .zstd_dec__axi_ram_ar_s__2(zstd_dec__axi_ram_ar_s__2), + .zstd_dec__axi_ram_ar_s__2_data(zstd_dec__axi_ram_ar_s__2), .zstd_dec__axi_ram_ar_s__2_vld(zstd_dec__axi_ram_ar_s__2_vld), .zstd_dec__axi_ram_ar_s__2_rdy(zstd_dec__axi_ram_ar_s__2_rdy), - .zstd_dec__axi_ram_r_r__2(zstd_dec__axi_ram_r_r__2), + .zstd_dec__axi_ram_r_r__2_data(zstd_dec__axi_ram_r_r__2), .zstd_dec__axi_ram_r_r__2_vld(zstd_dec__axi_ram_r_r__2_vld), .zstd_dec__axi_ram_r_r__2_rdy(zstd_dec__axi_ram_r_r__2_rdy), - .zstd_dec__axi_ram_ar_s__3(zstd_dec__axi_ram_ar_s__3), + .zstd_dec__axi_ram_ar_s__3_data(zstd_dec__axi_ram_ar_s__3), .zstd_dec__axi_ram_ar_s__3_vld(zstd_dec__axi_ram_ar_s__3_vld), .zstd_dec__axi_ram_ar_s__3_rdy(zstd_dec__axi_ram_ar_s__3_rdy), - .zstd_dec__axi_ram_r_r__3(zstd_dec__axi_ram_r_r__3), + .zstd_dec__axi_ram_r_r__3_data(zstd_dec__axi_ram_r_r__3), .zstd_dec__axi_ram_r_r__3_vld(zstd_dec__axi_ram_r_r__3_vld), .zstd_dec__axi_ram_r_r__3_rdy(zstd_dec__axi_ram_r_r__3_rdy), - .zstd_dec__axi_ram_ar_s__4(zstd_dec__axi_ram_ar_s__4), + .zstd_dec__axi_ram_ar_s__4_data(zstd_dec__axi_ram_ar_s__4), .zstd_dec__axi_ram_ar_s__4_vld(zstd_dec__axi_ram_ar_s__4_vld), .zstd_dec__axi_ram_ar_s__4_rdy(zstd_dec__axi_ram_ar_s__4_rdy), - .zstd_dec__axi_ram_r_r__4(zstd_dec__axi_ram_r_r__4), + .zstd_dec__axi_ram_r_r__4_data(zstd_dec__axi_ram_r_r__4), .zstd_dec__axi_ram_r_r__4_vld(zstd_dec__axi_ram_r_r__4_vld), .zstd_dec__axi_ram_r_r__4_rdy(zstd_dec__axi_ram_r_r__4_rdy), - .zstd_dec__axi_ram_ar_s__5(zstd_dec__axi_ram_ar_s__5), + .zstd_dec__axi_ram_ar_s__5_data(zstd_dec__axi_ram_ar_s__5), .zstd_dec__axi_ram_ar_s__5_vld(zstd_dec__axi_ram_ar_s__5_vld), .zstd_dec__axi_ram_ar_s__5_rdy(zstd_dec__axi_ram_ar_s__5_rdy), - .zstd_dec__axi_ram_r_r__5(zstd_dec__axi_ram_r_r__5), + .zstd_dec__axi_ram_r_r__5_data(zstd_dec__axi_ram_r_r__5), .zstd_dec__axi_ram_r_r__5_vld(zstd_dec__axi_ram_r_r__5_vld), .zstd_dec__axi_ram_r_r__5_rdy(zstd_dec__axi_ram_r_r__5_rdy), - .zstd_dec__axi_ram_ar_s__6(zstd_dec__axi_ram_ar_s__6), + .zstd_dec__axi_ram_ar_s__6_data(zstd_dec__axi_ram_ar_s__6), .zstd_dec__axi_ram_ar_s__6_vld(zstd_dec__axi_ram_ar_s__6_vld), .zstd_dec__axi_ram_ar_s__6_rdy(zstd_dec__axi_ram_ar_s__6_rdy), - .zstd_dec__axi_ram_r_r__6(zstd_dec__axi_ram_r_r__6), + .zstd_dec__axi_ram_r_r__6_data(zstd_dec__axi_ram_r_r__6), .zstd_dec__axi_ram_r_r__6_vld(zstd_dec__axi_ram_r_r__6_vld), .zstd_dec__axi_ram_r_r__6_rdy(zstd_dec__axi_ram_r_r__6_rdy), - .zstd_dec__axi_ram_ar_s__7(zstd_dec__axi_ram_ar_s__7), + .zstd_dec__axi_ram_ar_s__7_data(zstd_dec__axi_ram_ar_s__7), .zstd_dec__axi_ram_ar_s__7_vld(zstd_dec__axi_ram_ar_s__7_vld), .zstd_dec__axi_ram_ar_s__7_rdy(zstd_dec__axi_ram_ar_s__7_rdy), - .zstd_dec__axi_ram_r_r__7(zstd_dec__axi_ram_r_r__7), + .zstd_dec__axi_ram_r_r__7_data(zstd_dec__axi_ram_r_r__7), .zstd_dec__axi_ram_r_r__7_vld(zstd_dec__axi_ram_r_r__7_vld), .zstd_dec__axi_ram_r_r__7_rdy(zstd_dec__axi_ram_r_r__7_rdy), - .zstd_dec__axi_ram_ar_s__8(zstd_dec__axi_ram_ar_s__8), + .zstd_dec__axi_ram_ar_s__8_data(zstd_dec__axi_ram_ar_s__8), .zstd_dec__axi_ram_ar_s__8_vld(zstd_dec__axi_ram_ar_s__8_vld), .zstd_dec__axi_ram_ar_s__8_rdy(zstd_dec__axi_ram_ar_s__8_rdy), - .zstd_dec__axi_ram_r_r__8(zstd_dec__axi_ram_r_r__8), + .zstd_dec__axi_ram_r_r__8_data(zstd_dec__axi_ram_r_r__8), .zstd_dec__axi_ram_r_r__8_vld(zstd_dec__axi_ram_r_r__8_vld), .zstd_dec__axi_ram_r_r__8_rdy(zstd_dec__axi_ram_r_r__8_rdy), - .zstd_dec__axi_ram_ar_s__9(zstd_dec__axi_ram_ar_s__9), + .zstd_dec__axi_ram_ar_s__9_data(zstd_dec__axi_ram_ar_s__9), .zstd_dec__axi_ram_ar_s__9_vld(zstd_dec__axi_ram_ar_s__9_vld), .zstd_dec__axi_ram_ar_s__9_rdy(zstd_dec__axi_ram_ar_s__9_rdy), - .zstd_dec__axi_ram_r_r__9(zstd_dec__axi_ram_r_r__9), + .zstd_dec__axi_ram_r_r__9_data(zstd_dec__axi_ram_r_r__9), .zstd_dec__axi_ram_r_r__9_vld(zstd_dec__axi_ram_r_r__9_vld), .zstd_dec__axi_ram_r_r__9_rdy(zstd_dec__axi_ram_r_r__9_rdy), - .zstd_dec__axi_ram_ar_s__10(zstd_dec__axi_ram_ar_s__10), + .zstd_dec__axi_ram_ar_s__10_data(zstd_dec__axi_ram_ar_s__10), .zstd_dec__axi_ram_ar_s__10_vld(zstd_dec__axi_ram_ar_s__10_vld), .zstd_dec__axi_ram_ar_s__10_rdy(zstd_dec__axi_ram_ar_s__10_rdy), - .zstd_dec__axi_ram_r_r__10(zstd_dec__axi_ram_r_r__10), + .zstd_dec__axi_ram_r_r__10_data(zstd_dec__axi_ram_r_r__10), .zstd_dec__axi_ram_r_r__10_vld(zstd_dec__axi_ram_r_r__10_vld), .zstd_dec__axi_ram_r_r__10_rdy(zstd_dec__axi_ram_r_r__10_rdy), @@ -2220,54 +2220,54 @@ module zstd_dec_wrapper #( .literals_buffer_ram7_wr_en(literals_buffer_ram7_wr_en), // CSR Interface - .zstd_dec__csr_axi_aw_r(zstd_dec__csr_axi_aw), + .zstd_dec__csr_axi_aw_r_data(zstd_dec__csr_axi_aw), .zstd_dec__csr_axi_aw_r_vld(zstd_dec__csr_axi_aw_vld), .zstd_dec__csr_axi_aw_r_rdy(zstd_dec__csr_axi_aw_rdy), - .zstd_dec__csr_axi_w_r(zstd_dec__csr_axi_w), + .zstd_dec__csr_axi_w_r_data(zstd_dec__csr_axi_w), .zstd_dec__csr_axi_w_r_vld(zstd_dec__csr_axi_w_vld), .zstd_dec__csr_axi_w_r_rdy(zstd_dec__csr_axi_w_rdy), - .zstd_dec__csr_axi_b_s(zstd_dec__csr_axi_b), + .zstd_dec__csr_axi_b_s_data(zstd_dec__csr_axi_b), .zstd_dec__csr_axi_b_s_vld(zstd_dec__csr_axi_b_vld), .zstd_dec__csr_axi_b_s_rdy(zstd_dec__csr_axi_b_rdy), - .zstd_dec__csr_axi_ar_r(zstd_dec__csr_axi_ar), + .zstd_dec__csr_axi_ar_r_data(zstd_dec__csr_axi_ar), .zstd_dec__csr_axi_ar_r_vld(zstd_dec__csr_axi_ar_vld), .zstd_dec__csr_axi_ar_r_rdy(zstd_dec__csr_axi_ar_rdy), - .zstd_dec__csr_axi_r_s(zstd_dec__csr_axi_r), + .zstd_dec__csr_axi_r_s_data(zstd_dec__csr_axi_r), .zstd_dec__csr_axi_r_s_vld(zstd_dec__csr_axi_r_vld), .zstd_dec__csr_axi_r_s_rdy(zstd_dec__csr_axi_r_rdy), // FrameHeaderDecoder - .zstd_dec__fh_axi_ar_s(zstd_dec__fh_axi_ar), + .zstd_dec__fh_axi_ar_s_data(zstd_dec__fh_axi_ar), .zstd_dec__fh_axi_ar_s_vld(zstd_dec__fh_axi_ar_vld), .zstd_dec__fh_axi_ar_s_rdy(zstd_dec__fh_axi_ar_rdy), - .zstd_dec__fh_axi_r_r(zstd_dec__fh_axi_r), + .zstd_dec__fh_axi_r_r_data(zstd_dec__fh_axi_r), .zstd_dec__fh_axi_r_r_vld(zstd_dec__fh_axi_r_vld), .zstd_dec__fh_axi_r_r_rdy(zstd_dec__fh_axi_r_rdy), // BlockHeaderDecoder - .zstd_dec__bh_axi_ar_s(zstd_dec__bh_axi_ar), + .zstd_dec__bh_axi_ar_s_data(zstd_dec__bh_axi_ar), .zstd_dec__bh_axi_ar_s_vld(zstd_dec__bh_axi_ar_vld), .zstd_dec__bh_axi_ar_s_rdy(zstd_dec__bh_axi_ar_rdy), - .zstd_dec__bh_axi_r_r(zstd_dec__bh_axi_r), + .zstd_dec__bh_axi_r_r_data(zstd_dec__bh_axi_r), .zstd_dec__bh_axi_r_r_vld(zstd_dec__bh_axi_r_vld), .zstd_dec__bh_axi_r_r_rdy(zstd_dec__bh_axi_r_rdy), // RawBlockDecoder - .zstd_dec__raw_axi_ar_s(zstd_dec__raw_axi_ar), + .zstd_dec__raw_axi_ar_s_data(zstd_dec__raw_axi_ar), .zstd_dec__raw_axi_ar_s_vld(zstd_dec__raw_axi_ar_vld), .zstd_dec__raw_axi_ar_s_rdy(zstd_dec__raw_axi_ar_rdy), - .zstd_dec__raw_axi_r_r(zstd_dec__raw_axi_r), + .zstd_dec__raw_axi_r_r_data(zstd_dec__raw_axi_r), .zstd_dec__raw_axi_r_r_vld(zstd_dec__raw_axi_r_vld), .zstd_dec__raw_axi_r_r_rdy(zstd_dec__raw_axi_r_rdy), // Output Writer - .zstd_dec__output_axi_aw_s(zstd_dec__output_axi_aw), + .zstd_dec__output_axi_aw_s_data(zstd_dec__output_axi_aw), .zstd_dec__output_axi_aw_s_vld(zstd_dec__output_axi_aw_vld), .zstd_dec__output_axi_aw_s_rdy(zstd_dec__output_axi_aw_rdy), - .zstd_dec__output_axi_w_s(zstd_dec__output_axi_w), + .zstd_dec__output_axi_w_s_data(zstd_dec__output_axi_w), .zstd_dec__output_axi_w_s_vld(zstd_dec__output_axi_w_vld), .zstd_dec__output_axi_w_s_rdy(zstd_dec__output_axi_w_rdy), - .zstd_dec__output_axi_b_r(zstd_dec__output_axi_b), + .zstd_dec__output_axi_b_r_data(zstd_dec__output_axi_b), .zstd_dec__output_axi_b_r_vld(zstd_dec__output_axi_b_vld), .zstd_dec__output_axi_b_r_rdy(zstd_dec__output_axi_b_rdy), diff --git a/xls/modules/zstd/zstd_dec_cocotb_cli.py b/xls/modules/zstd/zstd_dec_cocotb_cli.py index f25b4ec3e6..e29a3b73db 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_cli.py +++ b/xls/modules/zstd/zstd_dec_cocotb_cli.py @@ -17,14 +17,15 @@ import cocotb import os import pathlib -from xls.modules.zstd.zstd_dec_cocotb_common import pregenerated_testing_routine, run_test, check_decoder_compliance +from xls.modules.zstd.zstd_dec_cocotb_common import run_test, check_decoder_compliance +from xls.modules.zstd.zstd_dec_detailed_test import detailed_testing_routine from multiprocessing import cpu_count @cocotb.test(timeout_time=int(os.getenv("ZSTD_DEC_COCOTB_CLI_TIMEOUT", "5000")), timeout_unit="ms") async def zstd_cli_test(dut): input_name = os.getenv("ZSTD_DEC_COCOTB_CLI_INPUT") print("input_name: ", input_name) - await pregenerated_testing_routine(dut, input_name) + await detailed_testing_routine(dut, input_name) def usage(): print(f"usage: {os.path.basename(sys.argv[0])} /abs/path/to/input.zst [timeout_is_ms]") diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index e9193281dd..316192321f 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -52,7 +52,7 @@ from pathlib import Path -import xls.modules.zstd.cocotb.channel as xlschannel +from xls.modules.zstd.cocotb.channel import XLSChannel, XLSChannelMonitor import xls.modules.zstd.cocotb.utils as cocotb_utils from xls.modules.zstd.zstd_test_debugger import debug_file from xls.modules.zstd.cocotb import data_generator @@ -380,8 +380,8 @@ def get_clock_time(clock: Clock): def connect_xls_channel(dut, channel_name, xls_struct): - channel = xlschannel.XLSChannel(dut, channel_name, dut.clk, start_now=True) - monitor = xlschannel.XLSChannelMonitor(dut, channel_name, dut.clk, xls_struct) + channel = XLSChannel(dut, channel_name, dut.clk, start_now=True) + monitor = XLSChannelMonitor(dut, channel_name, dut.clk, xls_struct) return (channel, monitor) @@ -400,7 +400,7 @@ def prepare_test_environment(dut): async def test_fse_lookup_decoder(dut, clock, expected_fse_lookups): - lookup_dec_resp_channel = xlschannel.XLSChannel( + lookup_dec_resp_channel = XLSChannel( dut.ZstdDecoder.xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next_inst149, "zstd_dec__flc_resp", dut.clk, @@ -439,7 +439,7 @@ def func(): async def test_fse_lookup_decoder_for_huffman(dut, clock, expected_fse_lookups): - lookup_dec_resp_channel = xlschannel.XLSChannel( + lookup_dec_resp_channel = XLSChannel( dut.ZstdDecoder.xls_modules_zstd_comp_lookup_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__HuffmanFseWeightsDecoder_0__CompLookupDecoder_0__64_8_16_1_15_8_32_1_7_9_8_1_8_16_1_next_inst5, "zstd_dec__fse_table_finish__1", dut.clk, @@ -548,7 +548,7 @@ async def test_huffman_codes(dut, clock, expected_codes): ) CODES_CHANNEL_NAME = "zstd_dec__code_builder_codes" - codes_channel = xlschannel.XLSChannel( + codes_channel = XLSChannel( WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME, dut.clk ) huffman_code_handshake = triggers.Event() @@ -594,7 +594,7 @@ def func(): async def test_huffman_weights(dut, clock, expected_huffman_weights): - lookup_dec_resp_channel = xlschannel.XLSChannel( + lookup_dec_resp_channel = XLSChannel( dut.ZstdDecoder.xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next_inst21, "zstd_dec__weights_dec_resp", dut.clk, @@ -860,7 +860,6 @@ async def pregenerated_testing_routine( f"Frame #0: latency: {measurement[0]} cycles; throughput: {measurement[1]} B/cycle ({measurement[2]} packets/cycle)" ) - async def test_expected_status( dut, pregenerated_path, diff --git a/xls/modules/zstd/zstd_dec_detailed_test.py b/xls/modules/zstd/zstd_dec_detailed_test.py new file mode 100644 index 0000000000..f81c81d35e --- /dev/null +++ b/xls/modules/zstd/zstd_dec_detailed_test.py @@ -0,0 +1,679 @@ +import math +import cocotb + +from pathlib import Path +from enum import IntEnum +from pprint import pformat + +from cocotb.utils import get_sim_time +from cocotb.triggers import RisingEdge, ClockCycles, Event, Edge + +from cocotbext.axi.sparse_memory import SparseMemory +from cocotbext.axi.axi_channels import AxiWMonitor + +from xls.modules.zstd.cocotb import data_generator +from xls.modules.zstd.cocotb.channel import XLSChannel, XLSChannelMonitor +from xls.modules.zstd.cocotb.xlsstruct import xls_dataclass, XLSStruct +from xls.modules.zstd.zstd_dec_cocotb_common import ( + configure_decoder, start_decoder, reset_dut, run_test, + prepare_test_environment, check_ram_contents, + reverse_expected_huffman_codes, fields_as_array, FseTableRecord, + print_fse_ram_contents, check_status, check_output +) +from xls.modules.zstd.cocotb.memory import AxiRamFromFile + +def check_if_ram_contents_are_valid(mem, expected, name="") -> bool: + for i, value in enumerate(expected): + if mem[i].value != value: + print(f"[{name}] index: {i} value: {mem[i].value}, expected: {value}") + return False + return True + +def print_ram_contents(mem, name="", size=None): + for i in range(size): + print(f"{name} [{i}]\t: {hex(mem[i]) if not hasattr(mem[i], 'value') else hex(mem[i].value)}") + +def printc(*args, **kwargs): + GREEN = '\033[92m' + ENDC = '\033[0m' + print(GREEN, *args, ENDC, **kwargs) + +class BlockType(IntEnum): + RAW = 0, + RLE = 1, + COMPRESSED = 2, + RESERVED = 3, + +@xls_dataclass +class BlockHeader(XLSStruct): + last: 1 + btype: 2 + size: 21 + +@xls_dataclass +class BlockHeaderDecoderResp(XLSStruct): + status: 2 + header: BlockHeader.total_width + rle_symbol: 8 + +@xls_dataclass +class BlockHeaderDecoderReq(XLSStruct): + addr: 32 + +@xls_dataclass +class RawBlockDecoderReq(XLSStruct): + id: 32 + addr: 32 + length: 32 + last_block: 1 + +class RawBlockDecoderStatus(IntEnum): + OKAY = 0 + ERROR = 1 + +@xls_dataclass +class RawBlockDecoderResp(XLSStruct): + status: 1 + +@xls_dataclass +class RleBlockDecoderReq(XLSStruct): + id: 32 + symbol: 8 + length: 21 + last_block: 1 + +@xls_dataclass +class RleBlockDecoderResp(XLSStruct): + status: 1 + +@xls_dataclass +class CompressedBlockDecoderReq(XLSStruct): + addr: 32 + length: 21 + id: 32 + last_block: 1 + +@xls_dataclass +class CompressedBlockDecoderResp(XLSStruct): + status: 1 + +@xls_dataclass +class LiteralsHeaderDecoderReq(XLSStruct): + addr: 32 + +class LiteralsBlockType(IntEnum): + RAW = 0 + RLE = 1 + COMP = 2 + COMP_4 = 3 + TREELESS = 4 + TREELESS_4 = 5 + +@xls_dataclass +class LiteralsHeader(XLSStruct): + literal_type: 3 + regenerated_size: 20 + compressed_size: 20 + +@xls_dataclass +class LiteralsHeaderDecoderResp(XLSStruct): + header: LiteralsHeader.total_width + symbol: 8 + length: 3 + status: 1 + +@xls_dataclass +class SequenceConfDecoderReq(XLSStruct): + addr: 32 + +@xls_dataclass +class SequenceConf(XLSStruct): + sequence_count: 17 + literals_mode: 2 + offset_mode: 2 + match_mode: 2 + +@xls_dataclass +class SequenceConfDecoderResp(XLSStruct): + header: SequenceConf.total_width + length: 3 + status: 1 + +@xls_dataclass +class RawLiteralsDecoderReq(XLSStruct): + id: 32 + addr: 32 + length: 32 + literals_last: 1 + +@xls_dataclass +class RawLiteralsDecoderResp(XLSStruct): + status: 1 + +@xls_dataclass +class RleLiteralsDecoderReq(XLSStruct): + id: 32 + symbol: 8 + length: 20 + literals_last: 1 + +@xls_dataclass +class RleLiteralsDecoderResp(XLSStruct): + status: 1 + + +@xls_dataclass +class HuffmanControlAndSequenceCtrl(XLSStruct): + base_addr: 32 + len: 32 + new_config: 1 + multi_stream: 1 + id: 32 + literals_last: 1 + + +@xls_dataclass +class HuffmanControlAndSequenceResp(XLSStruct): + status: 1 + + +class CompressionMode(IntEnum): + PREDEFINED = 0 + RLE = 1 + COMPRESSED = 2 + REPEAT = 3 + +@xls_dataclass +class HuffmanWeightsDecoderReq(XLSStruct): + addr: 32 + +@xls_dataclass +class HuffmanWeightsDecoderResp(XLSStruct): + status: 1 + tree_description_size: 32 + +MAX_WEIGHT = 11 +WEIGHT_LOG = math.ceil(math.log2(MAX_WEIGHT + 1)) +VALID_W = 1 + +class WeightType(IntEnum): + RAW = 0 + FSE = 1 + +@xls_dataclass +class CodeBuilderOutput(XLSStruct): + symbol_valid_7: VALID_W + symbol_valid_6: VALID_W + symbol_valid_5: VALID_W + symbol_valid_4: VALID_W + symbol_valid_3: VALID_W + symbol_valid_2: VALID_W + symbol_valid_1: VALID_W + symbol_valid_0: VALID_W + code_length_7: WEIGHT_LOG + code_length_6: WEIGHT_LOG + code_length_5: WEIGHT_LOG + code_length_4: WEIGHT_LOG + code_length_3: WEIGHT_LOG + code_length_2: WEIGHT_LOG + code_length_1: WEIGHT_LOG + code_length_0: WEIGHT_LOG + code_7: MAX_WEIGHT + code_6: MAX_WEIGHT + code_5: MAX_WEIGHT + code_4: MAX_WEIGHT + code_3: MAX_WEIGHT + code_2: MAX_WEIGHT + code_1: MAX_WEIGHT + code_0: MAX_WEIGHT + + +@xls_dataclass +class HuffmanRawWeightsDecoderReq(XLSStruct): + addr: 32 + n_symbols: 8 + + +@xls_dataclass +class HuffmanRawWeightsDecoderResp(XLSStruct): + status: 1 + + +@xls_dataclass +class HuffmanFseWeightsDecoderReq(XLSStruct): + addr: 32 + length: 8 + + +@xls_dataclass +class HuffmanFseWeightsDecoderResp(XLSStruct): + status: 1 + + +class CompressionMode(IntEnum): + PREDEFINED = 0 + RLE = 1 + COMPRESSED = 2 + REPEAT = 3 + + +@xls_dataclass +class FseLookupCtrlReq(XLSStruct): + ll_mode: 2 + ml_mode: 2 + of_mode: 2 + + +@xls_dataclass +class FseLookupCtrlResp(XLSStruct): + ll_accuracy_log: 7 + ml_accuracy_log: 7 + of_accuracy_log: 7 + + +@xls_dataclass +class HuffmanDecoderStart(XLSStruct): + new_config: 1 + id: 32 + literals_last: 1 + last_stream: 1 + + +async def detailed_testing_routine(dut, + pregenerated_path, + expected_fse_huffman_lookups=None, + expected_huffman_weights=None, + expected_huffman_codes=None, + expected_fse_lookups=None +): + (axi_buses, cpu, clock) = prepare_test_environment(dut) + + encoded_file = Path(pregenerated_path) + + # TODO: Make this a dict + + BLOCK_HEADER_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_block_header_dec__ZstdDecoderInst__ZstdDecoder_0__BlockHeaderDecoder_0__32_64_next_inst2 + BLOCK_HEADER_REQ_CHANNEL_NAME = "zstd_dec__bh_req" + BLOCK_HEADER_RESP_CHANNEL_NAME = "zstd_dec__bh_resp" + MAX_ENCODED_FRAME_SIZE_B = 2**32 + + RAW_BLOCK_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_raw_block_dec__ZstdDecoderInst__ZstdDecoder_0__RawBlockDecoder_0__32_64_next_inst135 + RAW_BLOCK_REQ_CHANNEL_NAME = "zstd_dec__raw_req" + RAW_BLOCK_RESP_CHANNEL_NAME = "zstd_dec__raw_resp" + + RLE_BLOCK_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_rle_block_dec__ZstdDecoderInst__ZstdDecoder_0__RleBlockDecoder_0__64_next_inst142 + RLE_BLOCK_REQ_CHANNEL_NAME = "zstd_dec__rle_req" + RLE_BLOCK_RESP_CHANNEL_NAME = "zstd_dec__rle_resp" + + COMPRESSED_BLOCK_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_comp_block_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__32_64_8_4_4_16_256_64_6_92_1_8_16_1_8_32_1_6_32_8_9_8_1_8_16_1_13_9_1_8_16_1_15_15_32_1_9_8_1_8_16_1_next_inst4 + COMPRESSED_BLOCK_REQ_CHANNEL_NAME = "zstd_dec__comp_block_req" + COMPRESSED_BLOCK_RESP_CHANNEL_NAME = "zstd_dec__comp_block_resp" + + LITERALS_HEADER_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_literals_block_header_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__LiteralsHeaderDecoder_0__32_64_next_inst32 + LITERALS_HEADER_REQ_CHANNEL_NAME = "zstd_dec__lit_header_req" + LITERALS_HEADER_RESP_CHANNEL_NAME = "zstd_dec__lit_header_resp" + + RAW_LITERALS_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_raw_literals_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__RawLiteralsDecoder_0__32_64_next_inst136 + RAW_LITERALS_DECODER_REQ_CHANNEL_NAME = "zstd_dec__raw_lit_req" + RAW_LITERALS_DECODER_RESP_CHANNEL_NAME = "zstd_dec__raw_lit_resp" + + RLE_LITERALS_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_rle_literals_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__RleLiteralsDecoder_0__64_next_inst143 + RLE_LITERALS_DECODER_REQ_CHANNEL_NAME = "zstd_dec__rle_lit_req" + RLE_LITERALS_DECODER_RESP_CHANNEL_NAME = "zstd_dec__rle_lit_resp" + + HUFFMAN_LITERALS_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next_inst21 + HUFFMAN_LITERALS_DECODER_REQ_CHANNEL_NAME = "zstd_dec__huffman_lit_req" + HUFFMAN_LITERALS_DECODER_RESP_CHANNEL_NAME = "zstd_dec__huffman_lit_resp" + + HUFFMAN_LITERALS_DECODER_WEIGHTS_REQ_CHANNEL_NAME = "zstd_dec__weights_dec_req" + HUFFMAN_LITERALS_DECODER_WEIGHTS_RESP_CHANNEL_NAME = "zstd_dec__weights_dec_resp" + + HUFFMAN_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_decoder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanDecoder_0_next_inst26 + HUFFMAN_DECODER_DONE_CHANNEL_NAME = "zstd_dec__decoder_done" + + HUFFMAN_LITERALS_WEIGHT_CODE_BUILDER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next_inst20 + HUFFMAN_LITERALS_WEIGHT_CODES_CHANNEL_NAME = "zstd_dec__code_builder_codes" + + HUFFMAN_WEIGHTS_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_weights_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__32_64_4_8_16_1_8_32_1_9_8_1_8_16_1_6_32_8_next_inst28 + HUFFMAN_WEIGHTS_DECODER_SEL_REQ_CHANNEL_NAME = "zstd_dec__decoded_weights_sel_req" + HUFFMAN_WEIGHTS_DECODER_SEL_RESP_CHANNEL_NAME = "zstd_dec__decoded_weights_sel_resp" + + HUFFMAN_RAW_WEIGHTS_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_weights_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__HuffmanRawWeightsDecoder_0__32_64_96_7_6_32_8_next_inst31 + HUFFMAN_RAW_WEIGHTS_DECODER_SEL_REQ_CHANNEL_NAME = "zstd_dec__raw_weights_req" + HUFFMAN_RAW_WEIGHTS_DECODER_SEL_RESP_CHANNEL_NAME = "zstd_dec__raw_weights_resp" + + HUFFMAN_FSE_WEIGHTS_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_weights_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__HuffmanFseWeightsDecoder_0__32_64_4_8_16_1_8_32_1_64_7_9_8_1_8_16_1_6_32_8_next_inst29 + HUFFMAN_FSE_WEIGHTS_DECODER_SEL_REQ_CHANNEL_NAME = "zstd_dec__fse_weights_req" + HUFFMAN_FSE_WEIGHTS_DECODER_SEL_RESP_CHANNEL_NAME = "zstd_dec__fse_weights_resp" + + HUFFMAN_CONTROL_AND_SEQUENCE_INTERNAL_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__HuffmanControlAndSequenceMultiStreamHandler_0__HuffmanControlAndSequenceInternal_0__32_next_inst23 + HUFFMAN_CONTROL_AND_SEQUENCE_INTERNAL_REQ_CHANNEL_NAME = "zstd_dec__hcs_internal_req" + HUFFMAN_CONTROL_AND_SEQUENCE_INTERNAL_RESP_CHANNEL_NAME = "zstd_dec__hcs_internal_resp" + + SEQUENCE_HEADER_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_sequence_conf_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceConfDecoder_0__32_64_next_inst145 + SEQUENCE_HEADER_REQ_CHANNEL_NAME = "zstd_dec__scd_req" + SEQUENCE_HEADER_RESP_CHANNEL_NAME = "zstd_dec__scd_resp" + + FSE_LOOKUP_CTRL_INST = dut.ZstdDecoder.xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next_inst149 + FSE_LOOKUP_CTRL_REQ_CHANNEL_NAME = "zstd_dec__flc_req" + FSE_LOOKUP_CTRL_RESP_CHANNEL_NAME = "zstd_dec__flc_resp" + + block_header_req = XLSChannel(BLOCK_HEADER_DECODER_INST, BLOCK_HEADER_REQ_CHANNEL_NAME, dut.clk) + block_header_resp = XLSChannel(BLOCK_HEADER_DECODER_INST, BLOCK_HEADER_RESP_CHANNEL_NAME, dut.clk) + + raw_block_req = XLSChannel(RAW_BLOCK_DECODER_INST, RAW_BLOCK_REQ_CHANNEL_NAME, dut.clk) + raw_block_resp = XLSChannel(RAW_BLOCK_DECODER_INST, RAW_BLOCK_RESP_CHANNEL_NAME, dut.clk) + + rle_block_req = XLSChannel(RLE_BLOCK_DECODER_INST, RLE_BLOCK_REQ_CHANNEL_NAME, dut.clk) + rle_block_resp = XLSChannel(RLE_BLOCK_DECODER_INST, RLE_BLOCK_RESP_CHANNEL_NAME, dut.clk) + + compressed_block_req = XLSChannel(COMPRESSED_BLOCK_DECODER_INST, COMPRESSED_BLOCK_REQ_CHANNEL_NAME, dut.clk) + compressed_block_resp = XLSChannel(COMPRESSED_BLOCK_DECODER_INST, COMPRESSED_BLOCK_RESP_CHANNEL_NAME, dut.clk) + + literals_header_req = XLSChannel(LITERALS_HEADER_DECODER_INST, LITERALS_HEADER_REQ_CHANNEL_NAME, dut.clk) + literals_header_resp = XLSChannel(LITERALS_HEADER_DECODER_INST, LITERALS_HEADER_RESP_CHANNEL_NAME, dut.clk) + + raw_literals_dec_req = XLSChannel(RAW_LITERALS_DECODER_INST, RAW_LITERALS_DECODER_REQ_CHANNEL_NAME, dut.clk) + raw_literals_dec_resp = XLSChannel(RAW_LITERALS_DECODER_INST, RAW_LITERALS_DECODER_RESP_CHANNEL_NAME, dut.clk) + + rle_literals_dec_req = XLSChannel(RLE_LITERALS_DECODER_INST, RLE_LITERALS_DECODER_REQ_CHANNEL_NAME, dut.clk) + rle_literals_dec_resp = XLSChannel(RLE_LITERALS_DECODER_INST, RLE_LITERALS_DECODER_RESP_CHANNEL_NAME, dut.clk) + + huffman_literals_dec_req = XLSChannel(HUFFMAN_LITERALS_DECODER_INST, HUFFMAN_LITERALS_DECODER_REQ_CHANNEL_NAME, dut.clk) + huffman_literals_dec_resp = XLSChannel(HUFFMAN_LITERALS_DECODER_INST, HUFFMAN_LITERALS_DECODER_RESP_CHANNEL_NAME, dut.clk) + + huffman_weights_req = XLSChannel(HUFFMAN_LITERALS_DECODER_INST, HUFFMAN_LITERALS_DECODER_WEIGHTS_REQ_CHANNEL_NAME, dut.clk) + huffman_weights_resp = XLSChannel(HUFFMAN_LITERALS_DECODER_INST, HUFFMAN_LITERALS_DECODER_WEIGHTS_RESP_CHANNEL_NAME, dut.clk) + + huffman_weights_sel_req = XLSChannel(HUFFMAN_WEIGHTS_DECODER_INST, HUFFMAN_WEIGHTS_DECODER_SEL_REQ_CHANNEL_NAME, dut.clk) + huffman_weights_sel_resp = XLSChannel(HUFFMAN_WEIGHTS_DECODER_INST, HUFFMAN_WEIGHTS_DECODER_SEL_RESP_CHANNEL_NAME, dut.clk) + + huffman_raw_weights_req = XLSChannel(HUFFMAN_RAW_WEIGHTS_DECODER_INST, HUFFMAN_RAW_WEIGHTS_DECODER_SEL_REQ_CHANNEL_NAME, dut.clk) + huffman_raw_weights_resp = XLSChannel(HUFFMAN_RAW_WEIGHTS_DECODER_INST, HUFFMAN_RAW_WEIGHTS_DECODER_SEL_RESP_CHANNEL_NAME, dut.clk) + + huffman_fse_weights_req = XLSChannel(HUFFMAN_FSE_WEIGHTS_DECODER_INST, HUFFMAN_FSE_WEIGHTS_DECODER_SEL_REQ_CHANNEL_NAME, dut.clk) + huffman_fse_weights_resp = XLSChannel(HUFFMAN_FSE_WEIGHTS_DECODER_INST, HUFFMAN_FSE_WEIGHTS_DECODER_SEL_RESP_CHANNEL_NAME, dut.clk) + + huffman_codes = XLSChannel(HUFFMAN_LITERALS_WEIGHT_CODE_BUILDER_INST, HUFFMAN_LITERALS_WEIGHT_CODES_CHANNEL_NAME, dut.clk) + huffman_done = XLSChannel(HUFFMAN_DECODER_INST, HUFFMAN_DECODER_DONE_CHANNEL_NAME, dut.clk) + + sequence_header_req = XLSChannel(SEQUENCE_HEADER_DECODER_INST, SEQUENCE_HEADER_REQ_CHANNEL_NAME, dut.clk) + sequence_header_resp = XLSChannel(SEQUENCE_HEADER_DECODER_INST, SEQUENCE_HEADER_RESP_CHANNEL_NAME, dut.clk) + + huffman_control_and_sequence_internal_req = XLSChannel(HUFFMAN_CONTROL_AND_SEQUENCE_INTERNAL_INST, HUFFMAN_CONTROL_AND_SEQUENCE_INTERNAL_REQ_CHANNEL_NAME, dut.clk) + huffman_control_and_sequence_internal_resp = XLSChannel(HUFFMAN_CONTROL_AND_SEQUENCE_INTERNAL_INST, HUFFMAN_CONTROL_AND_SEQUENCE_INTERNAL_RESP_CHANNEL_NAME, dut.clk) + + fse_lookup_ctrl_req = XLSChannel(FSE_LOOKUP_CTRL_INST, FSE_LOOKUP_CTRL_REQ_CHANNEL_NAME, dut.clk) + fse_lookup_ctrl_resp = XLSChannel(FSE_LOOKUP_CTRL_INST, FSE_LOOKUP_CTRL_RESP_CHANNEL_NAME, dut.clk) + + await reset_dut(dut, 50) + + memory_bus = axi_buses["memory"] + mem_size = MAX_ENCODED_FRAME_SIZE_B + ibuf_addr = 0x0 + obuf_addr = mem_size // 2 + await reset_dut(dut, 50) + + AXI_DATA_W = 64 + AXI_DATA_W_BYTES = AXI_DATA_W // 8 + + # Initialise testbench memory with generated ZSTD frame + + with open(encoded_file, "rb") as f: + expected_decoded_frame = data_generator.DecompressFrame(f.read()) + reference_memory = SparseMemory(mem_size) + reference_memory.write(obuf_addr, expected_decoded_frame) + expected_packet_count = (len(expected_decoded_frame) + (AXI_DATA_W_BYTES - 1)) // AXI_DATA_W_BYTES + + memory = AxiRamFromFile( + bus=memory_bus, clock=dut.clk, reset=dut.rst, path=str(encoded_file), size=mem_size + ) + + await configure_decoder(dut, cpu, ibuf_addr, obuf_addr) + output_monitor = AxiWMonitor(memory_bus.write.w, dut.clk, dut.rst) + + check_status_thread = await cocotb.start(check_status(dut, cpu)) + check_output_thread = await cocotb.start(check_output(expected_packet_count, memory, reference_memory, output_monitor, obuf_addr, clock, encoded_file)) + + await start_decoder(cpu) + + score = 0 + block_cnt = 0 + expected_fse_count = 0 + expected_fse_huffman_count = 0 + expected_huffman_weights_count = 0 + expected_huffman_codes_count = 0 + + while True: + req = await block_header_req.recv_as(BlockHeaderDecoderReq) + score += 1 + printc(f"[block {block_cnt}] Requested block header, score: {score}") + + resp = await block_header_resp.recv_as(BlockHeaderDecoderResp) + block_header = BlockHeader.from_int(resp.header) + score += 1 + printc(f"[block {block_cnt}] Decoded block header: {block_header}, score {score}") + printc(f"SIM TIME: {get_sim_time(units='step')}") + + block_type = BlockType(block_header.btype) + match block_type: + case BlockType.RAW: + req = await raw_block_req.recv_as(RawBlockDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding a RAW block, score: {score}") + + resp = await raw_block_resp.recv_as(RawBlockDecoderResp) + score +=1 + printc(f"[block {block_cnt}] Decoded a RAW block, score: {score}") + + case BlockType.RLE: + req = await rle_block_req.recv_as(RleBlockDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding an RLE block, score: {score}") + + resp = await rle_block_resp.recv_as(RleBlockDecoderResp) + score +=1 + printc(f"[block {block_cnt}] Decoded an RLE block, score: {score}") + + case BlockType.COMPRESSED: + req = await compressed_block_req.recv_as(CompressedBlockDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding a COMPRESSED block, score: {score}") + + req = await literals_header_req.recv_as(LiteralsHeaderDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding a literals header, score: {score}") + + resp = await literals_header_resp.recv_as(LiteralsHeaderDecoderResp) + literal_header = LiteralsHeader.from_int(resp.header) + score +=1 + printc(f"[block {block_cnt}] Decoded a literals header: {literal_header}, score: {score}") + + literal_type = LiteralsBlockType(literal_header.literal_type) + match literal_type: + case LiteralsBlockType.RAW: + req = await raw_literals_dec_req.recv_as(RawLiteralsDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding RAW literals, score: {score}") + + resp = await raw_literals_dec_resp.recv_as(RawLiteralsDecoderResp) + score +=1 + printc(f"[block {block_cnt}] Decoded RAW literals, score: {score}") + + case LiteralsBlockType.RLE: + req = await rle_literals_dec_req.recv_as(RleLiteralsDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding RLE literals, score: {score}") + + resp = await rle_literals_dec_resp.recv_as(RleLiteralsDecoderResp) + score +=1 + printc(f"[block {block_cnt}] Decoded RLE literals, score: {score}") + + case LiteralsBlockType.COMP | LiteralsBlockType.TREELESS | LiteralsBlockType.COMP_4 | LiteralsBlockType.TREELESS_4: + req = await huffman_literals_dec_req.recv_as(HuffmanControlAndSequenceCtrl) + score +=1 + printc(f"SIM TIME: {get_sim_time(units='step')}") + + if not literal_type in [LiteralsBlockType.TREELESS, LiteralsBlockType.TREELESS_4]: + weight_dec_req = await huffman_weights_req.recv_as(HuffmanWeightsDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding COMPRESSED literals, score: {score}") + + sel_req = await huffman_weights_sel_req.recv() + score +=1 + + huffman_weights_type = WeightType(sel_req) + if huffman_weights_type == WeightType.RAW: + + req = await huffman_raw_weights_req.recv_as(HuffmanRawWeightsDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding RAW Huffman weights, score: {score}") + + resp = await huffman_raw_weights_resp.recv_as(HuffmanRawWeightsDecoderResp) + score +=1 + printc(f"[block {block_cnt}] Decoded RAW Huffman weights, score: {score}") + + else: + req = await huffman_fse_weights_req.recv_as(HuffmanFseWeightsDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding FSE Huffman weights, score: {score}") + + resp = await huffman_fse_weights_resp.recv_as(HuffmanFseWeightsDecoderResp) + score +=1 + printc(f"[block {block_cnt}] Decoded FSE Huffman weights, score: {score}") + + huffman_fse_mem = dut.huffman_literals_weights_fse_ram_ram.mem + if expected_fse_huffman_lookups is not None: + valid = check_if_ram_contents_are_valid(huffman_fse_mem, [x.value for x in expected_fse_huffman_lookups[expected_fse_huffman_count]], "huffman_fse") + if not valid: + print_fse_ram_contents(huffman_fse_mem, "huffman_fse_mem", len(expected_fse_huffman_lookups[expected_fse_huffman_count])) + print_fse_ram_contents(expected_fse_huffman_lookups[expected_fse_huffman_count], "expected_huffman_fse_mem", len(expected_fse_huffman_lookups[expected_fse_huffman_count])) + assert False, f"Huffman FSE table in block {block_cnt} is invalid" + expected_fse_huffman_count += 1 + printc(f"[block {block_cnt}] Verified FSE Huffman weights, score: {score}") + score +=1 + + weight_dec_resp = await huffman_weights_resp.recv_as(HuffmanWeightsDecoderResp) + score +=1 + printc(f"[block {block_cnt}] Decoded Huffman weights, score: {score}") + + huffman_mem = dut.huffman_literals_weights_mem_ram_ram.mem + if expected_huffman_weights is not None: + valid = check_if_ram_contents_are_valid(huffman_mem, expected_huffman_weights[expected_huffman_weights_count], "huffman_weights") + if not valid: + print_ram_contents(huffman_mem, "huffman_weights", len(expected_huffman_weights[expected_huffman_weights_count])) + printc("VS.") + print_ram_contents(expected_huffman_weights[expected_huffman_weights_count], "expected_huffman_weights", len(expected_huffman_weights[expected_huffman_weights_count])) + assert False, f"Huffman weights table in block {block_cnt} is invalid" + expected_huffman_weights_count += 1 + score +=1 + printc(f"[block {block_cnt}] Checked huffman weights, score: {score}") + + codes = [] + symbol_cnt = 0 + + j = 32 + while (j > 0): + data = await huffman_codes.recv_as(CodeBuilderOutput) + score +=1 + printc(f"[block {block_cnt}] Received huffman codes, score: {score}") + + symbol_valid_array = fields_as_array(data, "symbol_valid", 8) + code_length_array = fields_as_array(data, "code_length", 8) + code_array = fields_as_array(data, "code", 8) + for symbol_valid, code_length, code in zip(symbol_valid_array, code_length_array, code_array): + if symbol_valid == 1: + new_codes = [{"symbol": symbol_cnt, "code": code, "length": code_length}] + codes += new_codes + symbol_cnt += 1 + j -= 1 + + if expected_huffman_codes is not None: + if codes != reverse_expected_huffman_codes(expected_huffman_codes[expected_huffman_codes_count]): + by_codes = lambda d: d['symbol'] + printc(pformat(sorted(codes, key=by_codes))) + printc("VS.") + printc(pformat(sorted(reverse_expected_huffman_codes(expected_huffman_codes[expected_huffman_codes_count]), key=by_codes))) + assert False, f"Huffman weights table in block {block_cnt} is invalid" + expected_huffman_codes_count += 1 + score +=1 + printc(f"[block {block_cnt}] Checked huffman codes, score: {score}") + + resp = await huffman_literals_dec_resp.recv_as(HuffmanControlAndSequenceResp) + score +=1 + printc(f"[block {block_cnt}] Decoded Huffman literals, score: {score}") + + + req = await sequence_header_req.recv_as(SequenceConfDecoderReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding sequence header, score: {score}") + + resp = await sequence_header_resp.recv_as(SequenceConfDecoderResp) + header = SequenceConf.from_int(resp.header) + score +=1 + printc(f"[block {block_cnt}] Decoded sequence header, score: {score}") + + if header.sequence_count != 0: + req = await fse_lookup_ctrl_req.recv_as(FseLookupCtrlReq) + score +=1 + printc(f"[block {block_cnt}] Requested decoding FSE lookups, score: {score}") + + ll_mode = CompressionMode(req.ll_mode) + ml_mode = CompressionMode(req.ml_mode) + of_mode = CompressionMode(req.of_mode) + + req = await fse_lookup_ctrl_resp.recv_as(FseLookupCtrlResp) + score +=1 + printc(f"[block {block_cnt}] Decoded FSE lookups, score: {score}") + + if expected_fse_lookups is not None: + if ll_mode != CompressionMode.PREDEFINED: + printc(f"LL mode: {ll_mode}") + ll_mem = dut.ll_fse_ram.mem + if expected_fse_lookups is not None: + expected_ll_mem = expected_fse_lookups[expected_fse_count]["ll"] + valid = check_if_ram_contents_are_valid(ll_mem, [x.value for x in expected_ll_mem]) + if not valid: + print_fse_ram_contents(ll_mem, "ll_fse_mem", len(expected_ll_mem)) + printc("VS.") + print_fse_ram_contents(expected_ll_mem, "expected_ll_fse_mem", len(expected_ll_mem)) + assert False, f"LL FSE lookup table in block {block_cnt} is invalid" + + if of_mode != CompressionMode.PREDEFINED: + printc(f"OF mode: {of_mode}") + of_mem = dut.of_fse_ram.mem + expected_of_mem = expected_fse_lookups[expected_fse_count]["of"] + valid = check_if_ram_contents_are_valid(of_mem, [x.value for x in expected_of_mem]) + if not valid: + print_fse_ram_contents(of_mem, "of_fse_mem", len(expected_of_mem)) + printc("VS.") + print_fse_ram_contents(expected_of_mem, "expected_of_fse_mem", len(expected_of_mem)) + assert False, f"OF FSE lookup table in block {block_cnt} is invalid" + + if ml_mode != CompressionMode.PREDEFINED: + printc(f"ML mode: {ml_mode}") + ml_mem = dut.ml_fse_ram.mem + expected_ml_mem = expected_fse_lookups[expected_fse_count]["ml"] + valid = check_if_ram_contents_are_valid(ml_mem, [x.value for x in expected_ml_mem]) + if not valid: + print_fse_ram_contents(ml_mem, "ml_fse_mem", len(expected_ml_mem)) + printc("VS.") + print_fse_ram_contents(expected_ml_mem, "expected_of_fse_mem", len(expected_ml_mem)) + assert False, f"ML FSE lookup table in block {block_cnt} is invalid" + + expected_fse_count += 1 + + resp = await compressed_block_resp.recv_as(CompressedBlockDecoderResp) + score +=1 + printc(f"[block {block_cnt}] Decoded COMPRESSED block, score: {score}") + + case BlockType.RESERVED: + raise Exception("Decoded block header of type RESERVED.") + + block_cnt += 1 + + if block_header.last: + break + + await check_status_thread + await check_output_thread From 15f0aea9564642d3c1995fe70d0f3cedacb906a4 Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Tue, 30 Sep 2025 17:11:16 +0200 Subject: [PATCH 058/159] modules: zstd: cocotb: add verbose assert --- xls/modules/zstd/zstd_dec_cocotb_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 316192321f..e8a3a1aa93 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -204,7 +204,7 @@ def check_decoder_compliance(file_path): def check_ram_contents(mem, expected, name=""): for i, value in enumerate(expected): - assert mem[i].value == value + assert mem[i].value == value, f"[{name}] index: {i} value: {mem[i].value}, expected: {value}" def print_fse_ram_contents(mem, name="", size=None): From 11924f7a34d80cd35edc6fce5d24462caa121c1d Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Wed, 1 Oct 2025 12:42:40 +0200 Subject: [PATCH 059/159] modules: zstd: cocotb: fix name of data signal after codegen args were set to use "_data" --- xls/modules/zstd/zstd_dec_cocotb_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index e8a3a1aa93..329bca47d0 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -565,7 +565,7 @@ def func(): nonlocal block_cnt assert block_cnt <= 32 - codes_data = getattr(WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME) + codes_data = getattr(WEIGHT_CODE_BUILDER_INST, CODES_CHANNEL_NAME + '_data') data = CodeBuilderOutput.from_int(codes_data.value) symbol_valid_array = fields_as_array(data, "symbol_valid", 8) From f28568cad574b1836896c6e9ccea99552437632c Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Mon, 25 Aug 2025 12:15:34 +0200 Subject: [PATCH 060/159] modules: zstd: bump verilator dependency --- .../rules_hdl/bump_verilator.patch | 65 +++++++++++++++++++ dependency_support/rules_hdl/workspace.bzl | 6 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 dependency_support/rules_hdl/bump_verilator.patch diff --git a/dependency_support/rules_hdl/bump_verilator.patch b/dependency_support/rules_hdl/bump_verilator.patch new file mode 100644 index 0000000000..447d89986e --- /dev/null +++ b/dependency_support/rules_hdl/bump_verilator.patch @@ -0,0 +1,65 @@ +--- dependency_support/verilator/verilator.bzl ++++ dependency_support/verilator/verilator.bzl +@@ -22,7 +22,7 @@ + http_archive, + name = "verilator", + build_file = Label("//dependency_support/verilator:verilator.BUILD.bazel"), +- urls = ["https://github.com/verilator/verilator/archive/refs/tags/v5.034.tar.gz"], +- sha256 = "002da98e316ca6eee40407f5deb7d7c43a0788847d39c90d4d31ddbbc03020e8", +- strip_prefix = "verilator-5.034", ++ urls = ["http://github.com/verilator/verilator/archive/435e1149d57b4f204b5dcff6e93135c0f79599fd.tar.gz"], # current as of 2025-10-01 ++ sha256 = "1e93d81a12529738b6b329191aaee7f84ef5a9d62237d972c1d2cad0a3ad6bb4", ++ strip_prefix = "verilator-435e1149d57b4f204b5dcff6e93135c0f79599fd", + ) +--- dependency_support/verilator/verilator.BUILD.bazel ++++ dependency_support/verilator/verilator.BUILD.bazel +@@ -90,6 +90,7 @@ + "src/V3AstNodeDType.h", + "src/V3AstNodeExpr.h", + "src/V3AstNodeOther.h", ++ "src/V3AstNodeStmt.h", + "src/V3DfgVertices.h", + "src/Verilator.cpp", + ], +@@ -106,6 +107,7 @@ + "V3Ast__gen_yystype.h", + "V3Dfg__gen_ast_to_dfg.h", + "V3Dfg__gen_auto_classes.h", ++ "V3Dfg__gen_clone_cases.h", + "V3Dfg__gen_dfg_to_ast.h", + "V3Dfg__gen_forward_class_decls.h", + "V3Dfg__gen_macros.h", +@@ -121,6 +123,8 @@ + "V3AstNodeExpr.h", + "--astdef", + "V3AstNodeOther.h", ++ "--astdef", ++ "V3AstNodeStmt.h", + "--dfgdef", + "V3DfgVertices.h", + "--classes", +@@ -135,6 +139,7 @@ + "src/V3AstNodeDType.h", + "src/V3AstNodeExpr.h", + "src/V3AstNodeOther.h", ++ "src/V3AstNodeStmt.h", + "src/V3Const.cpp", + "src/V3DfgVertices.h", + "src/Verilator.cpp", +@@ -149,6 +154,8 @@ + "V3AstNodeExpr.h", + "--astdef", + "V3AstNodeOther.h", ++ "--astdef", ++ "V3AstNodeStmt.h", + "--dfgdef", + "V3DfgVertices.h", + "V3Const.cpp", +@@ -234,6 +241,7 @@ + ":V3Const__gen.cpp", + ":V3Dfg__gen_ast_to_dfg.h", + ":V3Dfg__gen_auto_classes.h", ++ ":V3Dfg__gen_clone_cases.h", + ":V3Dfg__gen_dfg_to_ast.h", + ":V3Dfg__gen_forward_class_decls.h", + ":V3Dfg__gen_macros.h", diff --git a/dependency_support/rules_hdl/workspace.bzl b/dependency_support/rules_hdl/workspace.bzl index 34617c4f82..a449083211 100644 --- a/dependency_support/rules_hdl/workspace.bzl +++ b/dependency_support/rules_hdl/workspace.bzl @@ -40,5 +40,9 @@ def repo(): urls = [ "https://github.com/hdl/bazel_rules_hdl/archive/%s.tar.gz" % git_hash, ], - patches = ["@//dependency_support/rules_hdl:add_standalone_verilator.patch"], + patches = [ + "@//dependency_support/rules_hdl:add_standalone_verilator.patch", + "@//dependency_support/rules_hdl:bump_verilator.patch", + ], + patch_tool = "patch", ) From d56a9c778ca0e38fec0dbcb6bc3425894e611727 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Mon, 25 Aug 2025 12:36:23 +0200 Subject: [PATCH 061/159] modules: zstd: cocotb: run verilator on a single thread --- xls/modules/zstd/zstd_dec_cocotb_cli.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_cli.py b/xls/modules/zstd/zstd_dec_cocotb_cli.py index e29a3b73db..d3d3e7335d 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_cli.py +++ b/xls/modules/zstd/zstd_dec_cocotb_cli.py @@ -58,8 +58,6 @@ def usage(): "-Wno-fatal", "-Wwarn-ASSIGNIN", "--trace-fst", # trace in more space-efficient format than vcd - "--trace-threads", "2", # 2 is maximum "-O3", - "--threads", str(cpu_count() - 2), "--assert", ], sim="verilator") From e985861f2976eecaf14c4d8bfd6d7153858483e7 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Mon, 25 Aug 2025 12:52:30 +0200 Subject: [PATCH 062/159] modules: zstd: cocotb: don't make unused signals public cocotb marks all signals as public, siginficantly restricting verilator from doing optimizations - public signal can change anytime and verilator must take it into account Instead of using global public flag, we list individual public signals/modules in verilator config --- xls/modules/zstd/BUILD | 1 + xls/modules/zstd/rtl/BUILD | 1 + xls/modules/zstd/rtl/cocotb_public_vars.vlt | 125 ++++++++++++++++++++ xls/modules/zstd/zstd_dec_cocotb_cli.py | 1 + xls/modules/zstd/zstd_dec_cocotb_common.py | 4 + 5 files changed, 132 insertions(+) create mode 100644 xls/modules/zstd/rtl/cocotb_public_vars.vlt diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index a0a2914a6d..f3ea7ed00a 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1795,6 +1795,7 @@ py_library( "//xls/modules/zstd/rtl:ram_1r1w.v", "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", "//xls/modules/zstd/rtl:zstd_dec_wrapper.sv", + "//xls/modules/zstd/rtl:cocotb_public_vars.vlt", "@com_github_alexforencich_verilog_axi//:rtl/arbiter.v", "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar.v", "@com_github_alexforencich_verilog_axi//:rtl/axi_crossbar_addr.v", diff --git a/xls/modules/zstd/rtl/BUILD b/xls/modules/zstd/rtl/BUILD index 1a6c5f1592..2fc37dbf3d 100644 --- a/xls/modules/zstd/rtl/BUILD +++ b/xls/modules/zstd/rtl/BUILD @@ -38,6 +38,7 @@ exports_files( "xls_fifo_wrapper.sv", "ram_demux_wrapper.v", "ram_1r1w.v", + "cocotb_public_vars.vlt", ], ) diff --git a/xls/modules/zstd/rtl/cocotb_public_vars.vlt b/xls/modules/zstd/rtl/cocotb_public_vars.vlt new file mode 100644 index 0000000000..8a75c59ae6 --- /dev/null +++ b/xls/modules/zstd/rtl/cocotb_public_vars.vlt @@ -0,0 +1,125 @@ +`verilator_config + +// Signals needed for tests of decoder "internals" + +public_flat_rd -module "ram_1r1w" -var "mem" + +public_flat_rd -module "xls_modules_zstd_sequence_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceDecoderCtrl_0__FseLookupCtrl_0_next" -var "*" +public_flat_rd -module "xls_modules_zstd_comp_lookup_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__HuffmanFseWeightsDecoder_0__CompLookupDecoder_0__64_8_16_1_15_8_32_1_7_9_8_1_8_16_1_next" -var "*" +public_flat_rd -module "xls_modules_zstd_fse_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__FseDecoder_0__64_15_32_1_64_7_next" -var "*" +public_flat_rd -module "xls_modules_zstd_sequence_executor__ZstdDecoderInst__ZstdDecoder_0__SequenceExecutor_0__32_64_64_0_0_0_13_8192_65536_next" -var "*" +public_flat_rd -module "xls_modules_zstd_fse_table_creator__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__FseLookupDecoder_0__CompLookupDecoder_0__FseTableCreator_0__8_16_1_15_32_1_9_8_1_8_16_1_next_inst16" -var "*" +public_flat_rd -module "xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next" -var "*" +public_flat_rd -module "xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next" -var "*" +public_flat_rd -module "xls_modules_zstd_block_header_dec__ZstdDecoderInst__ZstdDecoder_0__BlockHeaderDecoder_0__32_64_next" -var "*" +public_flat_rd -module "xls_modules_zstd_raw_block_dec__ZstdDecoderInst__ZstdDecoder_0__RawBlockDecoder_0__32_64_next" -var "*" +public_flat_rd -module "xls_modules_zstd_rle_block_dec__ZstdDecoderInst__ZstdDecoder_0__RleBlockDecoder_0__64_next" -var "*" +public_flat_rd -module "xls_modules_zstd_comp_block_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__32_64_8_4_4_16_256_64_6_92_1_8_16_1_8_32_1_6_32_8_9_8_1_8_16_1_13_9_1_8_16_1_15_15_32_1_9_8_1_8_16_1_next" -var "*" +public_flat_rd -module "xls_modules_zstd_literals_block_header_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__LiteralsHeaderDecoder_0__32_64_next" -var "*" +public_flat_rd -module "xls_modules_zstd_raw_literals_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__RawLiteralsDecoder_0__32_64_next" -var "*" +public_flat_rd -module "xls_modules_zstd_rle_literals_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__RleLiteralsDecoder_0__64_next" -var "*" +public_flat_rd -module "xls_modules_zstd_huffman_decoder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanDecoder_0_next" -var "*" +public_flat_rd -module "xls_modules_zstd_huffman_weights_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__32_64_4_8_16_1_8_32_1_9_8_1_8_16_1_6_32_8_next" -var "*" +public_flat_rd -module "xls_modules_zstd_huffman_weights_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__HuffmanRawWeightsDecoder_0__32_64_96_7_6_32_8_next" -var "*" +public_flat_rd -module "xls_modules_zstd_huffman_weights_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__HuffmanFseWeightsDecoder_0__32_64_4_8_16_1_8_32_1_64_7_9_8_1_8_16_1_6_32_8_next" -var "*" +public_flat_rd -module "xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__HuffmanControlAndSequenceMultiStreamHandler_0__HuffmanControlAndSequenceInternal_0__32_next" -var "*" +public_flat_rd -module "xls_modules_zstd_sequence_conf_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__SequenceConfDecoder_0__32_64_next" -var "*" + + +// Signals of top module +// Commented ones are currently not used by tests (but might be needed +// in the future) +public_flat_rw -module "zstd_dec_wrapper" -var "clk" +public_flat_rw -module "zstd_dec_wrapper" -var "rst" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_aw_awready" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_w_wready" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_b_bid" +// public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_b_bresp" +// public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_b_buser" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_b_bvalid" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_ar_arready" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_r_rid" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_r_rdata" +// public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_r_rresp" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_r_rlast" +// public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_r_ruser" +public_flat_rw -module "zstd_dec_wrapper" -var "memory_axi_r_rvalid" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awid" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awaddr" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awlen" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awsize" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awburst" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awlock" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awcache" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awprot" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awqos" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awregion" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awuser" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_aw_awvalid" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_w_wdata" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_w_wstrb" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_w_wlast" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_w_wuser" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_w_wvalid" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_b_bready" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arid" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_araddr" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arlen" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arsize" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arburst" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arlock" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arcache" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arprot" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arqos" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arregion" +// public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_aruser" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_ar_arvalid" +public_flat_rw -module "zstd_dec_wrapper" -var "csr_axi_r_rready" +public_flat_rw -module "zstd_dec_wrapper" -var "notify_rdy" + +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awid" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awaddr" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awlen" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awsize" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awburst" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awlock" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awcache" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awprot" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awqos" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awregion" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awuser" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_aw_awvalid" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_w_wdata" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_w_wstrb" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_w_wlast" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_w_wuser" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_w_wvalid" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_b_bready" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arid" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_araddr" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arlen" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arsize" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arburst" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arlock" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arcache" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arprot" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arqos" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arregion" +// public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_aruser" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_ar_arvalid" +public_flat_rd -module "zstd_dec_wrapper" -var "memory_axi_r_rready" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_aw_awready" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_w_wready" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_b_bid" +// public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_b_bresp" +// public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_b_buser" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_b_bvalid" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_ar_arready" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_r_rid" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_r_rdata" +// public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_r_rresp" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_r_rlast" +// public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_r_ruser" +public_flat_rd -module "zstd_dec_wrapper" -var "csr_axi_r_rvalid" +public_flat_rd -module "zstd_dec_wrapper" -var "notify_data" +public_flat_rd -module "zstd_dec_wrapper" -var "notify_vld" diff --git a/xls/modules/zstd/zstd_dec_cocotb_cli.py b/xls/modules/zstd/zstd_dec_cocotb_cli.py index d3d3e7335d..e8b7fa1d37 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_cli.py +++ b/xls/modules/zstd/zstd_dec_cocotb_cli.py @@ -58,6 +58,7 @@ def usage(): "-Wno-fatal", "-Wwarn-ASSIGNIN", "--trace-fst", # trace in more space-efficient format than vcd + "--no-public-flat-rw", "-O3", "--assert", ], sim="verilator") diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 329bca47d0..d750d55d5c 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -893,4 +893,8 @@ def run_test(test_module, build_args=[], sim="icarus"): "xls/modules/zstd/rtl/ram_1r1w.v", ] + if sim == "verilator": + # verilator-specific config + verilog_sources += "xls/modules/zstd/rtl/cocotb_public_vars.vlt", + cocotb_utils.run_test(toplevel, test_module, verilog_sources, build_args=build_args, sim=sim) From 603a7a12080a54de20c90b3233c50b68f637192a Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Tue, 16 Sep 2025 19:10:14 +0200 Subject: [PATCH 063/159] modules: zstd: huffman literals dec: fix broken raw huffman weights ordering First symbol is stored in the most significant nibble of given input byte requiring us to swap each adjecent weights (rfc8878#section-4.2.1.1) --- xls/modules/zstd/huffman_literals_dec.x | 36 ++++++++++++------------- xls/modules/zstd/huffman_weights_dec.x | 30 +++++++++++---------- xls/modules/zstd/literals_decoder.x | 12 ++++----- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index 22f328688d..d84a541baa 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -588,12 +588,12 @@ const TEST_MEMORY: TestAxiRamWrReq[7] = [ // HTD Header: 0x84 (Direct representation, HTD length: 3) // Huffman Tree Description // code symbol length weight - // N/A 0x03 0 0 + // N/A 0x02 0 0 // 0b0000 0x04 4 1 // 0b0001 0x05 4 1 // last weight implicit - // 0b001 0x02 3 2 - // 0b01 0x01 2 3 - // 0b1 0x00 1 4 + // 0b001 0x03 3 2 + // 0b01 0x00 2 3 + // 0b1 0x01 1 4 // 0b00001 padding TestAxiRamWrReq { addr: TestAxiRamAddr:0x0, data: (u16:0b00001_1_01_0000_0001 ++ u24:0x100234 ++ u8:0x84) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, @@ -615,12 +615,12 @@ const TEST_MEMORY: TestAxiRamWrReq[7] = [ // Jump Table: 0x0002_0002_0002 (Stream1: 2 bytes; Stream2: 2 bytes; Stream3: 2 bytes) // Huffman Tree Description // code symbol length weight - // N/A 0x03 0 0 + // N/A 0x02 0 0 // 0b0000 0x04 4 1 // 0b0001 0x05 4 1 // last weight implicit - // 0b001 0x02 3 2 - // 0b01 0x01 2 3 - // 0b1 0x00 1 4 + // 0b001 0x03 3 2 + // 0b01 0x00 2 3 + // 0b1 0x01 1 4 // 0b00001 padding TestAxiRamWrReq { addr: TestAxiRamAddr:0x40, data: (u32:0x0002_0002 ++ u24:0x100234 ++ u8:0x84) as TestAxiRamData, mask: TestAxiRamMask:0xFF }, // AXI addr: 0x200 ^ ^ ^ @@ -689,7 +689,7 @@ const TEST_CTRL: TestCtrl[4] = [ const TEST_DECODED_LITERALS = common::LiteralsDataWithSync[10]:[ // Literals #0 common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: true, id: u32:0, @@ -697,7 +697,7 @@ const TEST_DECODED_LITERALS = common::LiteralsDataWithSync[10]:[ }, // Literals #1 common::LiteralsDataWithSync { - data: common::LitData:0x0001_0405, + data: common::LitData:0x0100_0405, length: common::LitLength:4, last: true, id: u32:1, @@ -705,28 +705,28 @@ const TEST_DECODED_LITERALS = common::LiteralsDataWithSync[10]:[ }, // Literals #2 common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: false, id: u32:2, literals_last: false, }, common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: false, id: u32:2, literals_last: false, }, common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: false, id: u32:2, literals_last: false, }, common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: true, id: u32:2, @@ -734,28 +734,28 @@ const TEST_DECODED_LITERALS = common::LiteralsDataWithSync[10]:[ }, // Literals #3 common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: false, id: u32:3, literals_last: true, }, common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: false, id: u32:3, literals_last: true, }, common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: false, id: u32:3, literals_last: true, }, common::LiteralsDataWithSync { - data: common::LitData:0x0504_0100, + data: common::LitData:0x0504_0001, length: common::LitLength:4, last: true, id: u32:3, diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index 77391ea077..ff725bcc24 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -172,9 +172,11 @@ proc HuffmanRawWeightsDecoder< let last_weight = (next_power - state.sum) as u4; // It is required to change the ordering of the weights. - // Huffman literals decoder expects the weight of the first symbol + // - First symbol is stored in the most significant nibble of given input + // byte requiring us to swap each adjecent weights (rfc8878#section-4.2.1.1). + // - Huffman literals decoder expects the weight of the first symbol // as the most significant nibble at the most significant byte - // in the first cell of the WeightsMemory. + // in the first cell of the WeightsMemory requiring us to reverse them. // Inject the last weight, take into the acount the reverse let weights = if (state.req.n_symbols > u8:0 && (mem_rd_resp_valid && mem_rd_resp.last)) { @@ -186,16 +188,16 @@ proc HuffmanRawWeightsDecoder< weights }; - let reversed_weights = match(AXI_DATA_W) { + let weights = match(AXI_DATA_W) { u32:32 => ( - weights[7] ++ weights[6] ++ weights[5] ++ weights[4] ++ - weights[3] ++ weights[2] ++ weights[1] ++ weights[0] + weights[6] ++ weights[7] ++ weights[4] ++ weights[5] ++ + weights[2] ++ weights[3] ++ weights[0] ++ weights[1] ) as uN[AXI_DATA_W], u32:64 => ( - weights[15] ++ weights[14] ++ weights[13] ++ weights[12] ++ - weights[11] ++ weights[10] ++ weights[9] ++ weights[8] ++ - weights[7] ++ weights[6] ++ weights[5] ++ weights[4] ++ - weights[3] ++ weights[2] ++ weights[1] ++ weights[0] + weights[14] ++ weights[15] ++ weights[12] ++ weights[13] ++ + weights[10] ++ weights[11] ++ weights[8] ++ weights[9] ++ + weights[6] ++ weights[7] ++ weights[4] ++ weights[5] ++ + weights[2] ++ weights[3] ++ weights[0] ++ weights[1] ) as uN[AXI_DATA_W], _ => fail!("unsupported_axi_data_width", uN[AXI_DATA_W]:0), }; @@ -210,7 +212,7 @@ proc HuffmanRawWeightsDecoder< let (buffer, buffer_len) = if do_recv_data && mem_rd_resp_valid { ( - buffer | ((reversed_weights as uN[BUFF_LEN] << (BUFF_LEN - AXI_DATA_W - buffer_len as u32))), + buffer | ((weights as uN[BUFF_LEN] << (BUFF_LEN - AXI_DATA_W - buffer_len as u32))), buffer_len + (AXI_DATA_W as uN[BUFF_LEN_LOG2]), ) } else { @@ -2181,10 +2183,10 @@ proc HuffmanWeightsDecoder_test { let tok = for (i, tok) in u32:0..u32:32 { let expected_value = if i < u32:16 { ( - (test_data[4*i + u32:1] as u4) ++ ((test_data[4*i + u32:1] >> u32:4) as u4) ++ - (test_data[4*i + u32:2] as u4) ++ ((test_data[4*i + u32:2] >> u32:4) as u4) ++ - (test_data[4*i + u32:3] as u4) ++ ((test_data[4*i + u32:3] >> u32:4) as u4) ++ - (test_data[4*i + u32:4] as u4) ++ ((test_data[4*i + u32:4] >> u32:4) as u4) + ((test_data[4*i + u32:1] >> u32:4) as u4) ++ (test_data[4*i + u32:1] as u4) ++ + ((test_data[4*i + u32:2] >> u32:4) as u4) ++ (test_data[4*i + u32:2] as u4) ++ + ((test_data[4*i + u32:3] >> u32:4) as u4) ++ (test_data[4*i + u32:3] as u4) ++ + ((test_data[4*i + u32:4] >> u32:4) as u4) ++ (test_data[4*i + u32:4] as u4) ) } else { u32:0 diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x index bf0942109d..442cb0776c 100644 --- a/xls/modules/zstd/literals_decoder.x +++ b/xls/modules/zstd/literals_decoder.x @@ -2001,7 +2001,7 @@ proc LiteralsDecoder_test { SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:4, - content: CopyOrMatchContent:0x0504_0100, + content: CopyOrMatchContent:0x0504_0001, last: true }, // Literals #5 (RLE) @@ -2021,7 +2021,7 @@ proc LiteralsDecoder_test { SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:4, - content: CopyOrMatchContent:0x0001_0405, + content: CopyOrMatchContent:0x0100_0405, last: true }, // Literals #7 (RLE) @@ -2030,13 +2030,13 @@ proc LiteralsDecoder_test { SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:8, - content: CopyOrMatchContent:0x0504_0100_0504_0100, + content: CopyOrMatchContent:0x0504_0001_0504_0001, last: false }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:8, - content: CopyOrMatchContent:0x0504_0100_0504_0100, + content: CopyOrMatchContent:0x0504_0001_0504_0001, last: true }, // Literals #9 (RAW) @@ -2068,13 +2068,13 @@ proc LiteralsDecoder_test { SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:8, - content: CopyOrMatchContent:0x0504_0100_0504_0100, + content: CopyOrMatchContent:0x0504_0001_0504_0001, last: false }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:8, - content: CopyOrMatchContent:0x0504_0100_0504_0100, + content: CopyOrMatchContent:0x0504_0001_0504_0001, last: true }, ]; From 558b85ab45f227b305792cf68aab867b0f511b9b Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Wed, 17 Sep 2025 15:04:50 +0200 Subject: [PATCH 064/159] modules: zstd: huffman weights dec: fix broken tree_description_size calculation As defined in https://datatracker.ietf.org/doc/html/rfc8878#section-4.2.1.1 raw weights consume ceil(n_symb/2) bytes. The correct equivalent of that ceiled div is: (n_symb+1)>>1 rather than (n_symb>>1)+1 we previously used --- xls/modules/zstd/huffman_weights_dec.x | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index ff725bcc24..f4a40267b7 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -1407,9 +1407,11 @@ pub proc HuffmanWeightsDecoder< let resp = match weights_type { WeightsType::RAW => { + // based on https://datatracker.ietf.org/doc/html/rfc8878#section-4.2.1.1 + let weights_size = (raw_weights_req.n_symbols + u8:1) >> u8:1; // equivalent of ceil(n_symbols/2) Resp { status: raw_status, - tree_description_size: (((header_byte - u8:127) >> u8:1) + u8:1) as uN[AXI_ADDR_W] + uN[AXI_ADDR_W]:1, // include header size + tree_description_size: weights_size as uN[AXI_ADDR_W] + uN[AXI_ADDR_W]:1, // include header size } }, WeightsType::FSE => { From 1d38b0c8a4f24146f37d3d15519203c763187a21 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Thu, 18 Sep 2025 11:57:55 +0200 Subject: [PATCH 065/159] modules: zstd: huffman weights dec: fix bad insertion position of last weight Previous version did not take into account weight swapping. --- xls/modules/zstd/huffman_weights_dec.x | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index f4a40267b7..d3c4c5d277 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -178,16 +178,6 @@ proc HuffmanRawWeightsDecoder< // as the most significant nibble at the most significant byte // in the first cell of the WeightsMemory requiring us to reverse them. - // Inject the last weight, take into the acount the reverse - let weights = if (state.req.n_symbols > u8:0 && (mem_rd_resp_valid && mem_rd_resp.last)) { - trace_fmt!("[RAW] The sum of weight's powers of 2's: {}", state.sum); - trace_fmt!("[RAW] The last weight: {}", last_weight); - trace_fmt!("[RAW] Injected {:#x} into weights[{}]", last_weight, MAX_WEIGHTS_IN_PACKET as u8 - state.req.n_symbols); - update(weights, (MAX_WEIGHTS_IN_PACKET as u8 - (state.req.n_symbols % MAX_WEIGHTS_IN_PACKET as u8)), last_weight) - } else { - weights - }; - let weights = match(AXI_DATA_W) { u32:32 => ( weights[6] ++ weights[7] ++ weights[4] ++ weights[5] ++ @@ -200,7 +190,17 @@ proc HuffmanRawWeightsDecoder< weights[2] ++ weights[3] ++ weights[0] ++ weights[1] ) as uN[AXI_DATA_W], _ => fail!("unsupported_axi_data_width", uN[AXI_DATA_W]:0), - }; + } as u4[MAX_WEIGHTS_IN_PACKET]; + + let weights = if (state.req.n_symbols > u8:0 && (mem_rd_resp_valid && mem_rd_resp.last)) { + trace_fmt!("[RAW] The sum of weight's powers of 2's: {}", state.sum); + trace_fmt!("[RAW] The last weight: {}", last_weight); + trace_fmt!("[RAW] MAX_WEIGHTS: {}", MAX_WEIGHTS_IN_PACKET); + trace_fmt!("[RAW] Injected {:#x} into weights[{}]", last_weight, (state.req.n_symbols % MAX_WEIGHTS_IN_PACKET as u8)); + update(weights, (state.req.n_symbols % MAX_WEIGHTS_IN_PACKET as u8), last_weight) + } else { + weights + } as uN[AXI_DATA_W]; if do_recv_data && mem_rd_resp_valid { trace_fmt!("[RAW] Weights: {:#x}", weights); From cd8d9230e7818a0746c033963a1e2a0045372975 Mon Sep 17 00:00:00 2001 From: Szymon Gizler Date: Wed, 24 Sep 2025 15:38:16 +0200 Subject: [PATCH 066/159] modules: zstd: huffman weights dec: test weight decoding bugfixes Old test reimplemented parts of decoder and compared output of proc against reimplementation. It had very little value (as we are likely to slip same bug into both implementations) and was hard to reason about. New test compares decoder against readable explicit golden prepared to test all recently fixed bugs (swapped weights, bad description size and wrong last weight position) --- xls/modules/zstd/huffman_weights_dec.x | 58 +++++++++++--------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index d3c4c5d277..bf75392710 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -1645,20 +1645,30 @@ const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP2_RAM_WORD_PART // RAW weights const TEST_RAW_INPUT_ADDR = uN[TEST_AXI_ADDR_W]:0x40; -// Weights sum is 1010, so the last one will be 14 -const TEST_RAW_DATA = u8[65]:[ - // len x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF - u8:248, u8:0xB__6, u8:0x8__5, u8:0x6__A, u8:0x9__C, u8:0x0__C, u8:0xA__9, u8:0x0__0, u8:0xD__0, // 0x0x - u8:0x6__E, u8:0x3__9, u8:0x8__4, u8:0x7__C, u8:0xC__2, u8:0x4__2, u8:0xB__A, u8:0x4__E, // 0x1x - u8:0xF__6, u8:0x2__7, u8:0x9__4, u8:0xD__1, u8:0xD__8, u8:0x2__B, u8:0xE__2, u8:0xD__1, // 0x2x - u8:0x8__F, u8:0x2__4, u8:0xD__3, u8:0x0__E, u8:0xF__E, u8:0x1__B, u8:0xF__9, u8:0x8__2, // 0x3x - u8:0xC__A, u8:0x6__1, u8:0x0__3, u8:0xD__C, u8:0xF__5, u8:0x1__D, u8:0x7__0, u8:0x1__6, // 0x4x - u8:0xA__A, u8:0x3__2, u8:0x8__8, u8:0x0__6, u8:0xE__7, u8:0x6__7, u8:0x8__E, u8:0x6__2, // 0x5x - u8:0x1__F, u8:0x3__E, u8:0xF__0, u8:0xC__7, u8:0x4__1, u8:0x7__E, u8:0x8__C, u8:0x8__4, // 0x6x - u8:0x3__3, u8:0xA__8, u8:0xE__E, u8:0x4__B, u8:0x0__0, u8:0x0__0, u8:0x0__0, u8:0x0__0, // 0x7x +const TEST_RAW_TREE_DESCRIPTION_SIZE = u32:15; // (header + weight stream) + +const TEST_RAW_DATA = u8[19]:[ + u8:155, // header + // weights stream + u8:0x75, u8:0x66, u8:0x66, u8:0x66, + u8:0x66, u8:0x66, u8:0x55, u8:0x55, + u8:0x44, u8:0x44, u8:0x33, u8:0x31, + u8:0x11, u8:0x00, + // dummy leftover + u8:0xDE, u8:0xAD, u8:0xBE, u8:0xEF, ]; -const TEST_RAW_DATA_LAST_WEIGHT = u8:0xA; +const_assert!(TEST_WEIGHTS_RAM_DATA_W == u32:32); +const TEST_RAW_RAM_PACKET_COUNT = TEST_WEIGHTS_RAM_SIZE / TEST_WEIGHTS_RAM_PARTITION_SIZE; + +const TEST_RAW_EXPECTED_WEIGHT_RAM = uN[TEST_WEIGHTS_RAM_DATA_W][TEST_RAW_RAM_PACKET_COUNT]:[ + uN[TEST_WEIGHTS_RAM_DATA_W]:0x75666666, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x66665555, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x44443331, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x11001000, + // ^- last weight + uN[TEST_WEIGHTS_RAM_DATA_W]:0x0, ... +]; // FSE weights const TEST_FSE_INPUT_ADDR = uN[TEST_AXI_ADDR_W]:0x200; @@ -2171,29 +2181,11 @@ proc HuffmanWeightsDecoder_test { let (tok, resp) = recv(tok, resp_r); trace_fmt!("[TEST] Received respose {:#x}", resp); assert_eq(HuffmanWeightsDecoderStatus::OKAY, resp.status); - assert_eq((((TEST_RAW_DATA[0] - u8:127) >> u32:1) + u8:2) as uN[TEST_AXI_ADDR_W], resp.tree_description_size); - - // Insert last weight in test data - let last_weight_idx = ((TEST_RAW_DATA[0] as u32 - u32:127) / u32:2) + u32:1; - let last_weight_entry = ( - TEST_RAW_DATA[last_weight_idx] | - (TEST_RAW_DATA_LAST_WEIGHT << (u32:4 * (u32:1 - ((TEST_RAW_DATA[0] - u8:127) as u1 as u32)))) - ); - let test_data = update(TEST_RAW_DATA, last_weight_idx, last_weight_entry); + assert_eq(TEST_RAW_TREE_DESCRIPTION_SIZE as uN[TEST_AXI_ADDR_W], resp.tree_description_size); // Check output RAM - let tok = for (i, tok) in u32:0..u32:32 { - let expected_value = if i < u32:16 { - ( - ((test_data[4*i + u32:1] >> u32:4) as u4) ++ (test_data[4*i + u32:1] as u4) ++ - ((test_data[4*i + u32:2] >> u32:4) as u4) ++ (test_data[4*i + u32:2] as u4) ++ - ((test_data[4*i + u32:3] >> u32:4) as u4) ++ (test_data[4*i + u32:3] as u4) ++ - ((test_data[4*i + u32:4] >> u32:4) as u4) ++ (test_data[4*i + u32:4] as u4) - ) - } else { - u32:0 - }; - + let tok = for (i, tok) in u32:0..array_size(TEST_RAW_EXPECTED_WEIGHT_RAM) { + let expected_value = TEST_RAW_EXPECTED_WEIGHT_RAM[i]; let weights_ram_rd_req = WeightsRamRdReq { addr: i as uN[TEST_WEIGHTS_RAM_ADDR_W], mask: !uN[TEST_WEIGHTS_RAM_NUM_PARTITIONS]:0, From 297ecfbb14d40f7fc500856938b7ee916fc3363b Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Mon, 6 Oct 2025 15:31:12 +0200 Subject: [PATCH 067/159] modules: zstd: fse table decoder: fix handling last read bit if it is a remainder --- xls/modules/zstd/BUILD | 2 + xls/modules/zstd/common.x | 9 ++-- xls/modules/zstd/comp_lookup_dec.x | 30 ++++++------ xls/modules/zstd/fse_lookup_dec.x | 32 +++++++------ xls/modules/zstd/fse_proba_freq_dec.x | 66 ++++++++++++++++++-------- xls/modules/zstd/huffman_weights_dec.x | 2 +- xls/modules/zstd/rle_lookup_dec.x | 4 +- xls/modules/zstd/sequence_dec.x | 48 +++++++++++++------ 8 files changed, 125 insertions(+), 68 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f3ea7ed00a..c9278be659 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -2738,6 +2738,7 @@ xls_dslx_verilog( "generator": "pipeline", "delay_model": "asap7", "pipeline_stages": "6", + "worst_case_throughput": "2", "reset": "rst", "use_system_verilog": "false", "multi_proc": "true", @@ -2757,6 +2758,7 @@ xls_benchmark_ir( src = ":fse_lookup_ctrl_verilog.opt.ir", benchmark_ir_args = { "delay_model": "asap7", + "worst_case_throughput": "2", "pipeline_stages": "6", }, ) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 7e55a76c34..f018ed22d4 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -179,8 +179,8 @@ pub struct FseTableRecord { base: u16 } -pub struct FseRemainder { value: u1, valid: bool } -pub struct FseProbaFreqDecoderCtrl { remainder: FseRemainder, finished: bool } +pub struct Remainder { value: u1, valid: bool } +pub struct FseProbaFreqDecoderCtrl { remainder: Remainder, finished: bool } pub struct FseTableCreatorCtrl { accuracy_log: FseAccuracyLog, @@ -313,11 +313,14 @@ pub enum LookupDecoderStatus: u1 { ERROR = u1:1, } -pub struct LookupDecoderReq {} +pub struct LookupDecoderReq { + remainder: Remainder +} pub struct LookupDecoderResp { status: LookupDecoderStatus, accuracy_log: FseAccuracyLog, + remainder: Remainder, } pub struct DataArray{ diff --git a/xls/modules/zstd/comp_lookup_dec.x b/xls/modules/zstd/comp_lookup_dec.x index aab59ba0a2..d65d8883f0 100644 --- a/xls/modules/zstd/comp_lookup_dec.x +++ b/xls/modules/zstd/comp_lookup_dec.x @@ -33,6 +33,7 @@ pub struct CompLookupDecoderResp { status: CompLookupDecoderStatus, accuracy_log: AccuracyLog, consumed_bytes: ConsumedFseBytes, + remainder: common::Remainder, } pub proc CompLookupDecoder< @@ -151,7 +152,7 @@ pub proc CompLookupDecoder< let (tok, start_req) = recv(tok, req_r); // start FSE probability frequency decoder - let tok = send(tok, fse_pf_dec_req_s, FsePFDecReq {}); + let tok = send(tok, fse_pf_dec_req_s, FsePFDecReq { remainder: start_req.remainder }); // wait for completion from FSE probability frequency decoder let (tok, pf_dec_res) = recv(tok, fse_pf_dec_resp_r); @@ -173,6 +174,7 @@ pub proc CompLookupDecoder< status: Status::OK, accuracy_log: pf_dec_res.accuracy_log, consumed_bytes: pf_dec_res.consumed_bytes, + remainder: pf_dec_res.remainder, } } else { Resp { status: Status::ERROR, ..zero!() } @@ -268,7 +270,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3} + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3, remainder: zero!() } ), ( u64[64]:[u64:0x1861862062081932, u64:0xC18628A106184184, u64:0x850720FACC49238, u64:0, ...], @@ -403,7 +405,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x9, num_of_bits: u8:0x6, base: u16:0x40 }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7, consumed_bytes: ConsumedFseBytes:21} + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7, consumed_bytes: ConsumedFseBytes:21, remainder: zero!() } ), ( u64[64]:[u64:0x60C3082082085072, u64:0x1C06F8077D850F20, u64:0, ...], @@ -538,7 +540,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0xb, num_of_bits: u8:0x3, base: u16:0x58 }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7, consumed_bytes: ConsumedFseBytes:13 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:7, consumed_bytes: ConsumedFseBytes:13, remainder: zero!() } ), ( u64[64]:[u64:0x41081C158003A5D0, u64:0, ...], @@ -577,7 +579,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x17 }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3, remainder: zero!() } ), ( u64[64]:[u64:0x1101141108088A1, u64:0xA210842108421011, u64:0xAC90E792007A5B4, u64:0, ...], @@ -648,7 +650,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x1c, num_of_bits: u8:0x3, base: u16:0x8 }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:6, consumed_bytes: ConsumedFseBytes:19 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:6, consumed_bytes: ConsumedFseBytes:19, remainder: zero!() } ), ( u64[64]:[u64:0x4AF830AC90E7920, u64:0, ...], @@ -687,7 +689,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x1, num_of_bits: u8:0x1, base: u16:0xa }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3, remainder: zero!() } ), ( u64[64]:[u64:0xF47FFEBBFF1D25C0, u64:0, ...], @@ -726,7 +728,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:3, remainder: zero!() } ), ( u64[64]:[u64:0xA84DF134544CA40, u64:0xEEC609988403B0C, u64:0, ...], @@ -765,7 +767,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x14, num_of_bits: u8:0x3, base: u16:0x8 }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:10 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:10, remainder: zero!() } ), ( u64[64]:[u64:0x38100EEC60998840, u64:0, ...], @@ -804,7 +806,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x2, base: u16:0xc }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:6 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:6, remainder: zero!() } ), ( u64[64]:[u64:0x6B1CA24D0CE43810, u64:0x6651065104A4DFFD, u64:0, ...], @@ -843,7 +845,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0xf, num_of_bits: u8:0x3, base: u16:0x8 }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:10 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, consumed_bytes: ConsumedFseBytes:10, remainder: zero!() } ), ( u64[64]:[u64:0x604FC0502602814, u64:0xE030505040131FF6, u64:0, ...], @@ -1361,7 +1363,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x10, num_of_bits: u8:0x2, base: u16:0x1fc }, FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0x1fc }, ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:9, consumed_bytes: ConsumedFseBytes:10 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:9, consumed_bytes: ConsumedFseBytes:10, remainder: zero!() } ), ( u64[64]:[u64:0x140FE03050504013, u64:0, ...], @@ -1624,7 +1626,7 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x5, num_of_bits: u8:0x2, base: u16:0xfc }, zero!(), ... ], - CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:8, consumed_bytes: ConsumedFseBytes:7 } + CompLookupDecoderResp { status: CompLookupDecoderStatus::OK, accuracy_log: AccuracyLog:8, consumed_bytes: ConsumedFseBytes:7, remainder: zero!() } ), ]; @@ -1793,7 +1795,7 @@ proc CompLookupDecoderTest { next(_: ()) { let tok = join(); // This has to be outside of unroll_for!, otherwise typechecker reports type mismatch on identical types - let req_start = Req {}; + let req_start = zero!(); let tok = unroll_for!(test_i, tok): (u32, token) in u32:0..array_size(COMP_LOOKUP_DECODER_TESTCASES) { let (input, output, resp_ok) = COMP_LOOKUP_DECODER_TESTCASES[test_i]; diff --git a/xls/modules/zstd/fse_lookup_dec.x b/xls/modules/zstd/fse_lookup_dec.x index 7a3707a443..49337d67c0 100644 --- a/xls/modules/zstd/fse_lookup_dec.x +++ b/xls/modules/zstd/fse_lookup_dec.x @@ -30,7 +30,10 @@ import xls.modules.zstd.ram_mux; type AccuracyLog = common::FseAccuracyLog; -pub struct FseLookupDecoderReq { is_rle: bool } +pub struct FseLookupDecoderReq { + is_rle: bool, + remainder: common::Remainder +} pub type FseLookupDecoderStatus = common::LookupDecoderStatus; pub type FseLookupDecoderResp = common::LookupDecoderResp; @@ -233,12 +236,12 @@ pub proc FseLookupDecoder< let tok3 = join(tok3_1, tok3_0); - let tok4_0 = send_if(tok3, rle_lookup_req_s, req.is_rle, LookupDecoderReq {}); + let tok4_0 = send_if(tok3, rle_lookup_req_s, req.is_rle, zero!()); if req.is_rle { trace_fmt!("[FseLookupDecoder]: Send request to RLE lookup decoder"); } else { }; let (tok5_0, rle_lookup_resp) = recv_if(tok4_0, rle_lookup_resp_r, req.is_rle, zero!()); if req.is_rle { trace_fmt!("[FseLookupDecoder]: Received response from RLE lookup decoder"); } else { }; - let tok4_1 = send_if(tok3, comp_lookup_req_s, !req.is_rle, CompLookupDecoderReq {}); + let tok4_1 = send_if(tok3, comp_lookup_req_s, !req.is_rle, CompLookupDecoderReq { remainder: req.remainder }); if !req.is_rle { trace_fmt!("[FseLookupDecoder]: Send request to Comp lookup decoder"); } else { }; let (tok5_1, comp_lookup_resp) = recv_if(tok4_1, comp_lookup_resp_r, !req.is_rle, zero!()); if !req.is_rle { trace_fmt!("[FseLookupDecoder]: Received response from Comp lookup decoder"); } else { }; @@ -248,7 +251,8 @@ pub proc FseLookupDecoder< let resp = if req.is_rle { rle_lookup_resp } else { Resp { status: comp_lookup_resp.status, - accuracy_log: comp_lookup_resp.accuracy_log + accuracy_log: comp_lookup_resp.accuracy_log, + remainder: comp_lookup_resp.remainder, } }; let tok6 = send(tok5, fse_lookup_dec_resp_s, resp); @@ -322,8 +326,8 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0xa, num_of_bits: u8:0x0, base: u16:0x0 }, zero!(), ... ], - FseLookupDecoderReq { is_rle: true }, - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0 } + FseLookupDecoderReq { is_rle: true, remainder: zero!() }, + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0, remainder: zero!() } ), ( u64[64]:[u64:0x2, u64:0, ...], @@ -331,8 +335,8 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x2, num_of_bits: u8:0x0, base: u16:0x0 }, zero!(), ... ], - FseLookupDecoderReq { is_rle: true }, - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0 } + FseLookupDecoderReq { is_rle: true, remainder: zero!() }, + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0, remainder: zero!() } ), ( u64[64]:[u64:0x7, u64:0, ...], @@ -340,8 +344,8 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x7, num_of_bits: u8:0x0, base: u16:0x0 }, zero!(), ... ], - FseLookupDecoderReq { is_rle: true }, - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0 } + FseLookupDecoderReq { is_rle: true, remainder: zero!() }, + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:0, remainder: zero!() } ), // COMPRESSED @@ -382,8 +386,8 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x15 }, zero!(), ... ], - FseLookupDecoderReq { is_rle: false }, - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + FseLookupDecoderReq { is_rle: false, remainder: zero!() }, + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, remainder: zero!() } ), ( u64[64]:[u64:0x41081C158003A5D0, u64:0, ...], @@ -422,8 +426,8 @@ const COMP_LOOKUP_DECODER_TESTCASES: (u64[64], FseTableRecord[TEST_FSE_RAM_SIZE] FseTableRecord { symbol: u8:0x0, num_of_bits: u8:0x0, base: u16:0x17 }, zero!(), ... ], - FseLookupDecoderReq { is_rle: false }, - FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5 } + FseLookupDecoderReq { is_rle: false, remainder: zero!() }, + FseLookupDecoderResp { status: FseLookupDecoderStatus::OK, accuracy_log: AccuracyLog:5, remainder: zero!() } ), ]; diff --git a/xls/modules/zstd/fse_proba_freq_dec.x b/xls/modules/zstd/fse_proba_freq_dec.x index a894685246..a2919f4db5 100644 --- a/xls/modules/zstd/fse_proba_freq_dec.x +++ b/xls/modules/zstd/fse_proba_freq_dec.x @@ -42,8 +42,6 @@ type SequenceData = common::SequenceData; const SYMBOL_COUNT_WIDTH = common::FSE_SYMBOL_COUNT_WIDTH; const ACCURACY_LOG_WIDTH = common::FSE_ACCURACY_LOG_WIDTH; -pub struct Remainder { value: u1, valid: bool } - pub enum FseProbaFreqDecoderStatus: u1 { OK = 0, ERROR = 1, @@ -57,12 +55,15 @@ const MAX_CONSUMED_FSE_BYTES = MAX_CONSUMED_FSE_BITS / u32:8; const CONSUMED_FSE_BYTES_WIDTH = std::clog2(MAX_CONSUMED_FSE_BYTES); pub type ConsumedFseBytes = uN[CONSUMED_FSE_BYTES_WIDTH]; -pub struct FseProbaFreqDecoderReq {} +pub struct FseProbaFreqDecoderReq { + remainder: common::Remainder +} pub struct FseProbaFreqDecoderResp { status: FseProbaFreqDecoderStatus, accuracy_log: AccuracyLog, symbol_count: SymbolCount, consumed_bytes: ConsumedFseBytes, + remainder: common::Remainder, } enum Fsm : u4 { @@ -83,7 +84,7 @@ struct State { // accuracy log used in the FSE decoding table accuracy_log: AccuracyLog, // remaining bit that can be a leftover from parsing small probability frequencies - remainder: Remainder, + remainder: common::Remainder, // indicates if one more packet with zero probabilities is expected next_recv_zero: bool, // information about remaining probability points @@ -172,7 +173,7 @@ fn get_threshold(bit_width: u16, remaining_proba: u16) -> u16 { } // get the adjusted stream value for calculating probability points -fn get_adjusted_value(data: u16, remainder: Remainder) -> u16 { +fn get_adjusted_value(data: u16, remainder: common::Remainder) -> u16 { if remainder.valid { (data << u16:1) | (remainder.value as u16) } else { data } } @@ -254,7 +255,7 @@ pub proc FseProbaFreqDecoder< let do_recv_req = (state.fsm == Fsm::IDLE); let tok0 = join(); - let (tok1_0, _) = recv_if(tok0, req_r, do_recv_req, zero!()); + let (tok1_0, req) = recv_if(tok0, req_r, do_recv_req, zero!()); let do_buff_data_recv = match (state.fsm) { Fsm::RECV_ACCURACY_LOG => true, @@ -274,33 +275,47 @@ pub proc FseProbaFreqDecoder< let tok1 = join(tok1_1, tok1_2); + trace_fmt!("[FseProbaFreqDec] State: {}", state); + trace_fmt!("[FseProbaFreqDec] Read bits {}", read_bits); + trace_fmt!("[FseProbaFreqDec] Read bits mod 8 {}", read_bits_mod8); + let (buffer_ctrl_option, ram_option, resp_option, new_state) = match state.fsm { Fsm::IDLE => { + trace_fmt!("[FseProbaFreqDec] Req: {}", req); ( (false, zero!()), (false, zero!()), (false, zero!()), - State { fsm: Fsm::SEND_ACCURACY_LOG_REQ, ..state }, + State { + fsm: Fsm::SEND_ACCURACY_LOG_REQ, + remainder: req.remainder, + read_bits: req.remainder.valid as ConsumedFseBits, + read_bits_mod8: req.remainder.valid as u3, + ..state + }, ) }, Fsm::SEND_ACCURACY_LOG_REQ => { ( - (true, BufferCtrl { length: ACCURACY_LOG_WIDTH as Length }), + (true, BufferCtrl { length: ACCURACY_LOG_WIDTH as Length - state.read_bits as Length }), (false, zero!()), (false, zero!()), - State { fsm: Fsm::RECV_ACCURACY_LOG, written_symbol_count, ..state }, + State { + fsm: Fsm::RECV_ACCURACY_LOG, + written_symbol_count, + ..state }, ) }, Fsm::RECV_ACCURACY_LOG => { - let accuracy_log = AccuracyLog:5 + out_data.data as AccuracyLog; + let accuracy_log = AccuracyLog:5 + get_adjusted_value(out_data.data as u16, state.remainder) as AccuracyLog; let remaining_proba = RemainingProba:1 << accuracy_log; - ( (false, zero!()), (false, zero!()), (false, zero!()), State { fsm: Fsm::SEND_SYMBOL_REQ, + remainder: zero!(), accuracy_log, remaining_proba, written_symbol_count, @@ -336,11 +351,11 @@ pub proc FseProbaFreqDecoder< let value = get_adjusted_value(data, state.remainder); let (remainder, value) = if (value & lower_mask) < threshold { - (Remainder { value: value[bit_width - u16:1+:u1], valid: true }, value & lower_mask) + (common::Remainder { value: value[bit_width - u16:1+:u1], valid: true }, value & lower_mask) } else if value > lower_mask { - (zero!(), value - threshold) + (zero!(), value - threshold) } else { - (zero!(), value) + (zero!(), value) }; let proba = value as s16 - s16:1; @@ -451,7 +466,7 @@ pub proc FseProbaFreqDecoder< (false, zero!()), State { fsm: new_fsm, - remainder: zero!(), + remainder: zero!(), written_symbol_count, data_invalid, read_bits, @@ -468,7 +483,7 @@ pub proc FseProbaFreqDecoder< (false, zero!()), State { fsm: Fsm::WRITE_ZERO_PROBA, - remainder: zero!(), + remainder: zero!(), written_symbol_count, zero_proba_count, next_recv_zero, @@ -541,9 +556,15 @@ pub proc FseProbaFreqDecoder< }, Fsm::WAIT_FOR_COMPLETION => { if written_symbol_count == state.symbol_count { + let leave_remainder = state.read_bits_mod8 == u3:1 && state.remainder.valid; + let shift_bits = if leave_remainder { + u3:0 + } else { + state.read_bits_mod8 + }; ( - if state.read_bits_mod8 != u3:0 { - (true, BufferCtrl { length: Length:8 - state.read_bits_mod8 as Length }) + if shift_bits != u3:0 { + (true, BufferCtrl { length: Length:8 - shift_bits as Length }) } else { (false, zero!()) }, @@ -552,7 +573,8 @@ pub proc FseProbaFreqDecoder< State { fsm: Fsm::CONSUME_PADDING, read_bits, - read_bits_mod8, + read_bits_mod8: if leave_remainder { u3:0 } else { read_bits_mod8 }, + remainder: if leave_remainder { state.remainder } else { zero!() }, ..state } ) @@ -576,8 +598,9 @@ pub proc FseProbaFreqDecoder< accuracy_log: state.accuracy_log, symbol_count: state.symbol_count, consumed_bytes: checked_cast(read_bits >> 3), + remainder: state.remainder }), - zero!() + zero!(), ) }, _ => { @@ -744,6 +767,7 @@ proc FseProbaFreqDecoderTest { accuracy_log: AccuracyLog:8, symbol_count: SymbolCount:12, consumed_bytes: ConsumedFseBytes:6, + remainder: zero!(), }); // check that the proc consumed the padding by sending request @@ -797,6 +821,7 @@ proc FseProbaFreqDecoderTest { accuracy_log: AccuracyLog:9, symbol_count: SymbolCount:2, consumed_bytes: ConsumedFseBytes:2, + remainder: zero!(), }); for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { @@ -834,6 +859,7 @@ proc FseProbaFreqDecoderTest { accuracy_log: AccuracyLog:9, symbol_count: SymbolCount:2, consumed_bytes: ConsumedFseBytes:2, + remainder: zero!(), }); for ((i, exp_val), tok): ((u32, RamData), token) in enumerate(EXPECTED_RAM_CONTENTS) { diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index bf75392710..c951c3444e 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -1069,7 +1069,7 @@ proc HuffmanFseWeightsDecoder< let tok = send(tok, fld_rsb_start_req_s, fld_rsb_start_req); trace_fmt!("[FSE] Sent refilling shift buffer start request {:#x}", fld_rsb_start_req); - let fld_req = CompLookupDecoderReq {}; + let fld_req = zero!(); let tok = send(tok, fld_req_s, fld_req); trace_fmt!("[FSE] Sent FSE lookup decoding request {:#x}", fld_req); diff --git a/xls/modules/zstd/rle_lookup_dec.x b/xls/modules/zstd/rle_lookup_dec.x index b965d119c2..4251889f20 100644 --- a/xls/modules/zstd/rle_lookup_dec.x +++ b/xls/modules/zstd/rle_lookup_dec.x @@ -93,6 +93,7 @@ pub proc RleLookupDecoder< let tok = send(tok, resp_s, Resp { status: if byte.error { Status::ERROR } else { Status::OK }, accuracy_log: common::FseAccuracyLog:0, + remainder: zero!(), }); } } @@ -163,7 +164,7 @@ proc RleLookupDecoderTest { next(_: ()) { let tok = join(); - let tok = send(tok, req_s, Req {}); + let tok = send(tok, req_s, zero!()); let (tok, buf_req) = recv(tok, buffer_ctrl_r); assert_eq(buf_req, SBCtrl { length: uN[TEST_SB_LENGTH_W]:8 @@ -184,6 +185,7 @@ proc RleLookupDecoderTest { assert_eq(resp, Resp { status: Status::OK, accuracy_log: common::FseAccuracyLog:0, + remainder: zero!(), }); send(tok, terminator, true); } diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x index 855d21b4a0..f51a6886c8 100644 --- a/xls/modules/zstd/sequence_dec.x +++ b/xls/modules/zstd/sequence_dec.x @@ -87,14 +87,19 @@ struct FseLookupCtrlState { mode_valid: bool, cnt: u2, accuracy_logs: u7[3], + remainder: common::Remainder, } struct FseLookupCtrlInternalReq { cnt: u2, - is_rle: bool + is_rle: bool, + remainder: common::Remainder, } -type FseLookupCtrlInternalResp = u7; +struct FseLookupCtrlInternalResp { + accuracy_log: u7, + remainder: common::Remainder, +} pub proc FseLookupCtrlInternal { type Req = FseLookupCtrlInternalReq; @@ -135,12 +140,15 @@ pub proc FseLookupCtrlInternal { trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Sent fse_demux req {:#x}", req.cnt); let (tok, demux_resp) = recv(tok, fse_demux_resp_r); trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Received demux resp {:#x}", demux_resp); - let fld_req = FseLookupDecoderReq { is_rle: req.is_rle }; + let fld_req = FseLookupDecoderReq { is_rle: req.is_rle, remainder: req.remainder }; let tok = send(tok, fld_req_s, fld_req); trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Sent FseLookupDecoder req: {:x}", fld_req); let (tok, fld_resp) = recv(tok, fld_resp_r); trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: Received FseLookupDecoder resp {:#x}", fld_resp); - let tok = send(tok, resp_s, fld_resp.accuracy_log as u7); + let tok = send(tok, resp_s, FseLookupCtrlInternalResp { + accuracy_log: fld_resp.accuracy_log as u7, + remainder: fld_resp.remainder, + }); } } @@ -198,7 +206,8 @@ pub proc FseLookupCtrl { mode: CompressionMode[3]:[req.ll_mode, req.of_mode, req.ml_mode], mode_valid: true, cnt: u2:0, - accuracy_logs: state.accuracy_logs // keep the accuracy logs from the previous block for "repeated" blocks + accuracy_logs: state.accuracy_logs, // keep the accuracy logs from the previous block for "repeated" blocks + ..state } } else { let is_rle = (state.mode[state.cnt] == CompressionMode::RLE); @@ -207,6 +216,8 @@ pub proc FseLookupCtrl { let is_repeated = (state.mode[state.cnt] == CompressionMode::REPEAT); let do_set = is_rle || is_compressed; + trace_fmt!("[FseLookupCtrl] State: {}", state); + match(state.cnt) { u2:0 => trace_fmt!("Handling LL"), u2:1 => trace_fmt!("Handling OF"), @@ -214,23 +225,30 @@ pub proc FseLookupCtrl { _ => trace_fmt!("Impossible case"), }; - let accuracy_log = if do_set { + let resp = if do_set { let tok = send(tok, flci_req_s, FseLookupCtrlInternalReq { cnt: state.cnt, - is_rle: is_rle + is_rle: is_rle, + remainder: state.remainder }); - let (tok, accuracy_log) = recv(tok, flci_resp_r); - accuracy_log + let (tok, resp) = recv(tok, flci_resp_r); + resp } else if is_predefined { - PREDEFINED_ACURACY_LOG[state.cnt] + FseLookupCtrlInternalResp { + accuracy_log: PREDEFINED_ACURACY_LOG[state.cnt], + remainder: state.remainder, + } } else if is_repeated { - state.accuracy_logs[state.cnt] + FseLookupCtrlInternalResp { + accuracy_log: state.accuracy_logs[state.cnt], + remainder: state.remainder, + } } else { - fail!("impossible_case", u7:0) + fail!("impossible_case", zero!()) }; - let accuracy_logs = update(state.accuracy_logs, state.cnt, accuracy_log); - trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: accuracy_log: {:#x}, accuracy_logs: {:#x}", accuracy_log, accuracy_logs); + let accuracy_logs = update(state.accuracy_logs, state.cnt, resp.accuracy_log); + trace_fmt!("[SequenceDecoderCtrl/FseLookupCtrl]: accuracy_log: {:#x}, accuracy_logs: {:#x}", resp.accuracy_log, accuracy_logs); if state.cnt >= u2:2 { let tok = send(tok, resp_s, FseLookupCtrlResp { @@ -240,7 +258,7 @@ pub proc FseLookupCtrl { }); State { accuracy_logs, ..zero!() } } else { - State { accuracy_logs, cnt: state.cnt + u2:1, ..state} + State { accuracy_logs, cnt: state.cnt + u2:1, remainder: resp.remainder, ..state} } } } From 4b7594e6e00c62b189bac7dd0da389b796e3d0b1 Mon Sep 17 00:00:00 2001 From: Wojciech Sipak Date: Fri, 10 Oct 2025 11:03:02 +0200 Subject: [PATCH 068/159] modules: zstd: rtl: ram_1r1w: fix handling of simultaneous read and write --- xls/modules/zstd/rtl/ram_1r1w.v | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xls/modules/zstd/rtl/ram_1r1w.v b/xls/modules/zstd/rtl/ram_1r1w.v index 6d79c6f9fe..ec3da544a6 100644 --- a/xls/modules/zstd/rtl/ram_1r1w.v +++ b/xls/modules/zstd/rtl/ram_1r1w.v @@ -81,7 +81,9 @@ assign wr_data_masked = (mem[wr_addr] & ~wr_exp_mask) | (wr_data & wr_exp_mask); always @(posedge clk) begin if (rd_en) begin rd_data <= rd_data_masked; - end else if (wr_en) begin + end + + if (wr_en) begin mem[wr_addr] <= wr_data_masked; end end From c95e9feb355d364c6e148da9f8c4c2874cee24cd Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Thu, 9 Oct 2025 11:09:50 +0200 Subject: [PATCH 069/159] modules: zstd: huffman ctrl: omit building new codes when decoding treeless literals --- xls/modules/zstd/huffman_ctrl.x | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/xls/modules/zstd/huffman_ctrl.x b/xls/modules/zstd/huffman_ctrl.x index 52758ec6b0..d259a7e210 100644 --- a/xls/modules/zstd/huffman_ctrl.x +++ b/xls/modules/zstd/huffman_ctrl.x @@ -357,9 +357,11 @@ pub proc HuffmanControlAndSequence { (tok, zero!(), ctrl.len) }; - let tok = send(tok, prescan_start_s, true); - let tok = send(tok, code_builder_start_s, true); - trace_fmt!("[HuffmanControlAndSequence] Sent START to prescan and code builder"); + if ctrl.new_config { + let tok = send(tok, prescan_start_s, true); + let tok = send(tok, code_builder_start_s, true); + trace_fmt!("[HuffmanControlAndSequence] Sent START to prescan and code builder"); + } else {}; let tok = send( tok, multi_stream_config_s, MultiStreamConfig { @@ -522,14 +524,6 @@ proc HuffmanControlAndSequence_test { }; let tok = send(tok, ctrl_s, ctrl); - let (tok, prescan_start) = recv(tok, prescan_start_r); - trace_fmt!("[TEST] Received prescan START"); - assert_eq(true, prescan_start); - - let (tok, code_builder_start) = recv(tok, code_builder_start_r); - trace_fmt!("[TEST] Received code builder START"); - assert_eq(true, code_builder_start); - let (tok, axi_reader_ctrl) = recv(tok, axi_reader_ctrl_r); trace_fmt!("[TEST] Received AXI reader CTRL"); assert_eq(AxiReaderCtrl {base_addr: ctrl.base_addr, len: ctrl.len}, axi_reader_ctrl); @@ -628,14 +622,6 @@ proc HuffmanControlAndSequence_test { uN[TEST_AXI_ADDR_W]:0x3, ]; - let (tok, prescan_start) = recv(tok, prescan_start_r); - trace_fmt!("[TEST] Received prescan START"); - assert_eq(true, prescan_start); - - let (tok, code_builder_start) = recv(tok, code_builder_start_r); - trace_fmt!("[TEST] Received code builder START"); - assert_eq(true, code_builder_start); - for (i, tok) in u32:0..u32:4 { trace_fmt!("[TEST] Stream #{}", i); From 0770058b855b98f449e76c29b8a88ac03ed1cd7f Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Thu, 16 Oct 2025 13:34:48 +0200 Subject: [PATCH 070/159] modules: zstd: cocotb: print correct packet values less often --- xls/modules/zstd/zstd_dec_cocotb_common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index d750d55d5c..779736ea12 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -676,7 +676,9 @@ async def check_output(expected_packet_count, memory, reference_memory, output_m hex(mem_contents), hex(exp_mem_contents), ) - print(f'[cocotb] Got correct packet (addr: {hex(current_addr)}, data: {hex(mem_contents)}, clk: {get_clock_time(clock)})', file=sys.stderr) + + if read_op % 0x10000 == 0: + print(f'[cocotb] Got correct packet (addr: {hex(current_addr)}, data: {hex(mem_contents)}, clk: {get_clock_time(clock)})', file=sys.stderr) decode_last_packet = get_clock_time(clock) return (decode_start, decode_first_packet, decode_last_packet) From 9bbcc7b24cd2e61fd4c11b721f18c71b30528d31 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Mon, 13 Oct 2025 11:30:43 +0200 Subject: [PATCH 071/159] modules: zstd: huffman weights dec: remove duplicate call --- xls/modules/zstd/huffman_weights_dec.x | 4 ---- 1 file changed, 4 deletions(-) diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index c951c3444e..0995ab99d7 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -206,10 +206,6 @@ proc HuffmanRawWeightsDecoder< trace_fmt!("[RAW] Weights: {:#x}", weights); } else {}; - if do_recv_data && mem_rd_resp_valid { - trace_fmt!("[RAW] Weights: {:#x}", weights); - } else {}; - let (buffer, buffer_len) = if do_recv_data && mem_rd_resp_valid { ( buffer | ((weights as uN[BUFF_LEN] << (BUFF_LEN - AXI_DATA_W - buffer_len as u32))), From a4649723f35e734d99b9ef87416f7a8612369e59 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Mon, 13 Oct 2025 10:54:47 +0200 Subject: [PATCH 072/159] modules: zstd: huffman weights dec: add more test cases for raw weights --- xls/modules/zstd/huffman_weights_dec.x | 190 ++++++++++++++++--------- 1 file changed, 121 insertions(+), 69 deletions(-) diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index 0995ab99d7..9908ad7c23 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -1641,31 +1641,9 @@ const TEST_TMP2_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_TMP2_RAM_WORD_PART // RAW weights const TEST_RAW_INPUT_ADDR = uN[TEST_AXI_ADDR_W]:0x40; -const TEST_RAW_TREE_DESCRIPTION_SIZE = u32:15; // (header + weight stream) - -const TEST_RAW_DATA = u8[19]:[ - u8:155, // header - // weights stream - u8:0x75, u8:0x66, u8:0x66, u8:0x66, - u8:0x66, u8:0x66, u8:0x55, u8:0x55, - u8:0x44, u8:0x44, u8:0x33, u8:0x31, - u8:0x11, u8:0x00, - // dummy leftover - u8:0xDE, u8:0xAD, u8:0xBE, u8:0xEF, -]; - const_assert!(TEST_WEIGHTS_RAM_DATA_W == u32:32); const TEST_RAW_RAM_PACKET_COUNT = TEST_WEIGHTS_RAM_SIZE / TEST_WEIGHTS_RAM_PARTITION_SIZE; -const TEST_RAW_EXPECTED_WEIGHT_RAM = uN[TEST_WEIGHTS_RAM_DATA_W][TEST_RAW_RAM_PACKET_COUNT]:[ - uN[TEST_WEIGHTS_RAM_DATA_W]:0x75666666, - uN[TEST_WEIGHTS_RAM_DATA_W]:0x66665555, - uN[TEST_WEIGHTS_RAM_DATA_W]:0x44443331, - uN[TEST_WEIGHTS_RAM_DATA_W]:0x11001000, - // ^- last weight - uN[TEST_WEIGHTS_RAM_DATA_W]:0x0, ... -]; - // FSE weights const TEST_FSE_INPUT_ADDR = uN[TEST_AXI_ADDR_W]:0x200; @@ -2136,66 +2114,140 @@ proc HuffmanWeightsDecoder_test { let tok = join(); - // RAW weights + // RAW tests + const TEST_MAX_WEIGHTS_STREAM = u32:21; + const TEST_NUM_OF_RAW_CASES = u32:3; + let raw_tests: (u32, u8[TEST_MAX_WEIGHTS_STREAM], uN[TEST_WEIGHTS_RAM_DATA_W][TEST_RAW_RAM_PACKET_COUNT])[TEST_NUM_OF_RAW_CASES] = [ + ( + // normal case + u32:15, // (header + weight stream) + u8[TEST_MAX_WEIGHTS_STREAM]:[ + u8:155, // header + // weights stream + u8:0x75, u8:0x66, u8:0x66, u8:0x66, + u8:0x66, u8:0x66, u8:0x55, u8:0x55, + u8:0x44, u8:0x44, u8:0x33, u8:0x31, + u8:0x11, u8:0x00, + // dummy leftover + u8:0x00, u8:0x00, + u8:0xDE, u8:0xAD, u8:0xBE, u8:0xEF, ... + ], + uN[TEST_WEIGHTS_RAM_DATA_W][TEST_RAW_RAM_PACKET_COUNT]:[ + uN[TEST_WEIGHTS_RAM_DATA_W]:0x75666666, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x66665555, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x44443331, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x11001000, + // ^- last weight + uN[TEST_WEIGHTS_RAM_DATA_W]:0x0, ... + ] + ), + ( + // case with last weight at modulo index 0 + u32:17, // (header + weight stream) + u8[TEST_MAX_WEIGHTS_STREAM]:[ + u8:159, // header + // weights stream + u8:0x75, u8:0x55, u8:0x66, u8:0x66, + u8:0x66, u8:0x66, u8:0x65, u8:0x55, + u8:0x55, u8:0x43, u8:0x33, u8:0x21, + u8:0x21, u8:0x11, u8:0x11, u8:0x01, + // dummy leftover + u8:0xDE, u8:0xAD, u8:0xBE, u8:0xEF, ... + ], + uN[TEST_WEIGHTS_RAM_DATA_W][TEST_RAW_RAM_PACKET_COUNT]:[ + uN[TEST_WEIGHTS_RAM_DATA_W]:0x75556666, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x66666555, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x55433321, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x21111101, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x10000000, + // ^- last weight + uN[TEST_WEIGHTS_RAM_DATA_W]:0x0, ... + ] + ), + ( + // case with last weight at modulo index 8 (middle position of the received weights packet) + u32:13, // (header + weight stream) + u8[TEST_MAX_WEIGHTS_STREAM]:[ + u8:0x97, // header + // weights stream + u8:0x86, u8:0x66, u8:0x66, u8:0x66, + u8:0x66, u8:0x55, u8:0x55, u8:0x43, + u8:0x43, u8:0x11, u8:0x21, u8:0x11, + // dummy leftover + u8:0xDE, u8:0xAD, u8:0xBE, u8:0xEF, ... + ], + uN[TEST_WEIGHTS_RAM_DATA_W][TEST_RAW_RAM_PACKET_COUNT]:[ + uN[TEST_WEIGHTS_RAM_DATA_W]:0x86666666, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x66555543, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x43112111, + uN[TEST_WEIGHTS_RAM_DATA_W]:0x10000000, + // ^- last weight + uN[TEST_WEIGHTS_RAM_DATA_W]:0x0, ... + ] + ) + ]; - // Fill input RAM - for (i, tok) in u32:0..(array_size(TEST_RAW_DATA) + TEST_DATA_PER_RAM_WRITE - u32:1) / TEST_DATA_PER_RAM_WRITE { - let ram_data = for (j, ram_data) in u32:0..TEST_DATA_PER_RAM_WRITE { - let data_idx = i * TEST_DATA_PER_RAM_WRITE + j; - if (data_idx < array_size(TEST_RAW_DATA)) { - ram_data | ((TEST_RAW_DATA[data_idx] as uN[TEST_RAM_DATA_W]) << (u32:8 * j)) - } else { - ram_data - } - }(uN[TEST_RAM_DATA_W]:0); + let tok = for ((_, (data_length, weights_stream, expected)), tok): ((u32, (u32, u8[TEST_MAX_WEIGHTS_STREAM], uN[TEST_WEIGHTS_RAM_DATA_W][TEST_RAW_RAM_PACKET_COUNT])), token) in enumerate(raw_tests) { + // Fill input RAM + for (i, tok) in u32:0..(array_size(weights_stream) + TEST_DATA_PER_RAM_WRITE - u32:1) / TEST_DATA_PER_RAM_WRITE { + let ram_data = for (j, ram_data) in u32:0..TEST_DATA_PER_RAM_WRITE { + let data_idx = i * TEST_DATA_PER_RAM_WRITE + j; + if (data_idx < array_size(weights_stream)) { + ram_data | ((weights_stream[data_idx] as uN[TEST_RAM_DATA_W]) << (u32:8 * j)) + } else { + ram_data + } + }(uN[TEST_RAM_DATA_W]:0); - let input_ram_wr_req = InputBufferRamWrReq { - addr: (TEST_RAW_INPUT_ADDR / u32:8) + i as uN[TEST_RAM_ADDR_W], - data: ram_data, - mask: !uN[TEST_RAM_NUM_PARTITIONS]:0, - }; + let input_ram_wr_req = InputBufferRamWrReq { + addr: (TEST_RAW_INPUT_ADDR / u32:8) + i as uN[TEST_RAM_ADDR_W], + data: ram_data, + mask: !uN[TEST_RAM_NUM_PARTITIONS]:0, + }; + + let tok = unroll_for! (i, tok) in u32:0..TEST_RAM_N { + let tok = send(tok, input_ram_wr_req_s[i], input_ram_wr_req); + let (tok, _) = recv(tok, input_ram_wr_resp_r[i]); + tok + }(tok); + + trace_fmt!("[TEST] Sent RAM write request to input RAMs {:#x}", input_ram_wr_req); - let tok = unroll_for! (i, tok) in u32:0..TEST_RAM_N { - let tok = send(tok, input_ram_wr_req_s[i], input_ram_wr_req); - let (tok, _) = recv(tok, input_ram_wr_resp_r[i]); tok }(tok); - trace_fmt!("[TEST] Sent RAM write request to input RAMs {:#x}", input_ram_wr_req); + // Send decoding request + let req = Req { + addr: TEST_RAW_INPUT_ADDR, + }; + let tok = send(tok, req_s, req); + trace_fmt!("[TEST] Sent request {:#x}", req); - tok - }(tok); + // Receive response + let (tok, resp) = recv(tok, resp_r); + trace_fmt!("[TEST] Received respose {:#x}", resp); + assert_eq(HuffmanWeightsDecoderStatus::OKAY, resp.status); + assert_eq(data_length as uN[TEST_AXI_ADDR_W], resp.tree_description_size); - // Send decoding request - let req = Req { - addr: TEST_RAW_INPUT_ADDR, - }; - let tok = send(tok, req_s, req); - trace_fmt!("[TEST] Sent request {:#x}", req); - - // Receive response - let (tok, resp) = recv(tok, resp_r); - trace_fmt!("[TEST] Received respose {:#x}", resp); - assert_eq(HuffmanWeightsDecoderStatus::OKAY, resp.status); - assert_eq(TEST_RAW_TREE_DESCRIPTION_SIZE as uN[TEST_AXI_ADDR_W], resp.tree_description_size); - - // Check output RAM - let tok = for (i, tok) in u32:0..array_size(TEST_RAW_EXPECTED_WEIGHT_RAM) { - let expected_value = TEST_RAW_EXPECTED_WEIGHT_RAM[i]; - let weights_ram_rd_req = WeightsRamRdReq { - addr: i as uN[TEST_WEIGHTS_RAM_ADDR_W], - mask: !uN[TEST_WEIGHTS_RAM_NUM_PARTITIONS]:0, - }; - let tok = send(tok, weights_ram_rd_req_s, weights_ram_rd_req); - let (tok, weights_ram_rd_resp) = recv(tok, weights_ram_rd_resp_r); - trace_fmt!("[TEST] Weights RAM content - addr: {:#x} data: expected {:#x}, got {:#x}", i, expected_value, weights_ram_rd_resp.data); + // Check output RAM + let tok = for (i, tok) in u32:0..array_size(expected) { + let expected_value = expected[i]; + let weights_ram_rd_req = WeightsRamRdReq { + addr: i as uN[TEST_WEIGHTS_RAM_ADDR_W], + mask: !uN[TEST_WEIGHTS_RAM_NUM_PARTITIONS]:0, + }; + let tok = send(tok, weights_ram_rd_req_s, weights_ram_rd_req); + let (tok, weights_ram_rd_resp) = recv(tok, weights_ram_rd_resp_r); + trace_fmt!("[TEST] Weights RAM content - addr: {:#x} data: expected {:#x}, got {:#x}", i, expected_value, weights_ram_rd_resp.data); - assert_eq(expected_value, weights_ram_rd_resp.data); + assert_eq(expected_value, weights_ram_rd_resp.data); + + tok + }(tok); tok }(tok); - // FSE-encoded weights unroll_for! (i, tok) in u32:0..array_size(TESTCASES_FSE) { let (TEST_FSE_DATA, TEST_FSE_WEIGHTS) = TESTCASES_FSE[i]; From ccd52c5de89bf237ebf32c7c2a3a960cd73c65bb Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Mon, 13 Oct 2025 14:39:42 +0200 Subject: [PATCH 073/159] modules: zstd: huffman weights dec: send last raw weight in the next iteration if required --- xls/modules/zstd/huffman_weights_dec.x | 47 ++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/xls/modules/zstd/huffman_weights_dec.x b/xls/modules/zstd/huffman_weights_dec.x index 9908ad7c23..017f5f85ac 100644 --- a/xls/modules/zstd/huffman_weights_dec.x +++ b/xls/modules/zstd/huffman_weights_dec.x @@ -28,6 +28,7 @@ import xls.modules.zstd.math; const HUFFMAN_FSE_MAX_ACCURACY_LOG = u32:9; const HUFFMAN_FSE_ACCURACY_W = std::clog2(HUFFMAN_FSE_MAX_ACCURACY_LOG + u32:1); +const BITS_PER_RAW_WEIGHT = u32:4; struct HuffmanRawWeightsDecoderReq { addr: uN[AXI_ADDR_W], @@ -43,11 +44,12 @@ struct HuffmanRawWeightsDecoderResp { status: HuffmanRawWeightsDecoderStatus, } -enum HuffmanRawWeightsDecoderFSM : u2 { +enum HuffmanRawWeightsDecoderFSM : u3 { IDLE = 0, DECODING = 1, - FILL_ZERO = 2, - RESP = 3, + SENDING_LAST_WEIGHT = 2, + FILL_ZERO = 3, + RESP = 4, } struct HuffmanRawWeightsDecoderState< @@ -63,6 +65,7 @@ struct HuffmanRawWeightsDecoderState< buffer: uN[BUFF_LEN], buffer_len: uN[BUFF_LEN_LOG2], ram_wr_resp_to_handle: u4, + pending_last_weight: bool, sum: u32, // The sum of 2^(weight-1) from HTD } @@ -192,21 +195,34 @@ proc HuffmanRawWeightsDecoder< _ => fail!("unsupported_axi_data_width", uN[AXI_DATA_W]:0), } as u4[MAX_WEIGHTS_IN_PACKET]; - let weights = if (state.req.n_symbols > u8:0 && (mem_rd_resp_valid && mem_rd_resp.last)) { + let received_last_data = mem_rd_resp_valid && mem_rd_resp.last; + let last_weight_index = state.req.n_symbols % MAX_WEIGHTS_IN_PACKET as u8; + let pending_last_weight = (state.req.n_symbols % (WEIGHTS_RAM_DATA_W / BITS_PER_RAW_WEIGHT) as u8) == u8:0 && received_last_data; + + let weights = if (state.req.n_symbols > u8:0 && received_last_data && last_weight_index > u8:0) || state.pending_last_weight { trace_fmt!("[RAW] The sum of weight's powers of 2's: {}", state.sum); trace_fmt!("[RAW] The last weight: {}", last_weight); trace_fmt!("[RAW] MAX_WEIGHTS: {}", MAX_WEIGHTS_IN_PACKET); - trace_fmt!("[RAW] Injected {:#x} into weights[{}]", last_weight, (state.req.n_symbols % MAX_WEIGHTS_IN_PACKET as u8)); - update(weights, (state.req.n_symbols % MAX_WEIGHTS_IN_PACKET as u8), last_weight) + trace_fmt!("[RAW] Injected {:#x} into weights[{}]", last_weight, last_weight_index); + update(weights, last_weight_index, last_weight) } else { weights } as uN[AXI_DATA_W]; - if do_recv_data && mem_rd_resp_valid { + if do_recv_data && mem_rd_resp_valid || state.pending_last_weight { trace_fmt!("[RAW] Weights: {:#x}", weights); } else {}; - let (buffer, buffer_len) = if do_recv_data && mem_rd_resp_valid { + let state = if (pending_last_weight) { + State { + pending_last_weight, + ..state + } + } else { + state + }; + + let (buffer, buffer_len) = if (do_recv_data && mem_rd_resp_valid) || state.fsm == FSM::SENDING_LAST_WEIGHT { ( buffer | ((weights as uN[BUFF_LEN] << (BUFF_LEN - AXI_DATA_W - buffer_len as u32))), buffer_len + (AXI_DATA_W as uN[BUFF_LEN_LOG2]), @@ -218,7 +234,7 @@ proc HuffmanRawWeightsDecoder< ) }; // Send to RAM - let do_send_data = state.fsm == FSM::DECODING && buffer_len >= (WEIGHTS_RAM_DATA_W as uN[BUFF_LEN_LOG2]); + let do_send_data = (state.fsm == FSM::DECODING || state.fsm == FSM::SENDING_LAST_WEIGHT) && buffer_len >= (WEIGHTS_RAM_DATA_W as uN[BUFF_LEN_LOG2]); let weights_ram_wr_req = WeightsRamWrReq { addr: state.ram_addr, data: buffer[-(WEIGHTS_RAM_DATA_W as s32):] as uN[WEIGHTS_RAM_DATA_W], @@ -226,6 +242,7 @@ proc HuffmanRawWeightsDecoder< }; let tok = send_if(tok, weights_ram_wr_req_s, do_send_data, weights_ram_wr_req); if do_send_data { + trace_fmt!("[RAW] Buffer: {}", buffer); trace_fmt!("[RAW] Buffer length: {}", buffer_len); trace_fmt!("[RAW] Sent RAM write request {:#x}", weights_ram_wr_req); } else {}; @@ -298,12 +315,22 @@ proc HuffmanRawWeightsDecoder< } } else { State { - fsm: FSM::FILL_ZERO, + fsm: if state.pending_last_weight { FSM::SENDING_LAST_WEIGHT } else { FSM::FILL_ZERO }, ram_addr: state.ram_addr + (data_decoded / WEIGHTS_RAM_DATA_W as uN[AXI_ADDR_W]) as uN[WEIGHTS_RAM_ADDR_W], + buffer: buffer, + buffer_len: buffer_len, ..state } } }, + FSM::SENDING_LAST_WEIGHT => { + State { + fsm: FSM::FILL_ZERO, + ram_addr: state.ram_addr + uN[WEIGHTS_RAM_ADDR_W]:1, + pending_last_weight: false, + ..state + } + }, FSM::FILL_ZERO => { if state.ram_addr < !uN[WEIGHTS_RAM_ADDR_W]:0 { trace_fmt!("[RAW] Filling with zeros {} / {}", state.ram_addr + uN[WEIGHTS_RAM_ADDR_W]:1, !uN[WEIGHTS_RAM_ADDR_W]:0); From b42afcec5c1e08f2f5f4f0af74a360d0b51aee9f Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Fri, 17 Oct 2025 11:37:42 +0200 Subject: [PATCH 074/159] modules: zstd: performance: add generating a throughput benchmark --- xls/modules/zstd/BUILD | 8 +++++ xls/modules/zstd/perf_report.py | 30 ++++++++++++++++++ xls/modules/zstd/zstd_dec_cocotb_common.py | 37 ++++++++++++++-------- xls/modules/zstd/zstd_dec_detailed_test.py | 24 ++++++++++++-- 4 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 xls/modules/zstd/perf_report.py diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index c9278be659..c2c393769a 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1765,6 +1765,13 @@ benchmark_synth( # target_die_utilization_percentage = "1", #) +py_library( + name = "perf_report", + srcs = ["perf_report.py"], + tags = ["manual"], + visibility = ["//xls:xls_users"], +) + filegroup( name = "zstd_dec_xx_fse_default", srcs = [ @@ -1791,6 +1798,7 @@ py_library( ":axi_crossbar_wrapper.v", ":patch_zstd_dec", ":zstd_dec_xx_fse_default", + ":perf_report", "//xls/modules/zstd:zstd_test_debugger", "//xls/modules/zstd/rtl:ram_1r1w.v", "//xls/modules/zstd/rtl:xls_fifo_wrapper.sv", diff --git a/xls/modules/zstd/perf_report.py b/xls/modules/zstd/perf_report.py new file mode 100644 index 0000000000..60df3c2162 --- /dev/null +++ b/xls/modules/zstd/perf_report.py @@ -0,0 +1,30 @@ +# Copyright 2025 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pathlib +import os + +_report_file = pathlib.Path("report_perf.log") +_report_file.parent.mkdir(exist_ok=True) + +def create_report_file(): + with _report_file.open('w') as f: + f.write("| test name | total cycles | latency [cycles] | total bytes decoded | throughput [GiB/s] |\n") + f.write("| --------- | ------------ | ---------------- | ------------------- | ------------------ |\n") + +create_report_file() + +def report_test_result(test_name, duration, latency, total_decoded_bytes, gigabytes_per_second): + with _report_file.open("a") as f: + f.write(f"| {test_name} | {round(duration)} | {round(latency)} | {round(total_decoded_bytes)} | {gigabytes_per_second:.3f} |\n") diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index 779736ea12..d18f6e8668 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -17,6 +17,7 @@ import enum import tempfile import sys +import os import cocotb from cocotb import triggers @@ -58,6 +59,7 @@ from xls.modules.zstd.cocotb import data_generator from xls.modules.zstd.cocotb.memory import AxiRamFromFile from xls.modules.zstd.cocotb import xlsstruct +from xls.modules.zstd.perf_report import report_test_result AXI_DATA_W = 64 AXI_DATA_W_BYTES = AXI_DATA_W // 8 @@ -65,6 +67,8 @@ NOTIFY_CHANNEL = "notify" RESET_CHANNEL = "reset" +CLOCK_PERIOD_PS = 750 + # Override default widths of AXI response signals signal_widths = {"bresp": 3} axi_channels.AxiBBus._signal_widths = signal_widths @@ -723,16 +727,23 @@ async def test_decoder(dut, axi_buses, cpu, clock, encoded_file): decode_end = get_clock_time(clock) latency = decode_first_packet - decode_start - throughput_repacketizer = expected_packet_count / (decode_end - decode_first_packet) - throughput_bytes = throughput_repacketizer * AXI_DATA_W_BYTES - print(f"Decoding latency: {latency} cycles") - print( - f"Decoding throughput: {throughput_bytes}B/cycle ({throughput_repacketizer} packets/cycle)" - ) + + duration = decode_end - decode_start + total_decoded_bytes = expected_packet_count * AXI_DATA_W_BYTES + bytes_per_clock = total_decoded_bytes / duration + CLOCK_PERIOD_PS = 750 + CLOCKS_PER_SECOND = 1e12 / CLOCK_PERIOD_PS + BYTES_IN_GIGABYTE = 1024 * 1024 * 1024 + gigabytes_per_second = bytes_per_clock * CLOCKS_PER_SECOND / BYTES_IN_GIGABYTE + + print(f"Duration: {duration} cycles") + print(f"Latency (clocks till first data): {latency} cycles") + print(f"Total decoded bytes: {total_decoded_bytes} bytes") + print(f"Decoding throughput: {gigabytes_per_second:.04f} GB/second") await ClockCycles(dut.clk, 20) - return (latency, throughput_bytes, throughput_repacketizer) + return (duration, latency, total_decoded_bytes, gigabytes_per_second) async def wait_for_status(cpu, timeout=100): status = None @@ -818,13 +829,12 @@ async def randomized_testing_routine( print( f"\nusing randomly generated (seed={seed}) input file for decoder: {input_file.name}\n" ) - measurements.append(await test_decoder(dut, axi_buses, cpu, clock, input_file)) + test_measurements = await test_decoder(dut, axi_buses, cpu, clock, input_file) + measurements.append(test_measurements) print("Decoding {} ZSTD frames done".format(block_type.name)) for measurement in measurements: - print( - f"Frame #{frame_id}: latency: {measurement[0]} cycles; throughput: {measurement[1]} B/cycle ({measurement[2]} packets/cycle)" - ) + report_test_result(f"{block_type.name}{frame_id}", measurement[0], measurement[1], measurement[2], measurement[3]) frame_id += 1 @@ -857,10 +867,9 @@ async def pregenerated_testing_routine( with open(pregenerated_path, 'rb') as input_file: measurement = await test_decoder(dut, axi_buses, cpu, clock, input_file) + test_name = os.path.basename(pregenerated_path) + report_test_result(f"{test_name}", measurement[0], measurement[1], measurement[2], measurement[3]) - print( - f"Frame #0: latency: {measurement[0]} cycles; throughput: {measurement[1]} B/cycle ({measurement[2]} packets/cycle)" - ) async def test_expected_status( dut, diff --git a/xls/modules/zstd/zstd_dec_detailed_test.py b/xls/modules/zstd/zstd_dec_detailed_test.py index f81c81d35e..5cbcc562b2 100644 --- a/xls/modules/zstd/zstd_dec_detailed_test.py +++ b/xls/modules/zstd/zstd_dec_detailed_test.py @@ -1,5 +1,6 @@ import math import cocotb +import os from pathlib import Path from enum import IntEnum @@ -18,9 +19,11 @@ configure_decoder, start_decoder, reset_dut, run_test, prepare_test_environment, check_ram_contents, reverse_expected_huffman_codes, fields_as_array, FseTableRecord, - print_fse_ram_contents, check_status, check_output + print_fse_ram_contents, check_status, check_output, get_clock_time, + CLOCK_PERIOD_PS ) from xls.modules.zstd.cocotb.memory import AxiRamFromFile +from xls.modules.zstd.perf_report import report_test_result def check_if_ram_contents_are_valid(mem, expected, name="") -> bool: for i, value in enumerate(expected): @@ -675,5 +678,22 @@ async def detailed_testing_routine(dut, if block_header.last: break + decode_times = await check_output_thread + (decode_start, decode_first_packet, decode_last_packet) = decode_times await check_status_thread - await check_output_thread + + decode_end = get_clock_time(clock) + latency = decode_first_packet - decode_start + duration = decode_end - decode_start + total_decoded_bytes = expected_packet_count * AXI_DATA_W_BYTES + bytes_per_clock = total_decoded_bytes / duration + BYTES_IN_GIGABYTE = 1024 * 1024 * 1024 + CLOCKS_PER_SECOND = 1e12 / CLOCK_PERIOD_PS + gigabytes_per_second = bytes_per_clock * CLOCKS_PER_SECOND / BYTES_IN_GIGABYTE + print(f"Duration: {duration} cycles") + print(f"Latency (clocks till first data): {latency} cycles") + print(f"Total decoded bytes: {total_decoded_bytes} bytes") + print(f"Decoding throughput: {gigabytes_per_second:.04f} GB/s") + + test_name = os.path.basename(pregenerated_path) + report_test_result(test_name, duration, latency, total_decoded_bytes, gigabytes_per_second) From e69c28b2962eff249af352baea2686307ddf7211 Mon Sep 17 00:00:00 2001 From: Mateusz Gancarz Date: Fri, 31 Oct 2025 11:54:16 +0100 Subject: [PATCH 075/159] modules: zstd: sequence executor: fix clearing current history buffer length --- xls/modules/zstd/sequence_executor.x | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index ddcc9ae069..c2409c3963 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -371,7 +371,7 @@ pub proc SequenceExecutor(state.hyp_ptr, packet); let new_repeat_req = packet.length == CopyOrMatchLength:0; let hb_add = packet.length as HistoryBufferLength; - let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL as uN[RAM_ADDR_WIDTH + RAM_NUM_CLOG2]); + let new_hb_len = std::min(state.hb_len + hb_add, parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB) as HistoryBufferLength); ( write_reqs, ZERO_READ_REQS, RamReadStart:0, RamReadLen:0, @@ -436,7 +436,7 @@ pub proc SequenceExecutor(state.hyp_ptr, packet); let hb_add = packet.length as HistoryBufferLength; - let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL as uN[RAM_ADDR_WIDTH + RAM_NUM_CLOG2]); + let new_hb_len = std::min(state.hb_len + hb_add, parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB) as HistoryBufferLength); ( write_reqs, ZERO_READ_REQS, RamReadStart:0, RamReadLen:0, From 9d67da9f4cf42140de31c53aba16029f79606234 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 17 Dec 2025 19:10:02 +0100 Subject: [PATCH 076/159] modules: zstd: remove outdated/commented code Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 110 +- xls/modules/zstd/command_constructor.x | 8 +- xls/modules/zstd/common.x | 3 - xls/modules/zstd/comp_block_dec.x | 4 - xls/modules/zstd/data/comp_frame.zst | Bin 51 -> 0 bytes xls/modules/zstd/data/comp_frame_fse_comp.zst | Bin 66 -> 0 bytes .../zstd/data/comp_frame_fse_repeated.zst | Bin 92 -> 0 bytes xls/modules/zstd/data/comp_frame_huffman.zst | Bin 93 -> 0 bytes .../zstd/data/comp_frame_huffman_fse.zst | Bin 64 -> 0 bytes ...erals_predefined_sequences_seed_107958.log | 30 - ...erals_predefined_sequences_seed_107958.zst | Bin 61 -> 0 bytes ...erals_predefined_sequences_seed_204626.log | 25 - ...erals_predefined_sequences_seed_204626.zst | Bin 64 -> 0 bytes ...erals_predefined_sequences_seed_210872.log | 28 - ...erals_predefined_sequences_seed_210872.zst | Bin 80 -> 0 bytes ...erals_predefined_sequences_seed_299289.log | 39 - ...erals_predefined_sequences_seed_299289.zst | Bin 60 -> 0 bytes ...erals_predefined_sequences_seed_319146.log | 26 - ...erals_predefined_sequences_seed_319146.zst | Bin 125 -> 0 bytes ...erals_predefined_sequences_seed_331938.log | 25 - ...erals_predefined_sequences_seed_331938.zst | Bin 93 -> 0 bytes ...erals_predefined_sequences_seed_333824.log | 27 - ...erals_predefined_sequences_seed_333824.zst | Bin 56 -> 0 bytes .../data/pregenerated_compressed_minimal.zst | Bin 22 -> 0 bytes .../data/pregenerated_compressed_random_1.log | 189 --- .../data/pregenerated_compressed_random_1.zst | Bin 505 -> 0 bytes .../data/pregenerated_compressed_random_2.log | 70 - .../data/pregenerated_compressed_random_2.zst | Bin 120 -> 0 bytes .../data/pregenerated_compressed_raw_1.log | 347 ----- .../data/pregenerated_compressed_raw_1.zst | Bin 975 -> 0 bytes .../data/pregenerated_compressed_raw_2.log | 100 -- .../data/pregenerated_compressed_raw_2.zst | Bin 193 -> 0 bytes .../data/pregenerated_compressed_rle_1.log | 179 --- .../data/pregenerated_compressed_rle_1.zst | Bin 281 -> 0 bytes .../data/pregenerated_compressed_rle_2.log | 103 -- .../data/pregenerated_compressed_rle_2.zst | Bin 61 -> 0 bytes .../zstd/data/pregenerated_uncompressed.zst | Bin 18 -> 0 bytes ...erals_compressed_sequences_seed_903062.log | 23 - ...erals_compressed_sequences_seed_903062.zst | Bin 37 -> 0 bytes ...erals_predefined_sequences_seed_422473.log | 25 - ...erals_predefined_sequences_seed_422473.zst | Bin 40 -> 0 bytes ...erals_predefined_sequences_seed_436965.log | 23 - ...erals_predefined_sequences_seed_436965.zst | Bin 36 -> 0 bytes ...erals_predefined_sequences_seed_462302.log | 21 - ...erals_predefined_sequences_seed_462302.zst | Bin 31 -> 0 bytes ...raw_literals_rle_sequences_seed_700216.log | 20 - ...raw_literals_rle_sequences_seed_700216.zst | Bin 46 -> 0 bytes ...erals_compressed_sequences_seed_701326.log | 87 -- ...erals_compressed_sequences_seed_701326.zst | Bin 75 -> 0 bytes ...erals_predefined_sequences_seed_406229.log | 37 - ...erals_predefined_sequences_seed_406229.zst | Bin 42 -> 0 bytes ...erals_predefined_sequences_seed_411034.log | 28 - ...erals_predefined_sequences_seed_411034.zst | Bin 35 -> 0 bytes ...erals_predefined_sequences_seed_413015.log | 53 - ...erals_predefined_sequences_seed_413015.zst | Bin 64 -> 0 bytes ...erals_predefined_sequences_seed_436165.log | 34 - ...erals_predefined_sequences_seed_436165.zst | Bin 38 -> 0 bytes ...erals_predefined_sequences_seed_464057.log | 21 - ...erals_predefined_sequences_seed_464057.zst | Bin 27 -> 0 bytes ...erals_predefined_sequences_seed_466803.log | 24 - ...erals_predefined_sequences_seed_466803.zst | Bin 32 -> 0 bytes .../rle_literals_rle_sequences_seed_2.log | 42 - .../rle_literals_rle_sequences_seed_2.zst | Bin 121 -> 0 bytes ...erals_predefined_sequences_seed_408158.log | 111 -- ...erals_predefined_sequences_seed_408158.zst | Bin 93 -> 0 bytes ...erals_predefined_sequences_seed_499212.log | 44 - ...erals_predefined_sequences_seed_499212.zst | Bin 60 -> 0 bytes ...erals_compressed_sequences_seed_400077.log | 80 -- ...erals_compressed_sequences_seed_400077.zst | Bin 142 -> 0 bytes ...d_rle_compressed_sequences_seed_400025.log | 46 - ...d_rle_compressed_sequences_seed_400025.zst | Bin 140 -> 0 bytes ...d_rle_compressed_sequences_seed_400061.log | 81 -- ...d_rle_compressed_sequences_seed_400061.zst | Bin 244 -> 0 bytes ...man_literals_rle_sequences_seed_403927.log | 53 - ...man_literals_rle_sequences_seed_403927.zst | Bin 137 -> 0 bytes xls/modules/zstd/fse_dec.x | 780 ----------- xls/modules/zstd/fse_lookup_dec.x | 2 - xls/modules/zstd/fse_proba_freq_dec.x | 4 - xls/modules/zstd/fse_table_creator.x | 1 - xls/modules/zstd/huffman_code_builder.x | 203 +-- xls/modules/zstd/huffman_common.x | 17 - xls/modules/zstd/huffman_literals_dec.x | 124 +- xls/modules/zstd/huffman_prescan.x | 162 ++- xls/modules/zstd/literals_block_header_dec.x | 1 - xls/modules/zstd/literals_buffer.x | 5 - xls/modules/zstd/literals_decoder.x | 295 +--- xls/modules/zstd/math.x | 11 +- xls/modules/zstd/memory/BUILD | 5 - xls/modules/zstd/memory/axi_ram_reader.x | 10 +- xls/modules/zstd/memory/mem_writer.x | 7 +- xls/modules/zstd/ram_demux.x | 4 +- xls/modules/zstd/refilling_shift_buffer.x | 6 +- xls/modules/zstd/rtl/cocotb_public_vars.vlt | 2 +- xls/modules/zstd/sequence_conf_dec.x | 2 - xls/modules/zstd/sequence_dec.x | 824 ++++++------ xls/modules/zstd/shift_buffer.x | 45 +- xls/modules/zstd/zstd_dec_cocotb_cli.py | 36 +- xls/modules/zstd/zstd_dec_cocotb_common.py | 4 +- xls/modules/zstd/zstd_dec_cocotb_test.py | 1183 +---------------- xls/modules/zstd/zstd_dec_detailed_test.py | 16 +- 100 files changed, 666 insertions(+), 5249 deletions(-) delete mode 100644 xls/modules/zstd/data/comp_frame.zst delete mode 100644 xls/modules/zstd/data/comp_frame_fse_comp.zst delete mode 100644 xls/modules/zstd/data/comp_frame_fse_repeated.zst delete mode 100644 xls/modules/zstd/data/comp_frame_huffman.zst delete mode 100644 xls/modules/zstd/data/comp_frame_huffman_fse.zst delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.log delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.zst delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.log delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.zst delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_210872.log delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_210872.zst delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.log delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.zst delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.log delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.zst delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.log delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.zst delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.log delete mode 100644 xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.zst delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_minimal.zst delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_random_1.log delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_random_1.zst delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_random_2.log delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_random_2.zst delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_raw_1.log delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_raw_1.zst delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_raw_2.log delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_raw_2.zst delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_rle_1.log delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_rle_1.zst delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_rle_2.log delete mode 100644 xls/modules/zstd/data/pregenerated_compressed_rle_2.zst delete mode 100644 xls/modules/zstd/data/pregenerated_uncompressed.zst delete mode 100644 xls/modules/zstd/data/raw_literals_compressed_sequences_seed_903062.log delete mode 100644 xls/modules/zstd/data/raw_literals_compressed_sequences_seed_903062.zst delete mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.log delete mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.zst delete mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_436965.log delete mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_436965.zst delete mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.log delete mode 100644 xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.zst delete mode 100644 xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.log delete mode 100644 xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.zst delete mode 100644 xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.log delete mode 100644 xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.zst delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.log delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.zst delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.log delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.zst delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.log delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.zst delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.log delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.zst delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_464057.log delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_464057.zst delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_466803.log delete mode 100644 xls/modules/zstd/data/rle_literals_predefined_sequences_seed_466803.zst delete mode 100644 xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.log delete mode 100644 xls/modules/zstd/data/rle_literals_rle_sequences_seed_2.zst delete mode 100644 xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.log delete mode 100644 xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.zst delete mode 100644 xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.log delete mode 100644 xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.zst delete mode 100644 xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.log delete mode 100644 xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.zst delete mode 100644 xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.log delete mode 100644 xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst delete mode 100644 xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.log delete mode 100644 xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst delete mode 100644 xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.log delete mode 100644 xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.zst diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index c2c393769a..250bf9ab59 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -280,7 +280,6 @@ xls_benchmark_verilog( verilog_target = "shift_buffer_storage_verilog", ) -# FIXME: Improve the proc to achieve CLOCK_PERIOD_PS SHIFT_BUFFER_CLOCK_PERIOD_PS = "1000" SHIFT_BUFFER_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { @@ -1158,14 +1157,6 @@ xls_dslx_verilog( "module_name": "FseProbaFreqDec", "generator": "pipeline", "delay_model": "asap7", - # FIXME: update ram rewrite - #"ram_configurations": "ram:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - # latency = 1, - # rd_req = "fse_proba_freq_dec__rd_req_s", - # rd_resp = "fse_proba_freq_dec__rd_resp_r", - # wr_req = "fse_proba_freq_dec__wr_req_s", - # wr_resp = "fse_proba_freq_dec__wr_resp_r", - #), "pipeline_stages": "6", "reset": "rst", "use_system_verilog": "false", @@ -1184,14 +1175,6 @@ xls_benchmark_ir( "pipeline_stages": "10", "delay_model": "asap7", "reset": "rst", - # FIXME: update ram rewrite - #"ram_configurations": "ram:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( - # latency = 1, - # rd_req = "fse_proba_freq_dec__rd_req_s", - # rd_resp = "fse_proba_freq_dec__rd_resp_r", - # wr_req = "fse_proba_freq_dec__wr_req_s", - # wr_resp = "fse_proba_freq_dec__wr_resp_r", - #), }, tags = ["manual"], ) @@ -1498,10 +1481,8 @@ ZSTD_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { "clock_period_ps": "0", "pipeline_stages": "16", "worst_case_throughput": "6", - # "flop_inputs_kind": "skid", - # "flop_outputs_kind": "skid", "streaming_channel_data_suffix": "_data", - "materialize_internal_fifos": "false", # TODO: remove once this option works with loopback channels + "materialize_internal_fifos": "false", "ram_configurations": ",".join([ ",".join([ "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( @@ -1753,17 +1734,17 @@ benchmark_synth( tags = ["manual"], ) -#place_and_route( -# name = "zstd_dec_place_and_route", -# clock_period = CLOCK_PERIOD_PS, -# core_padding_microns = 2, -# min_pin_distance = "0.4", -# placement_density = "0.1", -# stop_after_step = "global_routing", -# synthesized_rtl = ":zstd_dec_synth_asap7", -# tags = ["manual"], -# target_die_utilization_percentage = "1", -#) +place_and_route( + name = "zstd_dec_place_and_route_skip", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.4", + placement_density = "0.1", + stop_after_step = "global_routing", + synthesized_rtl = ":zstd_dec_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "1", +) py_library( name = "perf_report", @@ -1837,47 +1818,9 @@ py_library( py_test( name = "zstd_dec_cocotb_test", srcs = ["zstd_dec_cocotb_test.py"], - data = [ - "data/comp_frame.zst", - "data/comp_frame_fse_comp.zst", - "data/comp_frame_fse_repeated.zst", - "data/comp_frame_huffman.zst", - "data/comp_frame_huffman_fse.zst", - "data/fse_huffman_literals_predefined_sequences_seed_107958.zst", - "data/fse_huffman_literals_predefined_sequences_seed_204626.zst", - "data/fse_huffman_literals_predefined_sequences_seed_210872.zst", - "data/fse_huffman_literals_predefined_sequences_seed_299289.zst", - "data/fse_huffman_literals_predefined_sequences_seed_319146.zst", - "data/fse_huffman_literals_predefined_sequences_seed_331938.zst", - "data/fse_huffman_literals_predefined_sequences_seed_333824.zst", - "data/pregenerated_compressed_minimal.zst", - "data/pregenerated_compressed_random_1.zst", - "data/pregenerated_compressed_random_2.zst", - "data/pregenerated_compressed_raw_1.zst", - "data/pregenerated_compressed_raw_2.zst", - "data/pregenerated_compressed_rle_1.zst", - "data/pregenerated_compressed_rle_2.zst", - "data/pregenerated_uncompressed.zst", - "data/raw_literals_compressed_sequences_seed_903062.zst", - "data/raw_literals_predefined_sequences_seed_422473.zst", - "data/raw_literals_predefined_sequences_seed_436965.zst", - "data/raw_literals_predefined_sequences_seed_462302.zst", - "data/raw_literals_rle_sequences_seed_700216.zst", - "data/rle_literals_compressed_sequences_seed_701326.zst", - "data/rle_literals_predefined_sequences_seed_406229.zst", - "data/rle_literals_predefined_sequences_seed_411034.zst", - "data/rle_literals_predefined_sequences_seed_413015.zst", - "data/rle_literals_predefined_sequences_seed_436165.zst", - "data/rle_literals_predefined_sequences_seed_464057.zst", - "data/rle_literals_predefined_sequences_seed_466803.zst", - "data/rle_literals_rle_sequences_seed_2.zst", - "data/rle_raw_literals_predefined_sequences_seed_408158.zst", - "data/rle_raw_literals_predefined_sequences_seed_499212.zst", - "data/treeless_huffman_literals_compressed_sequences_seed_400077.zst", - "data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst", - "data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst", - "data/treeless_huffman_literals_rle_sequences_seed_403927.zst", - ], + env = { + "PYTHONUNBUFFERED": "1", + }, tags = [ "manual", "exclusive", # Test with heavy use of multithreading. Running it in parallel results in significant performance drop @@ -3045,7 +2988,6 @@ xls_dslx_library( ":literals_block_header_dec_dslx", ":literals_buffer_dslx", ":parallel_rams_dslx", - ":ram_printer_dslx", ":raw_literals_dec_dslx", ":rle_literals_dec_dslx", "//xls/examples:ram_dslx", @@ -3137,7 +3079,7 @@ LITERALS_DECODER_CODEGEN_ARGS = { "reset": "rst", "worst_case_throughput": "3", "use_system_verilog": "false", - "materialize_internal_fifos": "false", # TODO: set to true once this option works with loopback channels + "materialize_internal_fifos": "false", } xls_dslx_verilog( @@ -3189,7 +3131,6 @@ benchmark_synth( tags = ["manual"], ) -# TODO: Remove _skip suffix after fixing long P&R time. Used now to skip this target in CI. place_and_route( name = "literals_decoder_place_and_route_skip", clock_period = "750", @@ -3239,14 +3180,14 @@ PRESCAN_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { "huffman_prescan__internal_write_req_s:" + "huffman_prescan__internal_write_rsp_r:1", "io_constraints": "huffman_prescan__read_req_s:send:" + - "huffman_prescan__read_rsp_r:recv:1:1", - "materialize_internal_fifos": "false", # TODO: remove once this option works with loopback channels + "huffman_prescan__read_rsp_r:recv:5:5", + "materialize_internal_fifos": "false", } xls_dslx_verilog( name = "huffman_prescan_verilog", codegen_args = PRESCAN_CODEGEN_ARGS, - dslx_top = "WeightPreScan", + dslx_top = "WeightPreScanInst", library = ":huffman_prescan_dslx", tags = ["manual"], verilog_file = "huffman_prescan.v", @@ -3329,7 +3270,7 @@ HUFFMAN_CODE_BUILDER_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { xls_dslx_verilog( name = "huffman_code_builder_verilog", codegen_args = HUFFMAN_CODE_BUILDER_CODEGEN_ARGS, - dslx_top = "WeightCodeBuilder", + dslx_top = "WeightCodeBuilderInst", library = ":huffman_code_builder_dslx", tags = ["manual"], verilog_file = "huffman_code_builder.v", @@ -3521,7 +3462,6 @@ benchmark_synth( tags = ["manual"], ) -# TODO: Remove _skip suffix after fixing long P&R time. Used now to skip this target in CI. place_and_route( name = "huffman_data_preprocessor_place_and_route_skip", clock_period = "750", @@ -3601,7 +3541,6 @@ benchmark_synth( tags = ["manual"], ) -# TODO: Remove _skip suffix after fixing long P&R time. Used now to skip this target in CI. place_and_route( name = "huffman_decoder_place_and_route_skip", clock_period = "750", @@ -3817,7 +3756,7 @@ HUFFMAN_LITERALS_DEC_CODEGEN_ARGS = COMMON_CODEGEN_ARGS | { "clock_period_ps": "0", "worst_case_throughput": "0", "minimize_worst_case_throughput": "true", - "materialize_internal_fifos": "false", # TODO: remove once this option works with loopback channels + "materialize_internal_fifos": "false", } xls_dslx_verilog( @@ -3863,7 +3802,6 @@ benchmark_synth( tags = ["manual"], ) -# TODO: Remove _skip suffix after fixing long P&R time. Used now to skip this target in CI. place_and_route( name = "huffman_literals_dec_place_and_route_skip", clock_period = CLOCK_PERIOD_PS, @@ -3922,15 +3860,9 @@ collect_benchmark_reports( ":raw_literals_dec_place_and_route", ":literals_buffer_place_and_route", ":literals_decoder_ctrl_place_and_route", - # ":literals_decoder_place_and_route", ":huffman_prescan_place_and_route", - # ":huffman_code_builder_place_and_route", ":huffman_axi_reader_place_and_route", - # ":huffman_data_preprocessor_place_and_route", - # ":huffman_decoder_place_and_route", ":huffman_ctrl_place_and_route", ":huffman_weights_dec_place_and_route", - # ":huffman_literals_dec_place_and_route", - # ":zstd_dec_place_and_route", ], ) diff --git a/xls/modules/zstd/command_constructor.x b/xls/modules/zstd/command_constructor.x index 35d58f31b3..6eb3706b4f 100644 --- a/xls/modules/zstd/command_constructor.x +++ b/xls/modules/zstd/command_constructor.x @@ -107,7 +107,7 @@ pub proc CommandConstructor { _ => fail!("impossible_case", (zero!(), false, false)), }; - let req = LiteralsBufferCtrl { length: command.data.length as u32, last: command.data.last}; // FIXME: remove cast after unifying types of 'length' fields + let req = LiteralsBufferCtrl { length: command.data.length as u32, last: command.data.last}; send_if(tok1, literals_buffer_req_s, do_send_literals_req, req); let resp = match(state.status) { @@ -119,7 +119,7 @@ pub proc CommandConstructor { last_block: command.sync.last_block, id: command.sync.id, data: command.data.content, - length: command.data.length as u32, // FIXME: remove cast after unifying types of 'length' fields + length: command.data.length as u32, } }, Status::RECV_LITERALS => ExtendedBlockDataPacket { @@ -129,7 +129,7 @@ pub proc CommandConstructor { last_block: command.sync.last_block, id: command.sync.id, data: literals.content, - length: literals.length as u32, // FIXME: remove cast after unifying types of 'length' fields + length: literals.length as u32, } }, _ => fail!("resp_match_unreachable", zero!()) @@ -182,7 +182,7 @@ proc FakeLiteralsBuffer { ( FakeLiteralsBufferState { status: FakeLiteralsBufferStatus::SEND, - literals_left_to_send: resp.length as u64 // FIXME: remove cast after unifying types of 'length' fields + literals_left_to_send: resp.length as u64 }, false, zero!(), ) }, diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index f018ed22d4..a1baf0cb62 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -69,7 +69,6 @@ pub struct ExtendedBlockDataPacket { pub struct SequenceExecutorPacket { msg_type: SequenceExecutorMessageType, - // TODO: this should be max(8, clog2(maximum match value)) length: CopyOrMatchLength, // Literal length or match length content: uN[DATA_W * u32:8], // Literal data or match offset last: bool, // Last packet in frame @@ -129,8 +128,6 @@ pub const FSE_PROB_DIST_WIDTH = u32:16; pub const FSE_MAX_PROB_DIST = u32:256; pub const FSE_SYMBOL_WIDTH = u32:16; -// FIXME: Tests in DSLX interpreter require smaller RAMs due to the problem -// with ram consumtopn descibed in https://github.com/google/xls/issues/1042 pub const TEST_FSE_MAX_ACCURACY_LOG = u32:9; pub type FseRemainingProba = uN[FSE_REMAINING_PROBA_WIDTH]; diff --git a/xls/modules/zstd/comp_block_dec.x b/xls/modules/zstd/comp_block_dec.x index 9b35558522..d9aa769a2f 100644 --- a/xls/modules/zstd/comp_block_dec.x +++ b/xls/modules/zstd/comp_block_dec.x @@ -348,9 +348,6 @@ pub proc CompressBlockDecoder< huffman_lit_weights_fse_wr_req_s: chan out, huffman_lit_weights_fse_wr_resp_r: chan in, ) { - // TODO: for consistency all MemReaders should be in toplevel ZSTD decoder - // so we should move them up in the hierarchy from LiteralsDecoder - // and SequenceDecoder to the toplevel const CHANNEL_DEPTH = u32:1; let (lit_ctrl_req_s, lit_ctrl_req_r) = chan("lit_ctrl_req"); @@ -1603,7 +1600,6 @@ proc CompressBlockDecoderTest { let tok = send(tok, ml_sel_test_req_s, u1:1); let (tok, _) = recv(tok, ml_sel_test_resp_r); - // TODO: Enable more test cases when posssible. Currently their number is limited to lower the RAM consumption let tok = unroll_for!(test_i, tok): (u32, token) in u32[2]:[u32:0, u32:4] { let (input_length, input, output_length, output) = COMP_BLOCK_DEC_TESTCASES[test_i]; diff --git a/xls/modules/zstd/data/comp_frame.zst b/xls/modules/zstd/data/comp_frame.zst deleted file mode 100644 index 27ae5c886796d5e7f8b1d2f1adcbaf532e9c24c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51 zcmV-30L=d=wJ-gIFHisg02KiMaFx%*ldOrkP<`PD=BAYh0Iv5Ma6JW`-kC J;IIYbaD)?4754xD diff --git a/xls/modules/zstd/data/comp_frame_fse_comp.zst b/xls/modules/zstd/data/comp_frame_fse_comp.zst deleted file mode 100644 index 6a16ad5e5c227c108129130c9342b23429f5c443..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66 zcmV-I0KNYxwJ-gIFH`^k0F40v&^AI31@t#{HJBY6dDqsiwUUi=l>sT&j#E(ss1b(y Y0TFOn(Fbvb|Idp3V+xM51cp>m#Y{;XVE_OC diff --git a/xls/modules/zstd/data/comp_frame_fse_repeated.zst b/xls/modules/zstd/data/comp_frame_fse_repeated.zst deleted file mode 100644 index 8fed4b3fbe63c83cc93ddfe47fbfd9d14d5d3797..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92 zcmdPcs{gk|#g2i2p@o5=@)}bHqaa(oVB7!yb$5eUTo@P(QyELx7`L&=Ffb@?W&o0m d&%q=wnA{5{t-xeGn9OF71**wp*f!I4CIA*T6FC3? diff --git a/xls/modules/zstd/data/comp_frame_huffman.zst b/xls/modules/zstd/data/comp_frame_huffman.zst deleted file mode 100644 index e760569c50ba62a20f62d419f3f02f01624f82f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93 zcmdPcs{gk|zkq>(A(e^2ig^t~pCAMXurq+5Pdi(f6|0nRi)qZ39FgslOBchq5d8nX-tPbV^Y-um?_FR1KlV2x TGsA`n4`mg3{5V!L*EIkD)9M)1 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.log deleted file mode 100644 index 74008be9c2..0000000000 --- a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.log +++ /dev/null @@ -1,30 +0,0 @@ -seed: 107958 -frame seed: 107958 - frame content size: 100 - frame window size: 1536 - content size flag: 1 - single segment flag: 0 - block: - block content size: 100 - last block: yes - compressed block: - compressed literals - distribution weight: 81% - huffman log: 10 - regenerated size: 45 - compressed size: 22 - literals size: 45 - total match lengths: 55 - LL: 13 OF: 9 ML: 4 srcPos: 17 seqNb: 0 - LL: 12 OF: 6 ML: 9 srcPos: 38 seqNb: 1 - LL: 3 OF: 36 ML: 25 srcPos: 66 seqNb: 2 - LL: 0 OF: 42 ML: 5 srcPos: 71 seqNb: 3 - LL: 14 OF: 2 ML: 5 srcPos: 90 seqNb: 4 - LL: 0 OF: 27 ML: 3 srcPos: 93 seqNb: 5 - LL: 1 OF: 67 ML: 4 srcPos: 98 seqNb: 6 - excess literals: 2 srcPos: 100 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 7 - block type: compressed - block size field: 44 - checksum: 61c6e5a0 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_107958.zst deleted file mode 100644 index b44e0191ea5b9139965130057d3b415d874e51b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61 zcmV-D0K)$$wJ-gI1Y`gJ0A&FH(t-sJ&}BFPumXTV3h1JO#smBR-+%YrsRsZ?2*f7< TxznavaMT(pl`tq@pykG4*5etX diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.log deleted file mode 100644 index 6a11616659..0000000000 --- a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.log +++ /dev/null @@ -1,25 +0,0 @@ -seed: 204626 -frame seed: 204626 - frame content size: 89 - frame window size: 1152 - content size flag: 1 - single segment flag: 0 - block: - block content size: 89 - last block: yes - compressed block: - compressed literals - distribution weight: 64% - huffman log: 9 - regenerated size: 66 - compressed size: 36 - literals size: 66 - total match lengths: 23 - LL: 42 OF: 16 ML: 9 srcPos: 51 seqNb: 0 - LL: 24 OF: 6 ML: 14 srcPos: 89 seqNb: 1 - excess literals: 0 srcPos: 89 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 2 - block type: compressed - block size field: 47 - checksum: 9834e3da diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_204626.zst deleted file mode 100644 index cc5812eb6ae7bf59dca1efd3eb6e4388601a463f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64 zcmV-G0KfkzwJ-gI0a*Y50DS=fA_NH)pe2QL|0Dm?|Fiz?`Z9`A?>> jFa1!@^wT_$t=97~6Dva(rwE^po`i(~3m>;o{=|F$trHuh diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.log deleted file mode 100644 index f2f01bd94a..0000000000 --- a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.log +++ /dev/null @@ -1,39 +0,0 @@ -seed: 299289 -frame seed: 299289 - frame content size: 68 - frame window size: 294912 - content size flag: 1 - single segment flag: 0 - block: - block content size: 68 - last block: yes - compressed block: - compressed literals - distribution weight: 24% - huffman log: 11 - regenerated size: 60 - compressed size: 74 - trying again - distribution weight: 44% - huffman log: 9 - regenerated size: 41 - compressed size: 45 - trying again - distribution weight: 86% - huffman log: 11 - regenerated size: 46 - compressed size: 26 - literals size: 46 - total match lengths: 22 - LL: 7 OF: 6 ML: 5 srcPos: 12 seqNb: 0 - LL: 5 OF: 8 ML: 4 srcPos: 21 seqNb: 1 - LL: 18 OF: 1 ML: 6 srcPos: 45 seqNb: 2 - repeat offset: 2 - LL: 0 OF: 16 ML: 4 srcPos: 49 seqNb: 3 - LL: 16 OF: 29 ML: 3 srcPos: 68 seqNb: 4 - excess literals: 0 srcPos: 68 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 5 - block type: compressed - block size field: 43 - checksum: 28ab4e28 diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_299289.zst deleted file mode 100644 index 290a0209b6a67afe7168912a4b65aec815c0aaee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60 zcmdPcs{gk|--UsJA(oNhSrZ%ggG`=P{>(uB(HtfQ5PWa{b^rfc-}zY?8k*)xHk2Ro N+7pw?t>L#?0{}}M6tVyS diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.log deleted file mode 100644 index dc4eaae5ff..0000000000 --- a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.log +++ /dev/null @@ -1,26 +0,0 @@ -seed: 319146 -frame seed: 319146 - frame content size: 114 - frame window size: 720896 - content size flag: 1 - single segment flag: 0 - block: - block content size: 114 - last block: yes - compressed block: - compressed literals - distribution weight: 23% - huffman log: 10 - regenerated size: 105 - compressed size: 91 - literals size: 105 - total match lengths: 9 - LL: 90 OF: 16 ML: 4 srcPos: 94 seqNb: 0 - LL: 15 OF: 1 ML: 5 srcPos: 114 seqNb: 1 - repeat offset: 1 - excess literals: 0 srcPos: 114 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 2 - block type: compressed - block size field: 104 - checksum: 9a966cac diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_319146.zst deleted file mode 100644 index 418a99be2fb638739cde7131a0a9859c7218a6a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125 zcmdPcs{i+heh~u%xH2=$V>=+mpx&VBd5N*M&%4!Abb`UsL)L4(4p`eh4N44Jn&7&s zfhCmbUgp(9tGRd>xEXjDo<#KNuVKH|(Q`2@YkIQV?bo+vCVyHjDlP19rf)H+gx5Ti cbxGO3=*gQaBbvFH7}ka4Hc7Is$(c3_0O?FGN&o-= diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.log deleted file mode 100644 index 557c6176f1..0000000000 --- a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.log +++ /dev/null @@ -1,25 +0,0 @@ -seed: 331938 -frame seed: 331938 - frame content size: 104 - frame window size: 36864 - content size flag: 1 - single segment flag: 0 - block: - block content size: 104 - last block: yes - compressed block: - compressed literals - distribution weight: 37% - huffman log: 9 - regenerated size: 67 - compressed size: 60 - literals size: 67 - total match lengths: 37 - LL: 31 OF: 31 ML: 23 srcPos: 54 seqNb: 0 - LL: 19 OF: 13 ML: 14 srcPos: 87 seqNb: 1 - excess literals: 17 srcPos: 104 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 2 - block type: compressed - block size field: 72 - checksum: d76a8c9e diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_331938.zst deleted file mode 100644 index 943fafbf00fffabb1fa2d2784f50c5cfb70ceba8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93 zcmdPcs{i+hW(ETUxH2(Vv3y{VnV{-v^|}1Zgt>J$_OH3BU;6cF_^+w*D~+EPeG_J5 vU}s=s(7L))x}52;xSZD~N$*wq>iHq8tCng!n#|9{ka#LFaJ9|6o~-KtQv4(n diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.log b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.log deleted file mode 100644 index 025dfe3f63..0000000000 --- a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.log +++ /dev/null @@ -1,27 +0,0 @@ -seed: 333824 -frame seed: 333824 - frame content size: 109 - frame window size: 109 - content size flag: 1 - single segment flag: 1 - block: - block content size: 109 - last block: yes - compressed block: - compressed literals - distribution weight: 74% - huffman log: 9 - regenerated size: 86 - compressed size: 30 - literals size: 86 - total match lengths: 23 - LL: 10 OF: 5 ML: 3 srcPos: 13 seqNb: 0 - LL: 45 OF: 40 ML: 10 srcPos: 68 seqNb: 1 - LL: 21 OF: 5 ML: 10 srcPos: 99 seqNb: 2 - repeat offset: 1 - excess literals: 10 srcPos: 109 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 3 - block type: compressed - block size field: 43 - checksum: 5905f5bb diff --git a/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.zst b/xls/modules/zstd/data/fse_huffman_literals_predefined_sequences_seed_333824.zst deleted file mode 100644 index 51b18d62b87ad7cfbad0b4f5e96e4cccda62bc75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56 zcmdPcs{dCdHZg`6ou9S z5JU*~5J;8PI8mhQEFGL?0Nh8g z4m2p9q(C15-jpEd0N`!#j#oYixCUef0Hc8$0VoX2|kOt6L zkPomvu%rNiO5lnD3<$uwhTkK=IHVu!04+dF0N@Bn&j166X8?!uzbLO0x}3Ppn%bWE^@Ud$Q%Yiz?mQboC^>)^sTYxgG_2nAe#AqD^v0Qw~? vEl4>)P9^5f~RykOuNWH4MB5uq}Ww-~kj7zL$y8T=~|_ diff --git a/xls/modules/zstd/data/pregenerated_compressed_random_2.log b/xls/modules/zstd/data/pregenerated_compressed_random_2.log deleted file mode 100644 index e120ce7b47..0000000000 --- a/xls/modules/zstd/data/pregenerated_compressed_random_2.log +++ /dev/null @@ -1,70 +0,0 @@ -seed: 2 -frame seed: 2 - frame content size: 152 - frame window size: 114688 - content size flag: 1 - single segment flag: 0 - block: - block content size: 91 - last block: no - compressed block: - compressed literals - distribution weight: 33% - huffman log: 10 - regenerated size: 44 - compressed size: 49 - trying again - distribution weight: 64% - huffman log: 8 - regenerated size: 35 - compressed size: 30 - literals size: 35 - total match lengths: 56 - LL: 14 OF: 12 ML: 6 srcPos: 20 seqNb: 0 - LL: 1 OF: 9 ML: 9 srcPos: 30 seqNb: 1 - LL: 5 OF: 12 ML: 10 srcPos: 45 seqNb: 2 - LL: 0 OF: 39 ML: 3 srcPos: 48 seqNb: 3 - LL: 5 OF: 3 ML: 13 srcPos: 66 seqNb: 4 - LL: 10 OF: 24 ML: 15 srcPos: 91 seqNb: 5 - excess literals: 0 srcPos: 91 - LL type: 2 OF type: 2 ML type: 2 - number of sequences: 6 - block type: compressed - block size field: 61 - block: - block content size: 53 - last block: no - compressed block: - rle literals: 0xf8 - literals size: 19 - total match lengths: 34 - LL: 2 OF: 56 ML: 3 srcPos: 96 seqNb: 0 - LL: 1 OF: 56 ML: 3 srcPos: 100 seqNb: 1 - repeat offset: 0 - LL: 0 OF: 40 ML: 4 srcPos: 104 seqNb: 2 - LL: 0 OF: 48 ML: 4 srcPos: 108 seqNb: 3 - LL: 6 OF: 53 ML: 3 srcPos: 117 seqNb: 4 - LL: 5 OF: 81 ML: 3 srcPos: 125 seqNb: 5 - LL: 2 OF: 111 ML: 4 srcPos: 131 seqNb: 6 - LL: 3 OF: 79 ML: 3 srcPos: 137 seqNb: 7 - LL: 0 OF: 78 ML: 3 srcPos: 140 seqNb: 8 - LL: 0 OF: 87 ML: 4 srcPos: 144 seqNb: 9 - excess literals: 0 srcPos: 144 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 10 - block type: compressed - block size field: 29 - block: - block content size: 8 - last block: yes - compressed block: - raw literals - literals size: 0 - total match lengths: 8 - LL: 0 OF: 58 ML: 8 srcPos: 152 seqNb: 0 - excess literals: 0 srcPos: 152 - LL type: 3 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 7 - checksum: e7ab4be9 diff --git a/xls/modules/zstd/data/pregenerated_compressed_random_2.zst b/xls/modules/zstd/data/pregenerated_compressed_random_2.zst deleted file mode 100644 index 5e5bc05a935d70ca3db27d2bc3714b0e1b8ba579..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 zcmV-;0Eho5wJ-gIFPH!T0PFz(GJ*#cpe0EMXu$vfze!Z7s;VGo5C8zf2W|GT<=Toz z9tNmjOxQ9O4-hid4uB#PnI#Jc3&^YR3!WYh3G4s>nfM9-S_BXU0HlB+M}VmSXsn>7 a3Ro%t0Nf2IEIj}K00Gnm1sw?KORMKkQYj$- diff --git a/xls/modules/zstd/data/pregenerated_compressed_raw_1.log b/xls/modules/zstd/data/pregenerated_compressed_raw_1.log deleted file mode 100644 index 15e8ef895b..0000000000 --- a/xls/modules/zstd/data/pregenerated_compressed_raw_1.log +++ /dev/null @@ -1,347 +0,0 @@ -seed: 1 -frame seed: 1 - frame content size: 1019 - frame window size: 294912 - content size flag: 1 - single segment flag: 0 - block: - block content size: 181 - last block: no - compressed block: - raw literals - literals size: 139 - total match lengths: 42 - LL: 37 OF: 31 ML: 13 srcPos: 50 seqNb: 0 - LL: 25 OF: 16 ML: 29 srcPos: 104 seqNb: 1 - excess literals: 77 srcPos: 181 - LL type: 2 OF type: 2 ML type: 2 - number of sequences: 2 - block type: compressed - block size field: 168 - block: - block content size: 0 - last block: no - compressed block: - raw literals - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 181 - number of sequences: 0 - block type: compressed - block size field: 2 - block: - block content size: 63 - last block: no - compressed block: - raw literals - literals size: 7 - total match lengths: 56 - LL: 0 OF: 70 ML: 3 srcPos: 184 seqNb: 0 - LL: 0 OF: 56 ML: 3 srcPos: 187 seqNb: 1 - LL: 0 OF: 55 ML: 3 srcPos: 190 seqNb: 2 - repeat offset: 2 - LL: 0 OF: 23 ML: 3 srcPos: 193 seqNb: 3 - LL: 0 OF: 4 ML: 3 srcPos: 196 seqNb: 4 - LL: 0 OF: 136 ML: 3 srcPos: 199 seqNb: 5 - LL: 0 OF: 23 ML: 3 srcPos: 202 seqNb: 6 - LL: 0 OF: 28 ML: 3 srcPos: 205 seqNb: 7 - LL: 0 OF: 2 ML: 3 srcPos: 208 seqNb: 8 - LL: 1 OF: 5 ML: 3 srcPos: 212 seqNb: 9 - LL: 0 OF: 4 ML: 3 srcPos: 215 seqNb: 10 - repeat offset: 2 - LL: 0 OF: 2 ML: 3 srcPos: 218 seqNb: 11 - repeat offset: 2 - LL: 0 OF: 56 ML: 3 srcPos: 221 seqNb: 12 - LL: 2 OF: 209 ML: 3 srcPos: 226 seqNb: 13 - LL: 0 OF: 24 ML: 4 srcPos: 230 seqNb: 14 - LL: 0 OF: 14 ML: 3 srcPos: 233 seqNb: 15 - LL: 0 OF: 112 ML: 4 srcPos: 237 seqNb: 16 - LL: 0 OF: 233 ML: 3 srcPos: 240 seqNb: 17 - excess literals: 4 srcPos: 244 - LL type: 2 OF type: 2 ML type: 2 - number of sequences: 18 - block type: compressed - block size field: 39 - block: - block content size: 59 - last block: no - compressed block: - raw literals - literals size: 19 - total match lengths: 40 - LL: 2 OF: 90 ML: 6 srcPos: 252 seqNb: 0 - LL: 2 OF: 153 ML: 5 srcPos: 259 seqNb: 1 - LL: 10 OF: 76 ML: 10 srcPos: 279 seqNb: 2 - LL: 1 OF: 226 ML: 5 srcPos: 285 seqNb: 3 - LL: 2 OF: 5 ML: 4 srcPos: 291 seqNb: 4 - LL: 2 OF: 33 ML: 7 srcPos: 300 seqNb: 5 - LL: 0 OF: 262 ML: 3 srcPos: 303 seqNb: 6 - excess literals: 0 srcPos: 303 - LL type: 2 OF type: 2 ML type: 0 - number of sequences: 7 - block type: compressed - block size field: 45 - block: - block content size: 614 - last block: no - compressed block: - raw literals - literals size: 159 - total match lengths: 455 - LL: 2 OF: 128 ML: 3 srcPos: 308 seqNb: 0 - LL: 3 OF: 96 ML: 3 srcPos: 314 seqNb: 1 - LL: 0 OF: 123 ML: 3 srcPos: 317 seqNb: 2 - LL: 0 OF: 225 ML: 3 srcPos: 320 seqNb: 3 - LL: 1 OF: 172 ML: 3 srcPos: 324 seqNb: 4 - LL: 2 OF: 56 ML: 3 srcPos: 329 seqNb: 5 - LL: 2 OF: 13 ML: 3 srcPos: 334 seqNb: 6 - LL: 1 OF: 327 ML: 3 srcPos: 338 seqNb: 7 - LL: 0 OF: 264 ML: 3 srcPos: 341 seqNb: 8 - LL: 0 OF: 13 ML: 3 srcPos: 344 seqNb: 9 - repeat offset: 2 - LL: 0 OF: 340 ML: 3 srcPos: 347 seqNb: 10 - LL: 0 OF: 95 ML: 3 srcPos: 350 seqNb: 11 - LL: 0 OF: 221 ML: 3 srcPos: 353 seqNb: 12 - LL: 0 OF: 95 ML: 3 srcPos: 356 seqNb: 13 - repeat offset: 1 - LL: 0 OF: 94 ML: 3 srcPos: 359 seqNb: 14 - repeat offset: 2 - LL: 0 OF: 95 ML: 3 srcPos: 362 seqNb: 15 - repeat offset: 1 - LL: 0 OF: 294 ML: 3 srcPos: 365 seqNb: 16 - LL: 0 OF: 250 ML: 3 srcPos: 368 seqNb: 17 - LL: 3 OF: 310 ML: 3 srcPos: 374 seqNb: 18 - LL: 0 OF: 59 ML: 3 srcPos: 377 seqNb: 19 - LL: 0 OF: 17 ML: 3 srcPos: 380 seqNb: 20 - LL: 1 OF: 59 ML: 3 srcPos: 384 seqNb: 21 - repeat offset: 1 - LL: 0 OF: 372 ML: 3 srcPos: 387 seqNb: 22 - LL: 1 OF: 203 ML: 3 srcPos: 391 seqNb: 23 - LL: 1 OF: 251 ML: 3 srcPos: 395 seqNb: 24 - LL: 0 OF: 203 ML: 3 srcPos: 398 seqNb: 25 - repeat offset: 1 - LL: 1 OF: 363 ML: 3 srcPos: 402 seqNb: 26 - LL: 0 OF: 321 ML: 3 srcPos: 405 seqNb: 27 - LL: 1 OF: 395 ML: 3 srcPos: 409 seqNb: 28 - LL: 1 OF: 381 ML: 3 srcPos: 413 seqNb: 29 - LL: 3 OF: 99 ML: 3 srcPos: 419 seqNb: 30 - LL: 1 OF: 116 ML: 3 srcPos: 423 seqNb: 31 - LL: 0 OF: 293 ML: 3 srcPos: 426 seqNb: 32 - LL: 0 OF: 292 ML: 3 srcPos: 429 seqNb: 33 - repeat offset: 2 - LL: 1 OF: 127 ML: 3 srcPos: 433 seqNb: 34 - LL: 0 OF: 219 ML: 3 srcPos: 436 seqNb: 35 - LL: 0 OF: 236 ML: 3 srcPos: 439 seqNb: 36 - LL: 1 OF: 353 ML: 3 srcPos: 443 seqNb: 37 - LL: 1 OF: 46 ML: 3 srcPos: 447 seqNb: 38 - LL: 3 OF: 404 ML: 3 srcPos: 453 seqNb: 39 - LL: 0 OF: 267 ML: 3 srcPos: 456 seqNb: 40 - LL: 0 OF: 421 ML: 3 srcPos: 459 seqNb: 41 - LL: 1 OF: 201 ML: 3 srcPos: 463 seqNb: 42 - LL: 1 OF: 12 ML: 3 srcPos: 467 seqNb: 43 - LL: 0 OF: 438 ML: 3 srcPos: 470 seqNb: 44 - LL: 1 OF: 136 ML: 3 srcPos: 474 seqNb: 45 - LL: 2 OF: 126 ML: 3 srcPos: 479 seqNb: 46 - LL: 0 OF: 400 ML: 3 srcPos: 482 seqNb: 47 - LL: 0 OF: 130 ML: 3 srcPos: 485 seqNb: 48 - LL: 2 OF: 206 ML: 3 srcPos: 490 seqNb: 49 - LL: 0 OF: 302 ML: 3 srcPos: 493 seqNb: 50 - LL: 0 OF: 206 ML: 3 srcPos: 496 seqNb: 51 - LL: 1 OF: 420 ML: 3 srcPos: 500 seqNb: 52 - LL: 1 OF: 131 ML: 3 srcPos: 504 seqNb: 53 - LL: 1 OF: 172 ML: 3 srcPos: 508 seqNb: 54 - LL: 0 OF: 131 ML: 3 srcPos: 511 seqNb: 55 - repeat offset: 1 - LL: 2 OF: 438 ML: 3 srcPos: 516 seqNb: 56 - LL: 0 OF: 453 ML: 3 srcPos: 519 seqNb: 57 - LL: 0 OF: 248 ML: 3 srcPos: 522 seqNb: 58 - LL: 0 OF: 438 ML: 3 srcPos: 525 seqNb: 59 - repeat offset: 2 - LL: 1 OF: 326 ML: 3 srcPos: 529 seqNb: 60 - LL: 0 OF: 248 ML: 3 srcPos: 532 seqNb: 61 - repeat offset: 2 - LL: 1 OF: 46 ML: 3 srcPos: 536 seqNb: 62 - LL: 2 OF: 139 ML: 3 srcPos: 541 seqNb: 63 - LL: 1 OF: 139 ML: 3 srcPos: 545 seqNb: 64 - repeat offset: 0 - LL: 0 OF: 508 ML: 4 srcPos: 549 seqNb: 65 - LL: 2 OF: 482 ML: 3 srcPos: 554 seqNb: 66 - LL: 1 OF: 127 ML: 3 srcPos: 558 seqNb: 67 - LL: 3 OF: 485 ML: 3 srcPos: 564 seqNb: 68 - LL: 0 OF: 300 ML: 3 srcPos: 567 seqNb: 69 - LL: 2 OF: 338 ML: 3 srcPos: 572 seqNb: 70 - LL: 1 OF: 285 ML: 3 srcPos: 576 seqNb: 71 - LL: 0 OF: 300 ML: 3 srcPos: 579 seqNb: 72 - repeat offset: 2 - LL: 1 OF: 60 ML: 3 srcPos: 583 seqNb: 73 - LL: 2 OF: 304 ML: 3 srcPos: 588 seqNb: 74 - LL: 2 OF: 106 ML: 3 srcPos: 593 seqNb: 75 - LL: 4 OF: 106 ML: 3 srcPos: 600 seqNb: 76 - repeat offset: 0 - LL: 2 OF: 106 ML: 3 srcPos: 605 seqNb: 77 - repeat offset: 0 - LL: 5 OF: 60 ML: 3 srcPos: 613 seqNb: 78 - repeat offset: 2 - LL: 2 OF: 594 ML: 3 srcPos: 618 seqNb: 79 - LL: 1 OF: 234 ML: 3 srcPos: 622 seqNb: 80 - LL: 1 OF: 227 ML: 3 srcPos: 626 seqNb: 81 - LL: 0 OF: 235 ML: 3 srcPos: 629 seqNb: 82 - LL: 1 OF: 311 ML: 3 srcPos: 633 seqNb: 83 - LL: 0 OF: 253 ML: 3 srcPos: 636 seqNb: 84 - LL: 1 OF: 286 ML: 3 srcPos: 640 seqNb: 85 - LL: 0 OF: 175 ML: 3 srcPos: 643 seqNb: 86 - LL: 1 OF: 395 ML: 3 srcPos: 647 seqNb: 87 - LL: 2 OF: 544 ML: 3 srcPos: 652 seqNb: 88 - LL: 0 OF: 278 ML: 3 srcPos: 655 seqNb: 89 - LL: 1 OF: 450 ML: 3 srcPos: 659 seqNb: 90 - LL: 4 OF: 469 ML: 3 srcPos: 666 seqNb: 91 - LL: 5 OF: 192 ML: 3 srcPos: 674 seqNb: 92 - LL: 0 OF: 492 ML: 3 srcPos: 677 seqNb: 93 - LL: 2 OF: 192 ML: 3 srcPos: 682 seqNb: 94 - repeat offset: 1 - LL: 2 OF: 597 ML: 3 srcPos: 687 seqNb: 95 - LL: 1 OF: 501 ML: 3 srcPos: 691 seqNb: 96 - LL: 0 OF: 148 ML: 3 srcPos: 694 seqNb: 97 - LL: 1 OF: 513 ML: 3 srcPos: 698 seqNb: 98 - LL: 0 OF: 143 ML: 3 srcPos: 701 seqNb: 99 - LL: 2 OF: 89 ML: 3 srcPos: 706 seqNb: 100 - LL: 1 OF: 513 ML: 3 srcPos: 710 seqNb: 101 - repeat offset: 2 - LL: 0 OF: 35 ML: 3 srcPos: 713 seqNb: 102 - LL: 0 OF: 218 ML: 3 srcPos: 716 seqNb: 103 - LL: 1 OF: 140 ML: 3 srcPos: 720 seqNb: 104 - LL: 2 OF: 465 ML: 3 srcPos: 725 seqNb: 105 - LL: 0 OF: 183 ML: 3 srcPos: 728 seqNb: 106 - LL: 0 OF: 704 ML: 4 srcPos: 732 seqNb: 107 - LL: 2 OF: 361 ML: 3 srcPos: 737 seqNb: 108 - LL: 1 OF: 460 ML: 3 srcPos: 741 seqNb: 109 - LL: 3 OF: 554 ML: 3 srcPos: 747 seqNb: 110 - LL: 3 OF: 544 ML: 3 srcPos: 753 seqNb: 111 - LL: 0 OF: 19 ML: 3 srcPos: 756 seqNb: 112 - LL: 2 OF: 539 ML: 3 srcPos: 761 seqNb: 113 - LL: 0 OF: 557 ML: 3 srcPos: 764 seqNb: 114 - LL: 3 OF: 501 ML: 3 srcPos: 770 seqNb: 115 - LL: 2 OF: 557 ML: 3 srcPos: 775 seqNb: 116 - repeat offset: 1 - LL: 0 OF: 683 ML: 3 srcPos: 778 seqNb: 117 - LL: 0 OF: 362 ML: 3 srcPos: 781 seqNb: 118 - LL: 1 OF: 723 ML: 4 srcPos: 786 seqNb: 119 - LL: 1 OF: 526 ML: 4 srcPos: 791 seqNb: 120 - LL: 0 OF: 653 ML: 3 srcPos: 794 seqNb: 121 - LL: 0 OF: 572 ML: 3 srcPos: 797 seqNb: 122 - LL: 1 OF: 572 ML: 3 srcPos: 801 seqNb: 123 - repeat offset: 0 - LL: 0 OF: 383 ML: 4 srcPos: 805 seqNb: 124 - LL: 4 OF: 383 ML: 4 srcPos: 813 seqNb: 125 - repeat offset: 0 - LL: 1 OF: 46 ML: 4 srcPos: 818 seqNb: 126 - LL: 0 OF: 742 ML: 3 srcPos: 821 seqNb: 127 - LL: 2 OF: 84 ML: 4 srcPos: 827 seqNb: 128 - LL: 1 OF: 793 ML: 3 srcPos: 831 seqNb: 129 - LL: 4 OF: 277 ML: 3 srcPos: 838 seqNb: 130 - LL: 0 OF: 312 ML: 3 srcPos: 841 seqNb: 131 - LL: 1 OF: 306 ML: 3 srcPos: 845 seqNb: 132 - LL: 0 OF: 706 ML: 3 srcPos: 848 seqNb: 133 - LL: 0 OF: 244 ML: 3 srcPos: 851 seqNb: 134 - LL: 4 OF: 301 ML: 3 srcPos: 858 seqNb: 135 - LL: 1 OF: 53 ML: 3 srcPos: 862 seqNb: 136 - LL: 1 OF: 313 ML: 3 srcPos: 866 seqNb: 137 - LL: 1 OF: 53 ML: 3 srcPos: 870 seqNb: 138 - repeat offset: 1 - LL: 1 OF: 301 ML: 4 srcPos: 875 seqNb: 139 - repeat offset: 2 - LL: 6 OF: 53 ML: 3 srcPos: 884 seqNb: 140 - repeat offset: 1 - LL: 3 OF: 295 ML: 3 srcPos: 890 seqNb: 141 - LL: 0 OF: 363 ML: 3 srcPos: 893 seqNb: 142 - LL: 1 OF: 742 ML: 4 srcPos: 898 seqNb: 143 - LL: 2 OF: 742 ML: 4 srcPos: 904 seqNb: 144 - repeat offset: 0 - LL: 2 OF: 728 ML: 3 srcPos: 909 seqNb: 145 - LL: 1 OF: 728 ML: 3 srcPos: 913 seqNb: 146 - repeat offset: 0 - LL: 0 OF: 597 ML: 3 srcPos: 916 seqNb: 147 - excess literals: 1 srcPos: 917 - LL type: 0 OF type: 0 ML type: 3 - number of sequences: 148 - block type: compressed - block size field: 550 - block: - block content size: 24 - last block: no - compressed block: - raw literals - literals size: 21 - total match lengths: 3 - LL: 9 OF: 905 ML: 3 srcPos: 929 seqNb: 0 - excess literals: 12 srcPos: 941 - LL type: 1 OF type: 1 ML type: 3 - number of sequences: 1 - block type: compressed - block size field: 28 - block: - block content size: 0 - last block: no - compressed block: - raw literals - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 941 - number of sequences: 0 - block type: compressed - block size field: 2 - block: - block content size: 24 - last block: no - compressed block: - raw literals - literals size: 14 - total match lengths: 10 - LL: 11 OF: 506 ML: 10 srcPos: 962 seqNb: 0 - excess literals: 3 srcPos: 965 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 22 - block: - block content size: 47 - last block: no - compressed block: - raw literals - literals size: 40 - total match lengths: 7 - LL: 40 OF: 833 ML: 4 srcPos: 1009 seqNb: 0 - LL: 0 OF: 832 ML: 3 srcPos: 1012 seqNb: 1 - repeat offset: 2 - excess literals: 0 srcPos: 1012 - LL type: 0 OF type: 0 ML type: 2 - number of sequences: 2 - block type: compressed - block size field: 53 - block: - block content size: 4 - last block: no - compressed block: - raw literals - literals size: 0 - total match lengths: 4 - LL: 0 OF: 92 ML: 4 srcPos: 1016 seqNb: 0 - excess literals: 0 srcPos: 1016 - LL type: 3 OF type: 1 ML type: 3 - number of sequences: 1 - block type: compressed - block size field: 7 - block: - block content size: 3 - last block: yes - compressed block: - raw literals - literals size: 0 - total match lengths: 3 - LL: 0 OF: 280 ML: 3 srcPos: 1019 seqNb: 0 - excess literals: 0 srcPos: 1019 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 8 - checksum: 20a07047 diff --git a/xls/modules/zstd/data/pregenerated_compressed_raw_1.zst b/xls/modules/zstd/data/pregenerated_compressed_raw_1.zst deleted file mode 100644 index 51d682f2ba7ef4d21c23b67f7d8b52c8c274d03c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 975 zcmV;=12Ft3wJ-g|FZ%-k0000007L}U1+0te4+(PM}5`4S%4#K~0e4b!> z6np>HcPmBA)MmhAm7o|)L^$3P119i$P3cL5u^8T}Cea-+RuWxK)_f4aTK^$2r!+1h$=1w39bMDa6beP zkOV*k5ob*TZd-u45EUo0hdDnB@YM!0^G%bz+b?{A^_|Fd?z3bAJEtfQ1Jqnv4Ai_0CZe{Wd>Nt0t~Q# z=`6r*1I%myYa2jR5deMx4FC)PX#>DB3&5`cc7TBpz^Vx77XmCg0V8of@L`rwEnV{I-$Jl#*-- xp(j4Orn?{>H6Q{A6!1SU0HOgz89V?000G{M>pXq=MiB#L6R)2XmS(&lAC#4x{_}C3XNx`tMGFeX&8w1c+zUu*076B9%Bmn?;Q_lcm z93$)3o$+ygq1Dv`h!ikj{{z%2fK>ti0W2LgJ#K*f002N=!^){%+&j1gpwudWRsRz} vP@tgTAGAOh?9mp(*gOCL00C401pzk{000008~^|aVgMZg00=7pcn0~a+lWeu diff --git a/xls/modules/zstd/data/pregenerated_compressed_rle_1.log b/xls/modules/zstd/data/pregenerated_compressed_rle_1.log deleted file mode 100644 index 6758c747f2..0000000000 --- a/xls/modules/zstd/data/pregenerated_compressed_rle_1.log +++ /dev/null @@ -1,179 +0,0 @@ -seed: 1 -frame seed: 1 - frame content size: 1019 - frame window size: 294912 - content size flag: 1 - single segment flag: 0 - block: - block content size: 181 - last block: no - compressed block: - rle literals: 0xed - literals size: 139 - total match lengths: 42 - LL: 139 OF: 97 ML: 42 srcPos: 181 seqNb: 0 - excess literals: 0 srcPos: 181 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 10 - block: - block content size: 178 - last block: no - compressed block: - rle literals: 0xc6 - literals size: 21 - total match lengths: 157 - LL: 0 OF: 53 ML: 4 srcPos: 185 seqNb: 0 - LL: 0 OF: 155 ML: 3 srcPos: 188 seqNb: 1 - LL: 0 OF: 7 ML: 3 srcPos: 191 seqNb: 2 - LL: 2 OF: 124 ML: 4 srcPos: 197 seqNb: 3 - LL: 0 OF: 138 ML: 4 srcPos: 201 seqNb: 4 - LL: 0 OF: 92 ML: 4 srcPos: 205 seqNb: 5 - LL: 0 OF: 1 ML: 5 srcPos: 210 seqNb: 6 - LL: 0 OF: 93 ML: 3 srcPos: 213 seqNb: 7 - LL: 1 OF: 144 ML: 5 srcPos: 219 seqNb: 8 - LL: 1 OF: 183 ML: 4 srcPos: 224 seqNb: 9 - LL: 0 OF: 140 ML: 3 srcPos: 227 seqNb: 10 - LL: 1 OF: 42 ML: 5 srcPos: 233 seqNb: 11 - LL: 0 OF: 118 ML: 4 srcPos: 237 seqNb: 12 - LL: 1 OF: 47 ML: 4 srcPos: 242 seqNb: 13 - LL: 0 OF: 232 ML: 5 srcPos: 247 seqNb: 14 - LL: 0 OF: 24 ML: 4 srcPos: 251 seqNb: 15 - LL: 1 OF: 24 ML: 5 srcPos: 257 seqNb: 16 - repeat offset: 0 - LL: 0 OF: 2 ML: 3 srcPos: 260 seqNb: 17 - LL: 0 OF: 212 ML: 4 srcPos: 264 seqNb: 18 - LL: 1 OF: 66 ML: 4 srcPos: 269 seqNb: 19 - LL: 0 OF: 65 ML: 5 srcPos: 274 seqNb: 20 - repeat offset: 2 - LL: 0 OF: 212 ML: 5 srcPos: 279 seqNb: 21 - repeat offset: 2 - LL: 0 OF: 119 ML: 4 srcPos: 283 seqNb: 22 - LL: 1 OF: 245 ML: 4 srcPos: 288 seqNb: 23 - LL: 0 OF: 152 ML: 5 srcPos: 293 seqNb: 24 - LL: 0 OF: 219 ML: 3 srcPos: 296 seqNb: 25 - LL: 0 OF: 107 ML: 5 srcPos: 301 seqNb: 26 - LL: 0 OF: 157 ML: 4 srcPos: 305 seqNb: 27 - LL: 1 OF: 30 ML: 6 srcPos: 312 seqNb: 28 - LL: 1 OF: 44 ML: 5 srcPos: 318 seqNb: 29 - LL: 0 OF: 151 ML: 4 srcPos: 322 seqNb: 30 - LL: 1 OF: 30 ML: 4 srcPos: 327 seqNb: 31 - repeat offset: 2 - LL: 1 OF: 8 ML: 4 srcPos: 332 seqNb: 32 - LL: 1 OF: 238 ML: 4 srcPos: 337 seqNb: 33 - LL: 6 OF: 244 ML: 5 srcPos: 348 seqNb: 34 - LL: 0 OF: 290 ML: 3 srcPos: 351 seqNb: 35 - LL: 1 OF: 271 ML: 3 srcPos: 355 seqNb: 36 - LL: 0 OF: 111 ML: 4 srcPos: 359 seqNb: 37 - excess literals: 0 srcPos: 359 - LL type: 0 OF type: 2 ML type: 2 - number of sequences: 38 - block type: compressed - block size field: 80 - block: - block content size: 419 - last block: no - compressed block: - rle literals: 0xc1 - literals size: 282 - total match lengths: 137 - LL: 0 OF: 353 ML: 5 srcPos: 364 seqNb: 0 - LL: 1 OF: 111 ML: 3 srcPos: 368 seqNb: 1 - repeat offset: 1 - LL: 18 OF: 65 ML: 4 srcPos: 390 seqNb: 2 - LL: 3 OF: 213 ML: 6 srcPos: 399 seqNb: 3 - LL: 0 OF: 249 ML: 6 srcPos: 405 seqNb: 4 - LL: 11 OF: 285 ML: 11 srcPos: 427 seqNb: 5 - LL: 4 OF: 297 ML: 5 srcPos: 436 seqNb: 6 - LL: 4 OF: 61 ML: 5 srcPos: 445 seqNb: 7 - LL: 11 OF: 204 ML: 4 srcPos: 460 seqNb: 8 - LL: 7 OF: 394 ML: 3 srcPos: 470 seqNb: 9 - LL: 7 OF: 30 ML: 5 srcPos: 482 seqNb: 10 - LL: 4 OF: 346 ML: 6 srcPos: 492 seqNb: 11 - LL: 1 OF: 394 ML: 3 srcPos: 496 seqNb: 12 - repeat offset: 2 - LL: 53 OF: 501 ML: 5 srcPos: 554 seqNb: 13 - LL: 15 OF: 116 ML: 3 srcPos: 572 seqNb: 14 - LL: 5 OF: 116 ML: 3 srcPos: 580 seqNb: 15 - repeat offset: 0 - LL: 4 OF: 80 ML: 5 srcPos: 589 seqNb: 16 - LL: 32 OF: 434 ML: 3 srcPos: 624 seqNb: 17 - LL: 6 OF: 80 ML: 4 srcPos: 634 seqNb: 18 - repeat offset: 1 - LL: 3 OF: 387 ML: 4 srcPos: 641 seqNb: 19 - LL: 0 OF: 639 ML: 6 srcPos: 647 seqNb: 20 - LL: 4 OF: 319 ML: 7 srcPos: 658 seqNb: 21 - LL: 0 OF: 639 ML: 4 srcPos: 662 seqNb: 22 - repeat offset: 1 - LL: 5 OF: 387 ML: 3 srcPos: 670 seqNb: 23 - repeat offset: 2 - LL: 0 OF: 639 ML: 4 srcPos: 674 seqNb: 24 - repeat offset: 1 - LL: 5 OF: 166 ML: 3 srcPos: 682 seqNb: 25 - LL: 0 OF: 135 ML: 3 srcPos: 685 seqNb: 26 - LL: 60 OF: 91 ML: 5 srcPos: 750 seqNb: 27 - LL: 1 OF: 141 ML: 4 srcPos: 755 seqNb: 28 - LL: 0 OF: 667 ML: 5 srcPos: 760 seqNb: 29 - excess literals: 18 srcPos: 778 - LL type: 2 OF type: 0 ML type: 2 - number of sequences: 30 - block type: compressed - block size field: 80 - block: - block content size: 7 - last block: no - compressed block: - rle literals: 0x3a - literals size: 1 - total match lengths: 6 - LL: 1 OF: 282 ML: 6 srcPos: 785 seqNb: 0 - excess literals: 0 srcPos: 785 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 9 - block: - block content size: 129 - last block: no - compressed block: - rle literals: 0xcf - literals size: 60 - total match lengths: 69 - LL: 0 OF: 455 ML: 69 srcPos: 854 seqNb: 0 - excess literals: 60 srcPos: 914 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 10 - block: - block content size: 85 - last block: no - compressed block: - rle literals: 0xf1 - literals size: 76 - total match lengths: 9 - LL: 44 OF: 616 ML: 4 srcPos: 962 seqNb: 0 - LL: 11 OF: 455 ML: 5 srcPos: 978 seqNb: 1 - repeat offset: 1 - excess literals: 21 srcPos: 999 - LL type: 2 OF type: 2 ML type: 2 - number of sequences: 2 - block type: compressed - block size field: 29 - block: - block content size: 20 - last block: yes - compressed block: - rle literals: 0xf5 - literals size: 11 - total match lengths: 9 - LL: 1 OF: 118 ML: 3 srcPos: 1003 seqNb: 0 - LL: 2 OF: 709 ML: 3 srcPos: 1008 seqNb: 1 - LL: 8 OF: 168 ML: 3 srcPos: 1019 seqNb: 2 - excess literals: 0 srcPos: 1019 - LL type: 2 OF type: 2 ML type: 1 - number of sequences: 3 - block type: compressed - block size field: 24 - checksum: 043d84eb diff --git a/xls/modules/zstd/data/pregenerated_compressed_rle_1.zst b/xls/modules/zstd/data/pregenerated_compressed_rle_1.zst deleted file mode 100644 index 201bc5bc338c7f9b1affd11beee54a2c72b1bbe3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 281 zcmV+!0p|WFwJ-g|FZ%-k0000008{_~wFvD2R2l{&i)4fX0I9|%C?Lofsp|oNst>XR z64{kB+*fga3)Z3;@EkA5AZ>2KtFR&9fBFa)EEKpaA({ZZ#aI)_z1*X7fQ~&_Q1J40e00=0e9P9u9#RTyJs1)EYV8Gvh6F^|#`3Dq$fd2u+H2@d_1jPUV fS@i>?6o6K%0{0ISuvbx4Z7Eo){E diff --git a/xls/modules/zstd/data/pregenerated_compressed_rle_2.log b/xls/modules/zstd/data/pregenerated_compressed_rle_2.log deleted file mode 100644 index ed2a1dbb00..0000000000 --- a/xls/modules/zstd/data/pregenerated_compressed_rle_2.log +++ /dev/null @@ -1,103 +0,0 @@ -seed: 2 -frame seed: 2 - frame content size: 152 - frame window size: 114688 - content size flag: 1 - single segment flag: 0 - block: - block content size: 91 - last block: no - compressed block: - rle literals: 0xda - literals size: 68 - total match lengths: 23 - LL: 2 OF: 1 ML: 3 srcPos: 5 seqNb: 0 - LL: 8 OF: 2 ML: 3 srcPos: 16 seqNb: 1 - LL: 5 OF: 10 ML: 4 srcPos: 25 seqNb: 2 - LL: 8 OF: 10 ML: 3 srcPos: 36 seqNb: 3 - repeat offset: 0 - LL: 4 OF: 7 ML: 3 srcPos: 43 seqNb: 4 - LL: 38 OF: 27 ML: 4 srcPos: 85 seqNb: 5 - LL: 2 OF: 68 ML: 3 srcPos: 90 seqNb: 6 - excess literals: 1 srcPos: 91 - LL type: 2 OF type: 0 ML type: 2 - number of sequences: 7 - block type: compressed - block size field: 26 - block: - block content size: 0 - last block: no - compressed block: - rle literals: 0x3c - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 91 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 61 - last block: yes - compressed block: - rle literals: 0xe1 - literals size: 56 - total match lengths: 5 - LL: 0 OF: 26 ML: 5 srcPos: 96 seqNb: 0 - excess literals: 56 srcPos: 152 - LL type: 1 OF type: 3 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 9 - checksum: 57673f5c - -seed: 2 -frame seed: 2 - frame content size: 152 - frame window size: 114688 - content size flag: 1 - single segment flag: 0 - block: - block content size: 91 - last block: no - compressed block: - rle literals: 0xda - literals size: 68 - total match lengths: 23 - LL: 2 OF: 1 ML: 3 srcPos: 5 seqNb: 0 - LL: 8 OF: 2 ML: 3 srcPos: 16 seqNb: 1 - LL: 5 OF: 10 ML: 4 srcPos: 25 seqNb: 2 - LL: 8 OF: 10 ML: 3 srcPos: 36 seqNb: 3 - repeat offset: 0 - LL: 4 OF: 7 ML: 3 srcPos: 43 seqNb: 4 - LL: 38 OF: 27 ML: 4 srcPos: 85 seqNb: 5 - LL: 2 OF: 68 ML: 3 srcPos: 90 seqNb: 6 - excess literals: 1 srcPos: 91 - LL type: 2 OF type: 0 ML type: 2 - number of sequences: 7 - block type: compressed - block size field: 26 - block: - block content size: 0 - last block: no - compressed block: - rle literals: 0x3c - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 91 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 61 - last block: yes - compressed block: - rle literals: 0xe1 - literals size: 56 - total match lengths: 5 - LL: 0 OF: 26 ML: 5 srcPos: 96 seqNb: 0 - excess literals: 56 srcPos: 152 - LL type: 1 OF type: 3 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 9 - checksum: 57673f5c diff --git a/xls/modules/zstd/data/pregenerated_compressed_rle_2.zst b/xls/modules/zstd/data/pregenerated_compressed_rle_2.zst deleted file mode 100644 index eb167ff0fe273078b167b6f957a61f8799dfdd96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61 zcmdPcs{gk|e+B~s!xaVwSC(7s9Rfy;8&^D?&s-qy-cUPaq7IAX}0Nr&ASO5S3 diff --git a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.log b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.log deleted file mode 100644 index a785b43a08..0000000000 --- a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.log +++ /dev/null @@ -1,25 +0,0 @@ -seed: 422473 -frame seed: 422473 - frame content size: 25 - frame window size: 5632 - content size flag: 1 - single segment flag: 0 - block: - block content size: 25 - last block: yes - compressed block: - raw literals - literals size: 11 - total match lengths: 14 - LL: 1 OF: 1 ML: 3 srcPos: 4 seqNb: 0 - LL: 0 OF: 3 ML: 4 srcPos: 8 seqNb: 1 - LL: 10 OF: 3 ML: 4 srcPos: 22 seqNb: 2 - repeat offset: 0 - LL: 0 OF: 1 ML: 3 srcPos: 25 seqNb: 3 - repeat offset: 2 - excess literals: 0 srcPos: 25 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 4 - block type: compressed - block size field: 23 - checksum: 335a35eb diff --git a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.zst b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_422473.zst deleted file mode 100644 index 07ac1493f58207b329213e3034f212774fa3a689..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40 wcmdPcs{gk|SdxK(VJ`ziM1|SyHl6ksf765cEDU-K0xP%~4#YCPHjOd{00iRQU(Tx?_c=7UlTpn&%$8bUcj#XO+rT6+Pi_@% diff --git a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.log b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.log deleted file mode 100644 index 672a98a312..0000000000 --- a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.log +++ /dev/null @@ -1,21 +0,0 @@ -seed: 462302 -frame seed: 462302 - frame content size: 27 - frame window size: 11264 - content size flag: 1 - single segment flag: 0 - block: - block content size: 27 - last block: yes - compressed block: - raw literals - literals size: 6 - total match lengths: 21 - LL: 1 OF: 1 ML: 18 srcPos: 19 seqNb: 0 - LL: 5 OF: 16 ML: 3 srcPos: 27 seqNb: 1 - excess literals: 0 srcPos: 27 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 2 - block type: compressed - block size field: 14 - checksum: 8eb0243a diff --git a/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.zst b/xls/modules/zstd/data/raw_literals_predefined_sequences_seed_462302.zst deleted file mode 100644 index 1f38ecd40da94b5837533ca2424f9f15528ab8df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31 mcmdPcs{gk|TAG1@p_GBapd~6~ZcsZDgE8aL;v6fL4SfKSG6{zO diff --git a/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.log b/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.log deleted file mode 100644 index 769fcf02cd..0000000000 --- a/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.log +++ /dev/null @@ -1,20 +0,0 @@ -seed: 700216 -frame seed: 700216 - frame content size: 21 - frame window size: 2097152 - content size flag: 1 - single segment flag: 0 - block: - block content size: 21 - last block: yes - compressed block: - raw literals - literals size: 18 - total match lengths: 3 - LL: 18 OF: 1 ML: 3 srcPos: 21 seqNb: 0 - excess literals: 0 srcPos: 21 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 25 - checksum: 46d2ac93 diff --git a/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.zst b/xls/modules/zstd/data/raw_literals_rle_sequences_seed_700216.zst deleted file mode 100644 index 4a76e5956950c908bed93349dd8752d713db5c49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46 ycmdPcs{i+hz9<6(oMm8`Q2#~IR#~s&Qp!c2KMrZ9k6d(N3=w2v;F!GTk{bYJxDaUo diff --git a/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.log b/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.log deleted file mode 100644 index ddd13d1a38..0000000000 --- a/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.log +++ /dev/null @@ -1,87 +0,0 @@ -seed: 701326 -frame seed: 701326 - frame content size: 28 - frame window size: 11264 - content size flag: 1 - single segment flag: 0 - block: - block content size: 1 - last block: no - compressed block: - rle literals: 0xf3 - literals size: 1 - total match lengths: 0 - excess literals: 1 srcPos: 1 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 3 - last block: no - compressed block: - rle literals: 0x08 - literals size: 3 - total match lengths: 0 - excess literals: 3 srcPos: 4 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 18 - last block: no - compressed block: - rle literals: 0x62 - literals size: 7 - total match lengths: 11 - LL: 0 OF: 1 ML: 3 srcPos: 7 seqNb: 0 - LL: 4 OF: 11 ML: 8 srcPos: 19 seqNb: 1 - excess literals: 3 srcPos: 22 - LL type: 2 OF type: 2 ML type: 2 - number of sequences: 2 - block type: compressed - block size field: 22 - block: - block content size: 2 - last block: no - compressed block: - rle literals: 0x90 - literals size: 2 - total match lengths: 0 - excess literals: 2 srcPos: 24 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 0 - last block: no - compressed block: - rle literals: 0xfd - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 24 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 1 - last block: no - compressed block: - rle literals: 0xab - literals size: 1 - total match lengths: 0 - excess literals: 1 srcPos: 25 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 3 - last block: yes - compressed block: - rle literals: 0x2d - literals size: 3 - total match lengths: 0 - excess literals: 3 srcPos: 28 - number of sequences: 0 - block type: compressed - block size field: 3 - checksum: 08dc0268 diff --git a/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.zst b/xls/modules/zstd/data/rle_literals_compressed_sequences_seed_701326.zst deleted file mode 100644 index 23f21c7ce7ad473eba27d42293af286ab54c0485..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75 zcmdPcs{gk|T84pv0f;$2gGfmZhAj*XmPt%2L<|o67Y=w}FJh4JkJ*fYhk*qsEjR(B Ui19C2!)gXupaxxr45m9A0E&kX+W-In diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.log b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.log deleted file mode 100644 index ac01d5ea38..0000000000 --- a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.log +++ /dev/null @@ -1,37 +0,0 @@ -seed: 406229 -frame seed: 406229 - frame content size: 83 - frame window size: 57344 - content size flag: 1 - single segment flag: 0 - block: - block content size: 76 - last block: no - compressed block: - rle literals: 0xba - literals size: 49 - total match lengths: 27 - LL: 1 OF: 1 ML: 6 srcPos: 7 seqNb: 0 - LL: 14 OF: 16 ML: 4 srcPos: 25 seqNb: 1 - LL: 15 OF: 27 ML: 4 srcPos: 44 seqNb: 2 - LL: 5 OF: 16 ML: 4 srcPos: 53 seqNb: 3 - repeat offset: 1 - LL: 14 OF: 23 ML: 6 srcPos: 73 seqNb: 4 - LL: 0 OF: 20 ML: 3 srcPos: 76 seqNb: 5 - excess literals: 0 srcPos: 76 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 6 - block type: compressed - block size field: 19 - block: - block content size: 7 - last block: yes - compressed block: - rle literals: 0x27 - literals size: 7 - total match lengths: 0 - excess literals: 7 srcPos: 83 - number of sequences: 0 - block type: compressed - block size field: 3 - checksum: e20e662f diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.zst b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_406229.zst deleted file mode 100644 index 5886d2796cec029257ee5564a2c1505a295d5ec8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42 ycmdPcs{gk|FPMRWVGaX>DDy5h26M&*#Z#nbonMj8mRb-l%fMi%&Y+*h_Xq$A77Qr> diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.log b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.log deleted file mode 100644 index 8bc1fedfa2..0000000000 --- a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.log +++ /dev/null @@ -1,28 +0,0 @@ -seed: 411034 -frame seed: 411034 - frame content size: 31 - frame window size: 31 - content size flag: 1 - single segment flag: 1 - block: - block content size: 31 - last block: yes - compressed block: - rle literals: 0x25 - literals size: 1 - total match lengths: 30 - LL: 1 OF: 1 ML: 8 srcPos: 9 seqNb: 0 - LL: 0 OF: 1 ML: 3 srcPos: 12 seqNb: 1 - LL: 0 OF: 5 ML: 6 srcPos: 18 seqNb: 2 - LL: 0 OF: 10 ML: 3 srcPos: 21 seqNb: 3 - LL: 0 OF: 5 ML: 4 srcPos: 25 seqNb: 4 - repeat offset: 1 - LL: 0 OF: 10 ML: 3 srcPos: 28 seqNb: 5 - repeat offset: 1 - LL: 0 OF: 1 ML: 3 srcPos: 31 seqNb: 6 - excess literals: 0 srcPos: 31 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 7 - block type: compressed - block size field: 19 - checksum: 1cc9a33c diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.zst b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_411034.zst deleted file mode 100644 index 08d368a59b26c379cd54fe05b890118dd0956afe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35 ocmdPcs{eP1JOcy6Tm}YCRdxmoAd_POE5j)uOW+-s&Ek_X0Fqb;QUCw| diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.log b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.log deleted file mode 100644 index 562e10ee40..0000000000 --- a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.log +++ /dev/null @@ -1,53 +0,0 @@ -seed: 413015 -frame seed: 413015 - frame content size: 86 - frame window size: 86 - content size flag: 1 - single segment flag: 1 - block: - block content size: 48 - last block: no - compressed block: - rle literals: 0x89 - literals size: 35 - total match lengths: 13 - LL: 35 OF: 22 ML: 10 srcPos: 45 seqNb: 0 - LL: 0 OF: 37 ML: 3 srcPos: 48 seqNb: 1 - excess literals: 0 srcPos: 48 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 2 - block type: compressed - block size field: 11 - block: - block content size: 0 - last block: no - compressed block: - rle literals: 0xa8 - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 48 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 38 - last block: yes - compressed block: - rle literals: 0x25 - literals size: 12 - total match lengths: 26 - LL: 0 OF: 24 ML: 3 srcPos: 51 seqNb: 0 - LL: 1 OF: 40 ML: 3 srcPos: 55 seqNb: 1 - LL: 11 OF: 37 ML: 4 srcPos: 70 seqNb: 2 - LL: 0 OF: 5 ML: 3 srcPos: 73 seqNb: 3 - LL: 0 OF: 40 ML: 3 srcPos: 76 seqNb: 4 - LL: 0 OF: 70 ML: 3 srcPos: 79 seqNb: 5 - LL: 0 OF: 74 ML: 3 srcPos: 82 seqNb: 6 - LL: 0 OF: 40 ML: 4 srcPos: 86 seqNb: 7 - repeat offset: 2 - excess literals: 0 srcPos: 86 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 8 - block type: compressed - block size field: 24 - checksum: 9f8a30e7 diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.zst b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_413015.zst deleted file mode 100644 index 209ef6617839bac20f9102a4ec76721e9caeed48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64 zcmdPcs{i*%7y|^vFff=hbuuw{FmAZ>Ojw42fpG=HQ3i%YRSpKd1~x_y0|sx#1}27; Q2c|PsG-k3sH|UxV0Q)Qu>;M1& diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.log b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.log deleted file mode 100644 index adcc599156..0000000000 --- a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.log +++ /dev/null @@ -1,34 +0,0 @@ -seed: 436165 -frame seed: 436165 - frame content size: 90 - frame window size: 589824 - content size flag: 1 - single segment flag: 0 - block: - block content size: 1 - last block: no - compressed block: - rle literals: 0x88 - literals size: 1 - total match lengths: 0 - excess literals: 1 srcPos: 1 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 89 - last block: yes - compressed block: - rle literals: 0x65 - literals size: 51 - total match lengths: 38 - LL: 4 OF: 1 ML: 5 srcPos: 10 seqNb: 0 - LL: 23 OF: 3 ML: 5 srcPos: 38 seqNb: 1 - LL: 2 OF: 4 ML: 6 srcPos: 46 seqNb: 2 - LL: 9 OF: 33 ML: 22 srcPos: 77 seqNb: 3 - excess literals: 13 srcPos: 90 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 4 - block type: compressed - block size field: 15 - checksum: 391cc8a2 diff --git a/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.zst b/xls/modules/zstd/data/rle_literals_predefined_sequences_seed_436165.zst deleted file mode 100644 index f4020c675357d88fa180e736cea247a03a39e406..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38 ucmdPcs{gk|KZ=2YL56{WvxA|Qfx(nHm4(4&cZvF;>AzjJge0Zvo`i BI_dxb diff --git a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.log b/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.log deleted file mode 100644 index d80c261dc5..0000000000 --- a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.log +++ /dev/null @@ -1,111 +0,0 @@ -seed: 408158 -frame seed: 408158 - frame content size: 44 - frame window size: 90112 - content size flag: 1 - single segment flag: 0 - block: - block content size: 43 - last block: no - compressed block: - raw literals - literals size: 15 - total match lengths: 28 - LL: 7 OF: 3 ML: 9 srcPos: 16 seqNb: 0 - LL: 0 OF: 2 ML: 4 srcPos: 20 seqNb: 1 - LL: 0 OF: 12 ML: 3 srcPos: 23 seqNb: 2 - LL: 8 OF: 6 ML: 12 srcPos: 43 seqNb: 3 - excess literals: 0 srcPos: 43 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 4 - block type: compressed - block size field: 27 - block: - block content size: 1 - last block: no - compressed block: - raw literals - literals size: 1 - total match lengths: 0 - excess literals: 1 srcPos: 44 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 0 - last block: no - compressed block: - raw literals - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 44 - number of sequences: 0 - block type: compressed - block size field: 2 - block: - block content size: 0 - last block: no - compressed block: - raw literals - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 44 - number of sequences: 0 - block type: compressed - block size field: 2 - block: - block content size: 0 - last block: no - compressed block: - raw literals - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 44 - number of sequences: 0 - block type: compressed - block size field: 2 - block: - block content size: 0 - last block: no - compressed block: - rle literals: 0x57 - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 44 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 0 - last block: no - compressed block: - rle literals: 0x02 - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 44 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 0 - last block: no - compressed block: - rle literals: 0x96 - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 44 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 0 - last block: yes - compressed block: - rle literals: 0x40 - literals size: 0 - total match lengths: 0 - excess literals: 0 srcPos: 44 - number of sequences: 0 - block type: compressed - block size field: 3 - checksum: 975ba214 diff --git a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.zst b/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_408158.zst deleted file mode 100644 index e70a6a3c279df92c406a179431ac188930656caa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93 zcmdPcs{i+hz77Kf++kp-Xirg?ntU=@rtg*OXZ8v2Sr{~B_B%Xb+ECyo!@$6CkwFBc b6hQz58N(SsBommN#vlu1J1~eWik=PtkBk%# diff --git a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.log b/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.log deleted file mode 100644 index 5528ebd698..0000000000 --- a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.log +++ /dev/null @@ -1,44 +0,0 @@ -seed: 499212 -frame seed: 499212 - frame content size: 39 - frame window size: 2304 - content size flag: 1 - single segment flag: 0 - block: - block content size: 35 - last block: no - compressed block: - raw literals - literals size: 17 - total match lengths: 18 - LL: 1 OF: 1 ML: 5 srcPos: 6 seqNb: 0 - LL: 2 OF: 6 ML: 5 srcPos: 13 seqNb: 1 - LL: 0 OF: 3 ML: 8 srcPos: 21 seqNb: 2 - excess literals: 14 srcPos: 35 - LL type: 0 OF type: 0 ML type: 0 - number of sequences: 3 - block type: compressed - block size field: 27 - block: - block content size: 3 - last block: no - compressed block: - rle literals: 0xe4 - literals size: 3 - total match lengths: 0 - excess literals: 3 srcPos: 38 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 1 - last block: yes - compressed block: - raw literals - literals size: 1 - total match lengths: 0 - excess literals: 1 srcPos: 39 - number of sequences: 0 - block type: compressed - block size field: 3 - checksum: fea5e563 diff --git a/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.zst b/xls/modules/zstd/data/rle_raw_literals_predefined_sequences_seed_499212.zst deleted file mode 100644 index ac03f4a85df5a1610349e59922bf412875a73838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60 zcmdPcs{i*0r#b@!++kqoa4FW7YvKBF=CZ@p(`pKyD`J@$te7<0xAgJKFfd3yVUT5D M;OJ*ae!BD@04?ki@c;k- diff --git a/xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.log b/xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.log deleted file mode 100644 index c47dd6d2d3..0000000000 --- a/xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.log +++ /dev/null @@ -1,80 +0,0 @@ -seed: 400077 -frame seed: 400077 - frame content size: 233 - frame window size: 26624 - content size flag: 1 - single segment flag: 0 - block: - block content size: 109 - last block: no - compressed block: - compressed literals - distribution weight: 56% - huffman log: 11 - regenerated size: 44 - compressed size: 44 - trying again - distribution weight: 16% - huffman log: 11 - regenerated size: 40 - compressed size: 72 - trying again - distribution weight: 87% - huffman log: 10 - regenerated size: 32 - compressed size: 27 - literals size: 32 - total match lengths: 77 - LL: 3 OF: 3 ML: 12 srcPos: 15 seqNb: 0 - LL: 3 OF: 15 ML: 37 srcPos: 55 seqNb: 1 - LL: 19 OF: 67 ML: 4 srcPos: 78 seqNb: 2 - LL: 7 OF: 23 ML: 4 srcPos: 89 seqNb: 3 - LL: 0 OF: 21 ML: 20 srcPos: 109 seqNb: 4 - excess literals: 0 srcPos: 109 - LL type: 2 OF type: 2 ML type: 2 - number of sequences: 5 - block type: compressed - block size field: 60 - block: - block content size: 16 - last block: no - compressed block: - rle literals: 0x31 - literals size: 16 - total match lengths: 0 - excess literals: 16 srcPos: 125 - number of sequences: 0 - block type: compressed - block size field: 3 - block: - block content size: 100 - last block: no - compressed block: - compressed literals - huffman repeat stats - regenerated size: 63 - compressed size: 18 - literals size: 63 - total match lengths: 37 - LL: 4 OF: 127 ML: 3 srcPos: 132 seqNb: 0 - LL: 11 OF: 36 ML: 5 srcPos: 148 seqNb: 1 - LL: 48 OF: 30 ML: 11 srcPos: 207 seqNb: 2 - LL: 0 OF: 1 ML: 15 srcPos: 222 seqNb: 3 - LL: 0 OF: 11 ML: 3 srcPos: 225 seqNb: 4 - excess literals: 0 srcPos: 225 - LL type: 2 OF type: 2 ML type: 2 - number of sequences: 5 - block type: compressed - block size field: 50 - block: - block content size: 8 - last block: yes - compressed block: - rle literals: 0xd8 - literals size: 8 - total match lengths: 0 - excess literals: 8 srcPos: 233 - number of sequences: 0 - block type: compressed - block size field: 3 - checksum: 9e8dd98a diff --git a/xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.zst b/xls/modules/zstd/data/treeless_huffman_literals_compressed_sequences_seed_400077.zst deleted file mode 100644 index 534fd3079c584ed6a4cc575746fba8fe1b191708..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142 zcmdPcs{gk|^(6xX!xKgZE~Xp?o(oHv4Hz33EEok$Ihhzh@IT{A{{M{s8Ch2pEE3m$ z&nVz^ke6Se;$zIb)BD*qtfc0>=t^LcVPI%9WSGLp@SE9#ff)?ne`o*ypYiW+zWX4( q6Ap>Y{#h$vrC#xHLX*h!kBoc_0_QezI~g&`GB7yaVCcHpI}ZR{058%2 diff --git a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.log b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.log deleted file mode 100644 index 2e7d1110a7..0000000000 --- a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.log +++ /dev/null @@ -1,46 +0,0 @@ -seed: 400025 -frame seed: 400025 - frame content size: 142 - frame window size: 40960 - content size flag: 1 - single segment flag: 0 - block: - block content size: 78 - last block: no - compressed block: - compressed literals - small range literals - huffman log: 4 - regenerated size: 66 - compressed size: 50 - literals size: 66 - total match lengths: 12 - LL: 66 OF: 45 ML: 12 srcPos: 78 seqNb: 0 - excess literals: 0 srcPos: 78 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 60 - block: - block content size: 64 - last block: yes - compressed block: - compressed literals - huffman repeat stats - regenerated size: 43 - compressed size: 30 - literals size: 43 - total match lengths: 21 - LL: 12 OF: 17 ML: 3 srcPos: 93 seqNb: 0 - LL: 0 OF: 65 ML: 5 srcPos: 98 seqNb: 1 - LL: 23 OF: 7 ML: 3 srcPos: 124 seqNb: 2 - LL: 4 OF: 65 ML: 4 srcPos: 132 seqNb: 3 - repeat offset: 1 - LL: 0 OF: 69 ML: 3 srcPos: 135 seqNb: 4 - LL: 1 OF: 75 ML: 3 srcPos: 139 seqNb: 5 - excess literals: 3 srcPos: 142 - LL type: 2 OF type: 2 ML type: 0 - number of sequences: 6 - block type: compressed - block size field: 60 - checksum: 29af0a62 diff --git a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst deleted file mode 100644 index 884f540c77e2e215f227606c3a7c765a36e17064..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140 zcmdPcs{gk|tB-+!;Rz#yS_@B~AOr|-GJxRI1Jl!v9SCQT3|0SaF)!<*wzSPpf%m${ zRwM`y diff --git a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.log b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.log deleted file mode 100644 index 6f5e6bf708..0000000000 --- a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.log +++ /dev/null @@ -1,81 +0,0 @@ -seed: 400061 -frame seed: 400061 - frame content size: 241 - frame window size: 2048 - content size flag: 1 - single segment flag: 0 - block: - block content size: 60 - last block: no - compressed block: - raw literals - literals size: 60 - total match lengths: 0 - excess literals: 60 srcPos: 60 - number of sequences: 0 - block type: compressed - block size field: 63 - block: - block content size: 91 - last block: no - compressed block: - compressed literals - small range literals - huffman log: 10 - regenerated size: 44 - compressed size: 39 - literals size: 44 - total match lengths: 47 - LL: 1 OF: 40 ML: 3 srcPos: 64 seqNb: 0 - LL: 0 OF: 12 ML: 3 srcPos: 67 seqNb: 1 - LL: 2 OF: 13 ML: 3 srcPos: 72 seqNb: 2 - LL: 0 OF: 70 ML: 3 srcPos: 75 seqNb: 3 - LL: 8 OF: 71 ML: 3 srcPos: 86 seqNb: 4 - LL: 17 OF: 17 ML: 3 srcPos: 106 seqNb: 5 - LL: 0 OF: 39 ML: 3 srcPos: 109 seqNb: 6 - LL: 0 OF: 56 ML: 3 srcPos: 112 seqNb: 7 - LL: 1 OF: 78 ML: 5 srcPos: 118 seqNb: 8 - LL: 1 OF: 48 ML: 3 srcPos: 122 seqNb: 9 - LL: 0 OF: 122 ML: 3 srcPos: 125 seqNb: 10 - LL: 1 OF: 1 ML: 3 srcPos: 129 seqNb: 11 - LL: 7 OF: 31 ML: 4 srcPos: 140 seqNb: 12 - LL: 3 OF: 19 ML: 5 srcPos: 148 seqNb: 13 - excess literals: 3 srcPos: 151 - LL type: 2 OF type: 0 ML type: 2 - number of sequences: 14 - block type: compressed - block size field: 74 - block: - block content size: 11 - last block: no - compressed block: - rle literals: 0x9c - literals size: 8 - total match lengths: 3 - LL: 4 OF: 1 ML: 3 srcPos: 158 seqNb: 0 - repeat offset: 2 - excess literals: 4 srcPos: 162 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 8 - block: - block content size: 79 - last block: yes - compressed block: - compressed literals - huffman repeat stats - regenerated size: 57 - compressed size: 37 - literals size: 57 - total match lengths: 22 - LL: 22 OF: 18 ML: 3 srcPos: 187 seqNb: 0 - LL: 7 OF: 168 ML: 5 srcPos: 199 seqNb: 1 - LL: 28 OF: 48 ML: 5 srcPos: 232 seqNb: 2 - LL: 0 OF: 176 ML: 9 srcPos: 241 seqNb: 3 - excess literals: 0 srcPos: 241 - LL type: 2 OF type: 0 ML type: 2 - number of sequences: 4 - block type: compressed - block size field: 69 - checksum: 1d05f549 diff --git a/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst b/xls/modules/zstd/data/treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst deleted file mode 100644 index b0fe4849b33e414c3f433a607ba487e6d9d3ac45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 244 zcmdPcs{i*0$43ST_`}F>g!xjNTiEi28&i!Jzum35@8Qy_?Cp09l1{rE`u}(4%Uj!S zZE;c#b$fVoj%U<YWcpoa_xNa^A+Eeg1;EnQ$xD{r2Ve0yl(u1xf{eU}5)(k8r)} z!oc7-hcSeOk%3v4iD9}kCkF!vF8m&;v7ckT)PjG7UK?B{$xXI-7P5ElKJma!6AC6S oXJqLR(MVus{lm)kgIz?UVL#)4Mq}n>X{kZHGgz5Czp}~#0A!C~PXGV_ diff --git a/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.log b/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.log deleted file mode 100644 index 9962123a00..0000000000 --- a/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.log +++ /dev/null @@ -1,53 +0,0 @@ -seed: 403927 -frame seed: 403927 - frame content size: 224 - frame window size: 229376 - content size flag: 1 - single segment flag: 0 - block: - block content size: 100 - last block: no - compressed block: - compressed literals - distribution weight: 46% - huffman log: 8 - regenerated size: 67 - compressed size: 56 - literals size: 67 - total match lengths: 33 - LL: 67 OF: 46 ML: 33 srcPos: 100 seqNb: 0 - excess literals: 0 srcPos: 100 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 68 - block: - block content size: 23 - last block: no - compressed block: - rle literals: 0x46 - literals size: 14 - total match lengths: 9 - LL: 14 OF: 27 ML: 9 srcPos: 123 seqNb: 0 - excess literals: 0 srcPos: 123 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 8 - block: - block content size: 101 - last block: yes - compressed block: - compressed literals - huffman repeat stats - regenerated size: 96 - compressed size: 29 - literals size: 96 - total match lengths: 5 - LL: 0 OF: 4 ML: 5 srcPos: 128 seqNb: 0 - excess literals: 96 srcPos: 224 - LL type: 1 OF type: 1 ML type: 1 - number of sequences: 1 - block type: compressed - block size field: 38 - checksum: d06e892b diff --git a/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.zst b/xls/modules/zstd/data/treeless_huffman_literals_rle_sequences_seed_403927.zst deleted file mode 100644 index da9ed3c95a43933b3b6cd9996590c58c67dccbb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137 zcmV;40CxW+G03-qcJ_G;`02rVpg>;~PIEbq8P;~|)RcEED@K9BF`Vj^I z2LJ{DQ+IGAWC_bi4TLIG11E0-sbK*P2Vt`%1OZeT1s+2TL;wJBMgdd~1O^^80RRI= r2UU_#uF#0^gp^g{SLC4)DJ6zfw|q|#!&$3=5; - -fn test_command - (block_idx: u32, msg_type: SequenceExecutorMessageType, length: CopyOrMatchLength, - content: CopyOrMatchContent, last: bool) -> CommandConstructorData { - CommandConstructorData { - sync: TEST_SYNC[block_idx], - data: SequenceExecutorPacket { msg_type, length, content, last }, - } -} - -const TEST_DATA_0 = TestRefillingSBOutput[48]:[ - TestRefillingSBOutput { error: false, data: u64:0b11111, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b101, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b100, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b110, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:4 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b101, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b1000, length: u7:4 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, -]; - -const TEST_DATA_1 = TestRefillingSBOutput[42]:[ - TestRefillingSBOutput { error: false, data: u64:0b10000, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b1110, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b11001, length: u7:6 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b110, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b1110, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:6 }, - TestRefillingSBOutput { error: false, data: u64:0b101, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b110, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:4 }, - TestRefillingSBOutput { error: false, data: u64:0b10011, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:4 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:2 }, - TestRefillingSBOutput { error: false, data: u64:0b1, length: u7:4 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:3 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:4 }, - TestRefillingSBOutput { error: false, data: u64:0b1010, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b1110, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:1 }, - TestRefillingSBOutput { error: false, data: u64:0b11, length: u7:6 }, - TestRefillingSBOutput { error: false, data: u64:0b10011, length: u7:5 }, - TestRefillingSBOutput { error: false, data: u64:0b10, length: u7:4 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, - TestRefillingSBOutput { error: false, data: u64:0b0, length: u7:0 }, -]; - -// FIXME: test error propagation with TestRefillingSBOutput { error: true, ...} - -const TEST_EXPECTED_COMMANDS_0 = CommandConstructorData[16]:[ - test_command( - u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:1, CopyOrMatchContent:0, - false), - test_command( - u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:1, - false), - test_command( - u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:3, CopyOrMatchContent:6, - false), - test_command( - u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:1, CopyOrMatchContent:0, - false), - test_command( - u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:8, - false), - test_command( - u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:11, - false), - test_command( - u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:16, - false), - test_command( - u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:13, - false), - test_command( - u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:6, CopyOrMatchContent:7, - false), - test_command( - u32:0, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:0, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:3, CopyOrMatchContent:24, - true), -]; - -const TEST_EXPECTED_COMMANDS_1 = CommandConstructorData[14]:[ - test_command( - u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:1, CopyOrMatchContent:0, - false), - test_command( - u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:7, CopyOrMatchContent:4, - false), - test_command( - u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:3, CopyOrMatchContent:0, - false), - test_command( - u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:3, CopyOrMatchContent:6, - false), - test_command( - u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:14, - false), - test_command( - u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:5, CopyOrMatchContent:0, - false), - test_command( - u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:19, - false), - test_command( - u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:13, CopyOrMatchContent:0, - false), - test_command( - u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:4, CopyOrMatchContent:1, - false), - test_command( - u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:3, CopyOrMatchContent:46, - false), - test_command( - u32:1, SequenceExecutorMessageType::LITERAL, CopyOrMatchLength:0, CopyOrMatchContent:0, - false), - test_command( - u32:1, SequenceExecutorMessageType::SEQUENCE, CopyOrMatchLength:6, CopyOrMatchContent:18, - true), -]; -//#[test_proc] -//proc FseDecoderTest { -// type FseRamRdReq = ram::ReadReq; -// type FseRamRdResp = ram::ReadResp; -// -// type FseRamWrReq = ram::WriteReq; -// type FseRamWrResp = ram::WriteResp; -// -// type RefillingSBCtrl = -// refilling_shift_buffer::RefillingShiftBufferCtrl; -// type RefillingSBOutput = -// refilling_shift_buffer::RefillingShiftBufferOutput; -// -// terminator: chan out; -// -// ctrl_s: chan out; -// finish_r: chan in; -// -// rsb_ctrl_r: chan in; -// rsb_data_s: chan out; -// -// command_r: chan in; -// -// ll_fse_wr_req_s: chan out; -// ll_fse_wr_resp_r: chan in; -// -// ml_fse_wr_req_s: chan out; -// ml_fse_wr_resp_r: chan in; -// -// of_fse_wr_req_s: chan out; -// of_fse_wr_resp_r: chan in; -// -// config (terminator: chan out) { -// let (ctrl_s, ctrl_r) = chan("ctrl"); -// let (finish_s, finish_r) = chan("finish"); -// -// let (rsb_ctrl_s, rsb_ctrl_r) = chan("rsb_ctrl"); -// let (rsb_data_s, rsb_data_r) = chan("rsb_out_data"); -// -// let (command_s, command_r) = chan("command"); -// -// // RAM with FSE lookup for Literal Lengths -// let (ll_fse_rd_req_s, ll_fse_rd_req_r) = chan("ll_fse_rd_req"); -// let (ll_fse_rd_resp_s, ll_fse_rd_resp_r) = chan("ll_fse_rd_resp"); -// let (ll_fse_wr_req_s, ll_fse_wr_req_r) = chan("ll_fse_wr_req"); -// let (ll_fse_wr_resp_s, ll_fse_wr_resp_r) = chan("ll_fse_wr_resp"); -// -// spawn ram::RamModel< -// TEST_RAM_DATA_W, -// TEST_RAM_SIZE, -// TEST_RAM_WORD_PARTITION_SIZE, -// >(ll_fse_rd_req_r, ll_fse_rd_resp_s, ll_fse_wr_req_r, ll_fse_wr_resp_s); -// -// // RAM with FSE lookup for Match Lengths -// let (ml_fse_rd_req_s, ml_fse_rd_req_r) = chan("ml_fse_rd_req"); -// let (ml_fse_rd_resp_s, ml_fse_rd_resp_r) = chan("ml_fse_rd_resp"); -// let (ml_fse_wr_req_s, ml_fse_wr_req_r) = chan("ml_fse_wr_req"); -// let (ml_fse_wr_resp_s, ml_fse_wr_resp_r) = chan("ml_fse_wr_resp"); -// -// spawn ram::RamModel< -// TEST_RAM_DATA_W, -// TEST_RAM_SIZE, -// TEST_RAM_WORD_PARTITION_SIZE, -// >(ml_fse_rd_req_r, ml_fse_rd_resp_s, ml_fse_wr_req_r, ml_fse_wr_resp_s); -// -// // RAM with FSE lookup for Offsets -// let (of_fse_rd_req_s, of_fse_rd_req_r) = chan("of_fse_rd_req"); -// let (of_fse_rd_resp_s, of_fse_rd_resp_r) = chan("of_fse_rd_resp"); -// let (of_fse_wr_req_s, of_fse_wr_req_r) = chan("of_fse_wr_req"); -// let (of_fse_wr_resp_s, of_fse_wr_resp_r) = chan("of_fse_wr_resp"); -// -// spawn ram::RamModel< -// TEST_RAM_DATA_W, -// TEST_RAM_SIZE, -// TEST_RAM_WORD_PARTITION_SIZE, -// >(of_fse_rd_req_r, of_fse_rd_resp_s, of_fse_wr_req_r, of_fse_wr_resp_s); -// -// spawn FseDecoder< -// TEST_RAM_DATA_W, TEST_RAM_ADDR_W, TEST_RAM_NUM_PARTITIONS, -// TEST_AXI_DATA_W, -// >( -// ctrl_r, finish_s, -// rsb_ctrl_s, rsb_data_r, -// command_s, -// ll_fse_rd_req_s, ll_fse_rd_resp_r, -// ml_fse_rd_req_s, ml_fse_rd_resp_r, -// of_fse_rd_req_s, of_fse_rd_resp_r, -// ); -// -// ( -// terminator, -// ctrl_s, finish_r, -// rsb_ctrl_r, rsb_data_s, -// command_r, -// ll_fse_wr_req_s, ll_fse_wr_resp_r, -// ml_fse_wr_req_s, ml_fse_wr_resp_r, -// of_fse_wr_req_s, of_fse_wr_resp_r, -// ) -// } -// -// init { u32:0 } -// -// next (state: u32) { -// let tok = join(); -// -// // write OF table -// let tok = for ((i, of_record), tok): ((u32, u32), token) in -// enumerate(TEST_OF_TABLE[state]) { -// let tok = send(tok, of_fse_wr_req_s, FseRamWrReq { -// addr: i as u8, -// data: of_record, -// mask: u4:0xf, -// }); -// let (tok, _) = recv(tok, of_fse_wr_resp_r); -// tok -// }(tok); -// -// // write ML table -// let tok = for ((i, ml_record), tok): ((u32, u32), token) in -// enumerate(TEST_ML_TABLE[state]) { -// let tok = send(tok, ml_fse_wr_req_s, FseRamWrReq { -// addr: i as u8, -// data: ml_record, -// mask: u4:0xf, -// }); -// let (tok, _) = recv(tok, ml_fse_wr_resp_r); -// tok -// }(tok); -// -// // write LL table -// let tok = for ((i, ll_record), tok): ((u32, u32), token) in -// enumerate(TEST_LL_TABLE[state]) { -// let tok = send(tok, ll_fse_wr_req_s, FseRamWrReq { -// addr: i as u8, -// data: ll_record, -// mask: u4:0xf, -// }); -// let (tok, _) = recv(tok, ll_fse_wr_resp_r); -// tok -// }(tok); -// -// // send ctrl -// let tok = send(tok, ctrl_s, TEST_CTRL[state]); -// trace_fmt!("Sent ctrl {:#x}", TEST_CTRL[state]); -// -// match state { -// u32:0 => { -// // block #0 -// // send data -// let tok = for ((i, data), tok): ((u32, RefillingSBOutput), token) in -// enumerate(TEST_DATA_0) { -// let (tok, buf_ctrl) = recv(tok, rsb_ctrl_r); -// trace_fmt!("Received #{} buf ctrl {:#x}", i + u32:1, buf_ctrl); -// assert_eq(RefillingSBCtrl {length: data.length}, buf_ctrl); -// let tok = send(tok, rsb_data_s, data); -// trace_fmt!("Sent #{} buf data {:#x}", i + u32:1, data); -// tok -// }(tok); -// -// // recv commands -// let tok = for ((i, expected_cmd), tok): ((u32, CommandConstructorData), token) in -// enumerate(TEST_EXPECTED_COMMANDS_0) { -// let (tok, cmd) = recv(tok, command_r); -// trace_fmt!("Received #{} cmd {:#x}", i + u32:1, cmd); -// assert_eq(expected_cmd, cmd); -// tok -// }(tok); -// -// // recv finish -// let (tok, _) = recv(tok, finish_r); -// }, -// u32:1 => { -// // block #1 -// // send data -// let tok = for ((i, data), tok): ((u32, RefillingSBOutput), token) in -// enumerate(TEST_DATA_1) { -// let (tok, buf_ctrl) = recv(tok, rsb_ctrl_r); -// trace_fmt!("Received #{} buf ctrl {:#x}", i + u32:1, buf_ctrl); -// assert_eq(RefillingSBCtrl {length: data.length}, buf_ctrl); -// let tok = send(tok, rsb_data_s, data); -// trace_fmt!("Sent #{} buf data {:#x}", i + u32:1, data); -// tok -// }(tok); -// -// // recv commands -// let tok = for ((i, expected_cmd), tok): ((u32, CommandConstructorData), token) in -// enumerate(TEST_EXPECTED_COMMANDS_1) { -// let (tok, cmd) = recv(tok, command_r); -// trace_fmt!("Received #{} cmd {:#x}", i + u32:1, cmd); -// assert_eq(expected_cmd, cmd); -// tok -// }(tok); -// -// // recv finish -// let (tok, _) = recv(tok, finish_r); -// -// send(tok, terminator, true); -// }, -// }; -// -// state + u32:1 -// } -//} diff --git a/xls/modules/zstd/fse_lookup_dec.x b/xls/modules/zstd/fse_lookup_dec.x index 49337d67c0..cc39be1768 100644 --- a/xls/modules/zstd/fse_lookup_dec.x +++ b/xls/modules/zstd/fse_lookup_dec.x @@ -21,8 +21,6 @@ import xls.modules.zstd.memory.mem_reader; import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.fse_table_creator; import xls.modules.zstd.refilling_shift_buffer; -import xls.modules.zstd.fse_proba_freq_dec; -import xls.modules.zstd.shift_buffer; import xls.modules.zstd.comp_lookup_dec; import xls.modules.zstd.rle_lookup_dec; import xls.modules.zstd.refilling_shift_buffer_mux; diff --git a/xls/modules/zstd/fse_proba_freq_dec.x b/xls/modules/zstd/fse_proba_freq_dec.x index a2919f4db5..67e791eca5 100644 --- a/xls/modules/zstd/fse_proba_freq_dec.x +++ b/xls/modules/zstd/fse_proba_freq_dec.x @@ -347,7 +347,6 @@ pub proc FseProbaFreqDecoder< let mask = (u16:1 << out_data.length) - u16:1; let data = out_data.data as u16; - assert!(data & mask == data, "data should not contain additional bits"); let value = get_adjusted_value(data, state.remainder); let (remainder, value) = if (value & lower_mask) < threshold { @@ -873,9 +872,6 @@ proc FseProbaFreqDecoderTest { tok }((tok)); - // FIXME: test error path: error propagated from ShiftBuffer and assigning more - // probability points than available - let tok = send(tok, terminator, true); } } diff --git a/xls/modules/zstd/fse_table_creator.x b/xls/modules/zstd/fse_table_creator.x index 6dcdca635f..ba7c965f9c 100644 --- a/xls/modules/zstd/fse_table_creator.x +++ b/xls/modules/zstd/fse_table_creator.x @@ -45,7 +45,6 @@ struct FseTableCreatorState { status: Status, req: bool, idx: u10, - // TODO: num_symbs is u8, possibly other fields as well num_symbs: u8, curr_symbol: u8, state_desc_for_symbol: u16, diff --git a/xls/modules/zstd/huffman_code_builder.x b/xls/modules/zstd/huffman_code_builder.x index 3eb8683508..3ce9f41b33 100644 --- a/xls/modules/zstd/huffman_code_builder.x +++ b/xls/modules/zstd/huffman_code_builder.x @@ -57,19 +57,8 @@ import xls.modules.zstd.huffman_common as hcommon; const MAX_WEIGHT = hcommon::MAX_WEIGHT; const WEIGHT_LOG = hcommon::WEIGHT_LOG; -const MAX_SYMBOL_COUNT = hcommon::MAX_SYMBOL_COUNT; - -const PARALLEL_ACCESS_WIDTH = hcommon::PARALLEL_ACCESS_WIDTH; const COUNTER_WIDTH = hcommon::COUNTER_WIDTH; -const RECV_COUNT = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH; -// recv_counter is used to iterate: -// 0...MAX_RECV -// (MAX_RECV+1)...MAX_RECV*2 -// so the counter type must be wide enough to store (MAX_RECV*2) -const RECV_COUNT_W = std::clog2((RECV_COUNT * u32:2) + u32:1); -const MAX_RECV = RECV_COUNT as uN[RECV_COUNT_W]; - type WeightPreScanMetaData = hcommon::WeightPreScanMetaData; type WeightPreScanOutput = hcommon::WeightPreScanOutput; type CodeBuilderToPreDecoderOutput = hcommon::CodeBuilderToPreDecoderOutput; @@ -82,7 +71,7 @@ enum WeightCodeBuilderFSM: u2 { GENERATE_CODES_RUN = u2:3, } -struct WeightCodeBuilderState { +struct WeightCodeBuilderState { fsm: WeightCodeBuilderFSM, recv_counter: uN[RECV_COUNT_W], loopback_counter: uN[RECV_COUNT_W], @@ -103,13 +92,17 @@ fn highbit(val: u32) -> u32 { } } -pub proc WeightCodeBuilder -// TODO: enable parametric expresion when they start working -//proc WeightCodeBuilder< -// PARALLEL_ACCESS_WIDTH: u32 = {u32:8}, -//> { -{ - type State = WeightCodeBuilderState; +pub proc WeightCodeBuilder< + MAX_SYMBOL_COUNT: u32 = {u32:256}, + PARALLEL_ACCESS_WIDTH: u32 = {u32:8}, + RECV_COUNT: u32 = {MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH}, + // recv_counter is used to iterate: + // 0...MAX_RECV + // (MAX_RECV+1)...MAX_RECV*2 + // so the counter type must be wide enough to store (MAX_RECV*2) + RECV_COUNT_W: u32 = {std::clog2((RECV_COUNT * u32:2) + u32:1)}, +> { + type State = WeightCodeBuilderState; type FSM = WeightCodeBuilderFSM; type PreScanData = WeightPreScanOutput; type DecoderOutput = CodeBuilderToDecoderOutput; @@ -137,6 +130,7 @@ pub proc WeightCodeBuilder init {zero!()} next(state: State) { + const MAX_RECV = RECV_COUNT as uN[RECV_COUNT_W]; let tok = join(); let (recv_start, recv_prescan) = match state.fsm { @@ -144,10 +138,6 @@ pub proc WeightCodeBuilder FSM::GATHER_WEIGHTS_RUN => (false, true), FSM::COMPUTE_MAX_LENGTH => (false, false), FSM::GENERATE_CODES_RUN => (false, true), - _ => { - assert!(false, "Invalid state"); - (false, false) - } }; let (_, start, start_valid) = recv_if_non_blocking(tok, start_r, recv_start, false); let (_, prescan_data, prescan_data_valid) = recv_if_non_blocking(tok, weight_r, recv_prescan, zero!()); @@ -168,10 +158,6 @@ pub proc WeightCodeBuilder let advance_state = state.recv_counter == (MAX_RECV * uN[RECV_COUNT_W]:2); (advance_state, advance_state, prescan_data_valid) }, - _ => { - assert!(false, "Invalid state"); - (false, false, false) - } }; let next_fsm_state = match(state.fsm, advance_state) { @@ -192,10 +178,6 @@ pub proc WeightCodeBuilder FSM::IDLE }, (_, false) => state.fsm, - _ => { - assert!(false, "Invalid state"); - FSM::IDLE - } }; let meta_data = prescan_data.meta_data; @@ -335,10 +317,6 @@ pub proc WeightCodeBuilder ..state } }, - _ => { - assert!(false, "Invalid state"); - zero!() - } }; let lookahead_packet = PreDecoderOutput { @@ -384,129 +362,32 @@ pub proc WeightCodeBuilder } } -//#[test_proc] -//proc WeightCodeBuilderSimpleTest{ -// type PrescanOut = WeightPreScanOutput; -// type DecoderOutput = CodeBuilderToDecoderOutput; -// type PreDecoderOutput = CodeBuilderToPreDecoderOutput; -// -// terminator: chan out; -//// external_ram_req: chan out; -//// external_ram_resp: chan in; -//// start_prescan: chan out; -//// prescan_response: chan in; -// init{()} -//// config (terminator: chan out) { -//// // Emulate external memory -//// let (RAMExternalWriteReq_s, RAMExternalWriteReq_r) = chan("Write_channel_req"); -//// let (RAMExternalWriteResp_s, RAMExternalWriteResp_r) = chan("Write_channel_resp"); -//// let (RAMExternalReadReq_s, RAMExternalReadReq_r) = chan("Read_channel_req"); -//// let (RAMExternalReadResp_s, RAMExternalReadResp_r) = chan("Read_channel_resp"); -//// spawn ram::RamModel( -//// RAMExternalReadReq_r, RAMExternalReadResp_s, RAMExternalWriteReq_r, RAMExternalWriteResp_s -//// ); -//// -//// // Emulate Internal prescan memory -//// let (RAMInternalWriteReq_s, RAMInternalWriteReq_r) = chan("Internal_write_channel_req"); -//// let (RAMInternalWriteResp_s, RAMInternalWriteResp_r) = chan("Internal_write_channel_resp"); -//// let (RAMInternalReadReq_s, RAMInternalReadReq_r) = chan("Internal_read_channel_req"); -//// let (RAMInternalReadResp_s, RAMInternalReadResp_r) = chan("Internal_read_channel_resp"); -//// spawn ram::RamModel<{WeightPreScanMetaDataSize()}, RAM_SIZE, {WeightPreScanMetaDataSize()}>( -//// RAMInternalReadReq_r, RAMInternalReadResp_s, RAMInternalWriteReq_r, RAMInternalWriteResp_s -//// ); -//// -//// let (PreScanStart_s, PreScanStart_r) = chan("Start_prescan"); -//// let (PreScanResponse_s, PreScanResponse_r) = chan("Start_prescan"); -//// spawn WeightPreScan( -//// PreScanStart_r, RAMExternalReadReq_s,RAMExternalReadResp_r, PreScanResponse_s, -//// RAMInternalReadReq_s, RAMInternalReadResp_r, RAMInternalWriteReq_s, RAMInternalWriteResp_r); -//// (terminator, RAMExternalWriteReq_s, RAMExternalWriteResp_r, PreScanStart_s, PreScanResponse_r) -//// } -//// next(state: ()) { -//// let tok = join(); -//// let rand_state = random::rng_new(random::rng_deterministic_seed()); -//// // Setup external memory with random values -//// for (i, rand_state) in u32:0..MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH { -//// let (new_rand_state, data_to_send) = for (j, (rand_state, data_to_send)) in u32:0..PARALLEL_ACCESS_WIDTH { -//// let (new_rand_state, data) = random::rng_next(rand_state); -//// let weight = (data - (data/u32:12) * u32:12) as u4; -//// let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; -//// (new_rand_state, new_data_to_send) -//// }((rand_state, zero!())); -//// let external_w_req = WriteReq { -//// addr: i as u5, -//// data: data_to_send, -//// mask: u1:1 -//// }; -//// send(tok, external_ram_req, external_w_req); -//// recv(tok, external_ram_resp); -//// new_rand_state -//// }(rand_state); -//// send(tok, start_prescan, true); -//// // First run -//// for (_, rand_state) in u32:0..MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH { -//// // Generate expected output -//// let (new_rand_state, expected_data) = for (j, (rand_state, data_to_send)) in u32:0..PARALLEL_ACCESS_WIDTH { -//// let (new_rand_state, data) = random::rng_next(rand_state); -//// let weight = (data - (data/u32:12) * u32:12) as u4; -//// let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; -//// (new_rand_state, new_data_to_send) -//// }((rand_state, zero!())); -//// let (_, prescan_resp) = recv(tok, prescan_response); -//// let expected_data = PrescanOut { -//// weights: expected_data as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], -//// meta_data: zero!() -//// }; -//// assert_eq(prescan_resp, expected_data); -//// new_rand_state -//// }(rand_state); -//// -//// // Second run -//// for (_, rand_state) in u32:0..MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH { -//// // Generate expected output -//// let (new_rand_state, expected_data) = for (j, (rand_state, data_to_send)) in u32:0..PARALLEL_ACCESS_WIDTH { -//// let (new_rand_state, data) = random::rng_next(rand_state); -//// let weight = (data - (data/u32:12) * u32:12) as u4; -//// let new_data_to_send = update(data_to_send as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], j, weight) as external_ram_data; -//// (new_rand_state, new_data_to_send) -//// }((rand_state, zero!())); -//// let expected_data = expected_data as uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH]; -//// let valid_weights = for (i, seen_weights) in u32:0..PARALLEL_ACCESS_WIDTH { -//// update(seen_weights, expected_data[i], true) -//// }(zero!()); -//// let occurance_number = for (i, occurance_number) in u32:0..PARALLEL_ACCESS_WIDTH { -//// let number = for (j, number) in u32:0..PARALLEL_ACCESS_WIDTH{ -//// if (j < i && expected_data[j] == expected_data[i]) { -//// number + u4:1 -//// } else { -//// number -//// } -//// }(zero!()); -//// update(occurance_number, i, number) -//// }(zero!()); -//// let weights_count = for (i, weights_count) in u32:0..MAX_WEIGHT + u32:1 { -//// let count = for (j, count) in u32:0..PARALLEL_ACCESS_WIDTH { -//// if (expected_data[j] == i as uN[COUNTER_WIDTH]) { -//// count + uN[COUNTER_WIDTH]:1 -//// } else { -//// count -//// } -//// }(zero!()); -//// update(weights_count, i, count) -//// }(zero!()); -//// let (_, prescan_resp) = recv(tok, prescan_response); -//// let expected_data = PrescanOut { -//// weights: expected_data, -//// meta_data: WeightPreScanMetaData { -//// occurance_number: occurance_number, -//// valid_weights: valid_weights, -//// weights_count: weights_count, -//// } -//// }; -//// assert_eq(prescan_resp, expected_data); -//// new_rand_state -//// }(rand_state); -//// -//// send(tok, terminator, true); -//// } -//} +const INST_MAX_WEIGHT = hcommon::MAX_WEIGHT; + +pub proc WeightCodeBuilderInst { + type PreScanData = WeightPreScanOutput; + type DecoderOutput = CodeBuilderToDecoderOutput; + type PreDecoderOutput = CodeBuilderToPreDecoderOutput; + + config( + start_r: chan in, + weight_r: chan in, + codes_s: chan out, + lookahead_config_s: chan out, + weights_pow_sum_loopback_s: chan out, + weights_pow_sum_loopback_r: chan in, + ) { + spawn WeightCodeBuilder ( + start_r, + weight_r, + codes_s, + lookahead_config_s, + weights_pow_sum_loopback_s, + weights_pow_sum_loopback_r, + ); + } + + init { } + + next (state: ()) { } +} diff --git a/xls/modules/zstd/huffman_common.x b/xls/modules/zstd/huffman_common.x index 7661f42315..52115cf099 100644 --- a/xls/modules/zstd/huffman_common.x +++ b/xls/modules/zstd/huffman_common.x @@ -30,27 +30,10 @@ pub struct WeightPreScanMetaData { weights_count: uN[COUNTER_WIDTH][MAX_WEIGHT + u32:1], } -// TODO: Enable once parametrics work -//pub struct WeightPreScanMetaData < -// PARALLEL_ACCESS_WIDTH: u32, -// COUNTER_WIDTH: u32 = {std::clog2(PARALLEL_ACCESS_WIDTH + u32:1)} -//> { -// occurance_number: uN[COUNTER_WIDTH][PARALLEL_ACCESS_WIDTH], -// valid_weights: u1[MAX_WEIGHT + u32:1], -// weights_count: uN[COUNTER_WIDTH][MAX_WEIGHT + u32:1], -//} - pub struct WeightPreScanOutput { weights: uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], meta_data: WeightPreScanMetaData, } -// TODO: Use parametrics when they work -//pub struct WeightPreScanOutput< -// PARALLEL_ACCESS_WIDTH: u32, WEIGHT_LOG: u32 -//> { -// weights: uN[WEIGHT_LOG][PARALLEL_ACCESS_WIDTH], -// meta_data: WeightPreScanMetaData, -//} pub struct CodeBuilderToPreDecoderOutput { max_code_length: uN[WEIGHT_LOG], diff --git a/xls/modules/zstd/huffman_literals_dec.x b/xls/modules/zstd/huffman_literals_dec.x index d84a541baa..b6d124575f 100644 --- a/xls/modules/zstd/huffman_literals_dec.x +++ b/xls/modules/zstd/huffman_literals_dec.x @@ -245,7 +245,11 @@ pub proc HuffmanLiteralsDecoder< weights_fse_rd_req_s, weights_fse_rd_resp_r, weights_fse_wr_req_s, weights_fse_wr_resp_r, ); - spawn prescan::WeightPreScan( + spawn prescan::WeightPreScan< + hcommon::MAX_SYMBOL_COUNT, + hcommon::PARALLEL_ACCESS_WIDTH, + hcommon::MAX_WEIGHT, + >( prescan_start_r, weights_ram_rd_req_s, weights_ram_rd_resp_r, @@ -256,7 +260,10 @@ pub proc HuffmanLiteralsDecoder< prescan_ram_wr_resp_r, ); - spawn code_builder::WeightCodeBuilder( + spawn code_builder::WeightCodeBuilder< + hcommon::MAX_SYMBOL_COUNT, + hcommon::PARALLEL_ACCESS_WIDTH, + >( code_builder_start_r, prescan_response_r, code_builder_codes_s, @@ -1116,116 +1123,3 @@ proc HuffmanLiteralsDecoder_test { send(tok, terminator, true); } } - -// TODO: implement tests with the following Huffman Tree -//const TEST_DATA_LEN_0 = u32:64; -//const TEST_DATA_0 = ( -// u8:0b001_1_010_0 ++ // 0x34 <- last byte in the memory -// u8:0b11_1_1_0001 ++ // 0xF1 -// u8:0b01_010_000 ++ // 0x50 -// u8:0b001_010_1_0 ++ // 0x2A -// u8:0b11_010_1_00 ++ // 0xD4 -// u8:0b0100_001_0 ++ // 0x42 -// u8:0b01_010_1_01 ++ // 0x55 -// u8:0b1_001_010_1 // 0x95 <- first byte in the memory -//); -// -//// code symbol length weight -//// 0b1 0x47 1 9 -//// 0b001 0x41 3 7 -//// 0b010 0x8A 3 7 -//// 0b011 0xD2 3 7 -//// 0b000001 0x45 6 4 -//// 0b000010 0x7A 6 4 -//// 0b000011 0x89 6 4 -//// 0b000100 0x8D 6 4 -//// 0b000101 0xD1 6 4 -//// 0b000110 0xD3 6 4 -//// 0b000111 0xDA 6 4 -//// 0b000000000 0x12 9 1 -//// 0b000000001 0x8F 9 1 -//// 0b000000010 0xAC 9 1 -//// 0b000000011 0xD4 9 1 -//// 0b000000100 0xD7 9 1 -//// 0b000000101 0xDB 9 1 -//// 0b000000110 0xDE 9 1 -//// 0b000000111 0xFE 9 1 -// -//const TEST_WEIGHT_MEMORY_0 = TestRamEntry[32]:[ -// // x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x0x -// TestRamEntry:0x_0__0__1__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x1x -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x2x -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x3x -// TestRamEntry:0x_0__7__0__0__0__4__0__9, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x4x -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x5x -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x6x -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__4__0__0__0__0__0, // 0x7x -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__4__7__0__0__4__0__1, // 0x8x -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0x9x -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__1__0__0__0, // 0xAx -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xBx -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xCx -// TestRamEntry:0x_0__4__7__4__1__0__0__1, TestRamEntry:0x_0__0__4__1__0__0__1__0, // 0xDx -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__0__0, // 0xEx -// TestRamEntry:0x_0__0__0__0__0__0__0__0, TestRamEntry:0x_0__0__0__0__0__0__1__0, // 0xFx -//]; -// -//const TEST_DECODED_LITERALS_0 = common::LiteralsDataWithSync[3]:[ -// common::LiteralsDataWithSync { -// data: common::LitData:0x458A_D147_47D2_8A47, -// length: common::LitLength:8, -// last: false, -// id: u32:0, -// literals_last: false, -// }, -// common::LiteralsDataWithSync { -// data: common::LitData:0x4141_8D47_8AD2_478A, -// length: common::LitLength:8, -// last: false, -// id: u32:0, -// literals_last: false, -// }, -// common::LiteralsDataWithSync { -// data: common::LitData:0x478A_41D2_478A, -// length: common::LitLength:6, -// last: true, -// id: u32:0, -// literals_last: false, -// }, -//]; -// -//// data for test case #1 (same config) -//const TEST_CTRL_1 = TestCtrl { -// base_addr: uN[TEST_AXI_RAM_ADDR_W]:0x20, -// len: uN[TEST_AXI_RAM_ADDR_W]:0x4, -// new_config: false, -// multi_stream: false, -// id: u32:1, -// literals_last: true, -//}; -// -//const TEST_DATA_LEN_1 = u32:32; -//const TEST_DATA_1 = ( -// u8:0b001_011_1_1 ++ // 0x2F <- last byte in the memory -// u8:0b1_1_000000 ++ // 0xC0 -// u8:0b000_0_000 ++ // 0x00 -// u8:0b0010_1_010 // 0x2A <- first byte in the memory -//); -// -//const TEST_DECODED_LITERALS_1 = common::LiteralsDataWithSync[2]:[ -// common::LiteralsDataWithSync { -// data: common::LitData:0x47AC_1247_4747_47D2, -// length: common::LitLength:8, -// last: false, -// id: u32:1, -// literals_last: true, -// }, -// common::LiteralsDataWithSync { -// data: common::LitData:0x8A, -// length: common::LitLength:1, -// last: true, -// id: u32:1, -// literals_last: true, -// }, -//]; diff --git a/xls/modules/zstd/huffman_prescan.x b/xls/modules/zstd/huffman_prescan.x index b940cb90e5..f599fb92d1 100644 --- a/xls/modules/zstd/huffman_prescan.x +++ b/xls/modules/zstd/huffman_prescan.x @@ -45,44 +45,14 @@ import xls.examples.ram; import xls.modules.zstd.common as common; import xls.modules.zstd.huffman_common as hcommon; -// TODO: Enable once parametrics work -//fn WeightPreScanMetaDataSize(PARALLEL_ACCESS_WIDTH: u32) -> u32 { -// let COUNTER_WIDTH = {std::clog2(PARALLEL_ACCESS_WIDTH + u32:1)}; -// (COUNTER_WIDTH as u32) * (PARALLEL_ACCESS_WIDTH as u32) + -// (MAX_WEIGHT as u32) + u32:1 + -// (COUNTER_WIDTH as u32) * (MAX_WEIGHT as u32 + u32:1) -//} -// -//fn InternalStructToBits< -// PARALLEL_ACCESS_WIDTH: u32, -// BITS: u32 = {WeightPreScanMetaDataSize(PARALLEL_ACCESS_WIDTH)} -//> (internalStruct: WeightPreScanMetaData) -> bits[BITS] { -// internalStruct as bits[BITS] -//} -// -//fn BitsToInternalStruct< -// PARALLEL_ACCESS_WIDTH: u32, -// BITS: u32 = {WeightPreScanMetaDataSize(PARALLEL_ACCESS_WIDTH)} -//> (rawBits: bits[BITS]) -> WeightPreScanMetaData { -// rawBits as WeightPreScanMetaData -//} - -const MAX_WEIGHT = hcommon::MAX_WEIGHT; -const WEIGHT_LOG = hcommon::WEIGHT_LOG; -const MAX_SYMBOL_COUNT = hcommon::MAX_SYMBOL_COUNT; - -const PARALLEL_ACCESS_WIDTH = hcommon::PARALLEL_ACCESS_WIDTH; -const COUNTER_WIDTH = hcommon::COUNTER_WIDTH; - type WeightPreScanMetaData = hcommon::WeightPreScanMetaData; type WeightPreScanOutput = hcommon::WeightPreScanOutput; -pub const WEIGHT_PRESCAN_METADATA_SIZE = - (COUNTER_WIDTH as u32) * (PARALLEL_ACCESS_WIDTH as u32) + - (MAX_WEIGHT as u32) + u32:1 + - (COUNTER_WIDTH as u32) * (MAX_WEIGHT as u32 + u32:1); - fn InternalStructToBits< + PARALLEL_ACCESS_WIDTH: u32, + MAX_WEIGHT: u32, + COUNTER_WIDTH: u32, + WEIGHT_PRESCAN_METADATA_SIZE: u32, BITS: u32 = {WEIGHT_PRESCAN_METADATA_SIZE}, OCCURANCE_WIDTH: u32 ={COUNTER_WIDTH * PARALLEL_ACCESS_WIDTH}, > (internalStruct: WeightPreScanMetaData) -> bits[BITS] { @@ -92,6 +62,10 @@ fn InternalStructToBits< } fn BitsToInternalStruct< + PARALLEL_ACCESS_WIDTH: u32, + MAX_WEIGHT: u32, + COUNTER_WIDTH: u32, + WEIGHT_PRESCAN_METADATA_SIZE: u32, BITS: u32 = {WEIGHT_PRESCAN_METADATA_SIZE}, OCCURANCE_WIDTH: u32 ={COUNTER_WIDTH * PARALLEL_ACCESS_WIDTH}, > (rawBits: bits[BITS]) -> WeightPreScanMetaData { @@ -102,23 +76,6 @@ fn BitsToInternalStruct< } } -#[quickcheck(test_count=50000)] -fn bits_to_struct_to_bits_qtest(x: bits[WEIGHT_PRESCAN_METADATA_SIZE]) -> bool { - x == InternalStructToBits(BitsToInternalStruct(x)) -} - -#[quickcheck(test_count=50000)] -fn struct_to_bots_to_struct_qtest(x: WeightPreScanMetaData) -> bool { - x == BitsToInternalStruct(InternalStructToBits(x)) -} - -pub const RAM_SIZE = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH * u32:8 / WEIGHT_LOG; -pub const RAM_ADDR_WIDTH = {std::clog2(RAM_SIZE)}; -pub const RAM_ACCESS_WIDTH = PARALLEL_ACCESS_WIDTH * WEIGHT_LOG; -const RAM_PARTITION_SIZE = RAM_ACCESS_WIDTH / u32:8; -const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_PARTITION_SIZE, RAM_ACCESS_WIDTH); -const MAX_RAM_ADDR = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH; - enum WeightPreScanFSM: u2 { IDLE = u2:0, FIRST_RUN = u2:1, @@ -131,16 +88,23 @@ struct WeightPreScanState { internal_addr: u9, } -pub proc WeightPreScan -// TODO: enable parametric expresion when they start working -//proc WeightPreScan< -// PARALLEL_ACCESS_WIDTH: u32 = {u32:8}, -// RAM_SIZE: u32 = {MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH}, -// RAM_ADDR_WIDTH: u32 = {std::clog2(RAM_SIZE)}, -// RAM_ACCESS_WIDTH: u32 = {PARALLEL_ACCESS_WIDTH * WEIGHT_LOG}, -// MAX_RAM_ADDR: u32 = {(u32:1< { -{ +pub proc WeightPreScan< + MAX_SYMBOL_COUNT: u32 = {u32:256}, + PARALLEL_ACCESS_WIDTH: u32 = {u32:8}, + MAX_WEIGHT: u32 = {u32:11}, + WEIGHT_LOG: u32 = {std::clog2(MAX_WEIGHT + u32:1)}, + COUNTER_WIDTH: u32 = {std::clog2(PARALLEL_ACCESS_WIDTH + u32:1)}, + RAM_SIZE: u32 = {MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH * u32:8 / WEIGHT_LOG}, + RAM_ADDR_WIDTH: u32 = {std::clog2(RAM_SIZE)}, + RAM_ACCESS_WIDTH: u32 = {PARALLEL_ACCESS_WIDTH * WEIGHT_LOG}, + RAM_PARTITION_SIZE: u32 = {RAM_ACCESS_WIDTH / u32:8}, + RAM_NUM_PARTITIONS: u32 = {ram::num_partitions(RAM_PARTITION_SIZE, RAM_ACCESS_WIDTH)}, + MAX_RAM_ADDR: u32 = {MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH}, + WEIGHT_PRESCAN_METADATA_SIZE: u32 = { + (COUNTER_WIDTH as u32) * (PARALLEL_ACCESS_WIDTH as u32) + + (MAX_WEIGHT as u32) + u32:1 + + (COUNTER_WIDTH as u32) * (MAX_WEIGHT as u32 + u32:1)}, +> { type State = WeightPreScanState; type FSM = WeightPreScanFSM; @@ -204,10 +168,6 @@ pub proc WeightPreScan let valid_data = state.addr < state.internal_addr || state.internal_addr as u32 == MAX_RAM_ADDR - u32:1; (false, valid_data, false, valid_data, state.addr as ExternalRamAddr) }, - _ => { - assert!(false, "Invalid state"); - (false, false, false, false, u9:0 as ExternalRamAddr) - } }; let (tok, start) = recv_if(tok, start_r, recv_start, false); if recv_start { @@ -284,7 +244,12 @@ pub proc WeightPreScan }; let tok = send_if(tok, internal_read_req_s, read_internal, internal_ram_r_req); let (tok, meta_data_flat) = recv_if(tok, internal_read_rsp_r, read_internal, zero!()); - let meta_data = BitsToInternalStruct(meta_data_flat.data); + let meta_data = BitsToInternalStruct< + PARALLEL_ACCESS_WIDTH, + MAX_WEIGHT, + COUNTER_WIDTH, + WEIGHT_PRESCAN_METADATA_SIZE, + >(meta_data_flat.data); if read_internal { trace_fmt!("[WeightPreScan] Reading internal memory data: {:#x}", meta_data); @@ -326,7 +291,12 @@ pub proc WeightPreScan let internal_ram_w_req = InternalWriteReq { addr: addr, - data: InternalStructToBits(_meta_data), + data: InternalStructToBits< + PARALLEL_ACCESS_WIDTH, + MAX_WEIGHT, + COUNTER_WIDTH, + WEIGHT_PRESCAN_METADATA_SIZE, + >(_meta_data), mask: u1:1 }; let tok = send_if(tok, internal_write_req_s, write_internal, internal_ram_w_req); @@ -340,6 +310,64 @@ pub proc WeightPreScan } } +const MAX_WEIGHT = hcommon::MAX_WEIGHT; +const WEIGHT_LOG = hcommon::WEIGHT_LOG; +const MAX_SYMBOL_COUNT = hcommon::MAX_SYMBOL_COUNT; +const PARALLEL_ACCESS_WIDTH = hcommon::PARALLEL_ACCESS_WIDTH; +const COUNTER_WIDTH = hcommon::COUNTER_WIDTH; +pub const WEIGHT_PRESCAN_METADATA_SIZE = + (COUNTER_WIDTH as u32) * (PARALLEL_ACCESS_WIDTH as u32) + + (MAX_WEIGHT as u32) + u32:1 + + (COUNTER_WIDTH as u32) * (MAX_WEIGHT as u32 + u32:1); +pub const RAM_SIZE = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH * u32:8 / WEIGHT_LOG; +pub const RAM_ADDR_WIDTH = {std::clog2(RAM_SIZE)}; +pub const RAM_ACCESS_WIDTH = PARALLEL_ACCESS_WIDTH * WEIGHT_LOG; +const RAM_PARTITION_SIZE = RAM_ACCESS_WIDTH / u32:8; +const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_PARTITION_SIZE, RAM_ACCESS_WIDTH); +const MAX_RAM_ADDR = MAX_SYMBOL_COUNT/PARALLEL_ACCESS_WIDTH; + +pub proc WeightPreScanInst { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + + type OutData = WeightPreScanOutput; + + type InternalRamAddr = uN[RAM_ADDR_WIDTH]; + type InternalData = WeightPreScanMetaData; + type InternalRamData = bits[WEIGHT_PRESCAN_METADATA_SIZE]; + + type InternalReadReq = ram::ReadReq; + type InternalReadResp = ram::ReadResp; + type InternalWriteReq = ram::WriteReq; + type InternalWriteResp = ram::WriteResp; + + config( + start_r: chan in, + read_req_s: chan out, + read_rsp_r: chan in, + weight_s: chan out, + internal_read_req_s: chan out, + internal_read_rsp_r: chan in, + internal_write_req_s: chan out, + internal_write_rsp_r: chan in + ) { + spawn WeightPreScan ( + start_r, + read_req_s, + read_rsp_r, + weight_s, + internal_read_req_s, + internal_read_rsp_r, + internal_write_req_s, + internal_write_rsp_r, + ); + } + + init { } + + next (state: ()) { } +} + #[test_proc] proc Prescan_test{ type external_ram_addr = uN[RAM_ADDR_WIDTH]; diff --git a/xls/modules/zstd/literals_block_header_dec.x b/xls/modules/zstd/literals_block_header_dec.x index 6d8e4b0f80..5d993bbcd6 100644 --- a/xls/modules/zstd/literals_block_header_dec.x +++ b/xls/modules/zstd/literals_block_header_dec.x @@ -203,7 +203,6 @@ pub proc LiteralsHeaderDecoder { // max number of bytes that the header can have, see RFC8878 Section 3.1.1.3.1.1. length: uN[AXI_ADDR_W]:5, }); - // TODO: handle multiple receives on mem_rd_resp_r when AXI_DATA_W < 40 const_assert!(AXI_DATA_W >= u32:64); let (tok, raw) = recv(tok, mem_rd_resp_r); let (header, length, symbol) = parse_literals_header(raw.data[:40]); diff --git a/xls/modules/zstd/literals_buffer.x b/xls/modules/zstd/literals_buffer.x index fb756e383d..45237fd0ec 100644 --- a/xls/modules/zstd/literals_buffer.x +++ b/xls/modules/zstd/literals_buffer.x @@ -139,9 +139,6 @@ proc PacketDecoder { }(bool[RAM_NUM]:[0, ...]); let literals_last = literals_lasts[literals.length - u64:1]; - // TODO: Restore this check after extending request to CommandConstructor - // assert!(literals.last == literals_last, "Invalid packet"); - // Send literals data let literals_out = SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, @@ -459,7 +456,6 @@ proc LiteralsBufferWriter< } next (state: State) { let tok0 = join(); - // TODO: Remove this workaround when fixed: https://github.com/google/xls/issues/1368 type State = LiteralsBufferWriterState; type WriteReq = ram::WriteReq; @@ -665,7 +661,6 @@ proc LiteralsBufferReader< next (state: State) { let tok0 = join(); - // TODO: Remove this workaround when fixed: https://github.com/google/xls/issues/1368 type ReadReq = ram::ReadReq; type State = LiteralsBufferReaderState; diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x index 442cb0776c..2145c0a574 100644 --- a/xls/modules/zstd/literals_decoder.x +++ b/xls/modules/zstd/literals_decoder.x @@ -24,7 +24,6 @@ import xls.modules.zstd.memory.axi as axi; import xls.modules.zstd.memory.axi_ram_reader; import xls.modules.zstd.memory.mem_reader as mem_reader; import xls.modules.zstd.parallel_rams as parallel_rams; -import xls.modules.zstd.ram_printer as ram_printer; import xls.modules.zstd.raw_literals_dec as raw_literals_dec; import xls.modules.zstd.rle_literals_dec as rle_literals_dec; import xls.modules.zstd.huffman_literals_dec as huffman_literals_dec; @@ -588,14 +587,13 @@ proc LiteralsDecoderCtrl_test { let tok = send(tok, rle_lit_resp_s, rle_lit_resp); trace_fmt!("Test: Sent RleResp: {:#x}", rle_lit_resp); } else { - //let (tok, huffman_lit_req) = recv(tok, huffman_lit_req_r); - //trace_fmt!("Test: Received HuffmanReq: {:#x}", huffman_lit_req); - //let expected_huffman_lit_req = HuffmanReq { - //}; - //assert_eq(huffman_lit_req, expected_huffman_lit_req); - //let huffman_lit_resp = HuffmanResp { status: HuffmanStatus::OKAY }; - //let tok = send(tok, huffman_lit_resp_s, huffman_lit_resp); - //trace_fmt!("Test: Sent HuffmanResp: {:#x}", huffman_lit_resp); + let (tok, huffman_lit_req) = recv(tok, huffman_lit_req_r); + trace_fmt!("Test: Received HuffmanReq: {:#x}", huffman_lit_req); + let expected_huffman_lit_req = zero!(); + assert_eq(huffman_lit_req, expected_huffman_lit_req); + let huffman_lit_resp = HuffmanResp { status: HuffmanStatus::OKAY }; + let tok = send(tok, huffman_lit_resp_s, huffman_lit_resp); + trace_fmt!("Test: Sent HuffmanResp: {:#x}", huffman_lit_resp); }; let (tok, lit_ctrl_resp) = recv(tok, lit_ctrl_resp_r); @@ -648,7 +646,6 @@ pub proc LiteralsDecoder< type BufferCtrl = common::LiteralsBufferCtrl; type BufferOut = common::SequenceExecutorPacket; - // TODO: make sure those can use the same parameters type HuffmanWeightsReadReq = ram::ReadReq; type HuffmanWeightsReadResp = ram::ReadResp; type HuffmanWeightsWriteReq = ram::WriteReq; @@ -1340,9 +1337,6 @@ proc LiteralsDecoder_test { buf_ctrl_s: chan out; buf_out_r: chan in; - print_start_s: chan<()> out; - print_finish_r: chan<()> in; - ram_wr_req_header_s : chan out; ram_wr_resp_header_r : chan in; ram_wr_req_raw_s : chan out; @@ -1392,9 +1386,6 @@ proc LiteralsDecoder_test { let (buf_ctrl_s, buf_ctrl_r) = chan("buf_ctrl"); let (buf_out_s, buf_out_r) = chan("buf_out"); - let (print_start_s, print_start_r) = chan<()>("print_start"); - let (print_finish_s, print_finish_r) = chan<()>("print_finish"); - let (ram_rd_req_s, ram_rd_req_r) = chan[literals_buffer::RAM_NUM]("ram_rd_req"); let (ram_rd_resp_s, ram_rd_resp_r) = chan[literals_buffer::RAM_NUM]("ram_rd_resp"); let (ram_wr_req_s, ram_wr_req_r) = chan[literals_buffer::RAM_NUM]("ram_wr_req"); @@ -1472,16 +1463,6 @@ proc LiteralsDecoder_test { huffman_lit_weights_fse_wr_req_s, huffman_lit_weights_fse_wr_resp_r, ); - spawn ram_printer::RamPrinter< - TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, - TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, - TEST_LITERALS_BUFFER_RAM_MODEL_NUM_PARTITIONS, - TEST_LITERALS_BUFFER_RAM_MODEL_ADDR_WIDTH, - TEST_LITERALS_BUFFER_RAM_MODEL_NUM - > ( - print_start_r, print_finish_s, ram_rd_req_s, ram_rd_resp_r - ); - spawn ram::RamModel< TEST_LITERALS_BUFFER_RAM_MODEL_DATA_WIDTH, TEST_LITERALS_BUFFER_RAM_MODEL_SIZE, @@ -1850,7 +1831,6 @@ proc LiteralsDecoder_test { terminator, ctrl_req_s, ctrl_resp_r, ctrl_header_r, buf_ctrl_s, buf_out_r, - print_start_s, print_finish_r, ram_wr_req_header_s, ram_wr_resp_header_r, ram_wr_req_raw_s, ram_wr_resp_raw_r, ram_wr_req_huffman_s, ram_wr_resp_huffman_r, @@ -2131,267 +2111,6 @@ proc LiteralsDecoder_test { tok }(tok); - //// print RAM content - //let tok = send(tok, print_start_s, ()); - //let (tok, _) = recv(tok, print_finish_r); - send(tok, terminator, true); } } - -// TODO: Uncomment this test when fixed: https://github.com/google/xls/issues/1502 -// type RamData = uN[literals_buffer::RAM_DATA_WIDTH]; - -// // Expected RAM content after each ctrl -// const TEST_EXPECTED_RAM_CONTENT = RamData[literals_buffer::RAM_NUM][10][7]:[ -// [ -// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// ], -// [ -// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// ], -// [ -// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], -// [RamData: 0x0, RamData: 0x0, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// ], -// [ -// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], -// [RamData:0x053, RamData:0x070, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], -// [RamData:0x1a9, RamData:0x16d, RamData:0x04c, RamData:0x0fb, RamData:0x041, RamData:0x0c6, RamData:0x07b, RamData:0x060], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData:0x19b, RamData:0x10f, RamData:0x19c, RamData:0x1e1, RamData:0x1ba], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// ], -// [ -// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], -// [RamData:0x053, RamData:0x070, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], -// [RamData:0x1a9, RamData:0x16d, RamData:0x04c, RamData:0x0fb, RamData:0x041, RamData:0x0c6, RamData:0x07b, RamData:0x060], -// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x19b, RamData:0x10f, RamData:0x19c, RamData:0x1e1, RamData:0x1ba], -// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData:0x05a], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// ], -// [ -// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], -// [RamData:0x053, RamData:0x070, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], -// [RamData:0x1a9, RamData:0x16d, RamData:0x04c, RamData:0x0fb, RamData:0x041, RamData:0x0c6, RamData:0x07b, RamData:0x060], -// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x19b, RamData:0x10f, RamData:0x19c, RamData:0x1e1, RamData:0x1ba], -// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData:0x05a], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// ], -// [ -// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], -// [RamData:0x053, RamData:0x070, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], -// [RamData:0x1a9, RamData:0x16d, RamData:0x04c, RamData:0x0fb, RamData:0x041, RamData:0x0c6, RamData:0x07b, RamData:0x060], -// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x19b, RamData:0x10f, RamData:0x19c, RamData:0x1e1, RamData:0x1ba], -// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a], -// [RamData:0x094, RamData:0x03e, RamData:0x096, RamData:0x018, RamData:0x034, RamData:0x0c2, RamData:0x047, RamData:0x05a], -// [RamData:0x002, RamData:0x0d0, RamData:0x0e8, RamData:0x0d7, RamData:0x028, RamData:0x09a, RamData:0x0be, RamData:0x060], -// [RamData:0x064, RamData:0x0c3, RamData:0x08b, RamData:0x0e1, RamData:0x0fa, RamData:0x08d, RamData:0x012, RamData:0x0bc], -// [RamData:0x119, RamData:0x163, RamData:0x1f1, RamData:0x1ce, RamData:0x121, RamData:0x1c2, RamData:0x194, RamData:0x0f8], -// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], -// ], -// ]; - -// const CYCLES_PER_RAM_READ = u32:16; - -// #[test_proc] -// proc LiteralsDecoderRamContent_test { -// terminator: chan out; - -// literals_ctrl_s: chan out; -// literals_data_s: chan out; -// literals_buf_ctrl_s: chan out; -// literals_r: chan in; - -// ram_rd_req_m0_s: chan out; -// ram_rd_req_m1_s: chan out; -// ram_rd_req_m2_s: chan out; -// ram_rd_req_m3_s: chan out; -// ram_rd_req_m4_s: chan out; -// ram_rd_req_m5_s: chan out; -// ram_rd_req_m6_s: chan out; -// ram_rd_req_m7_s: chan out; - -// ram_rd_resp_m0_r: chan in; -// ram_rd_resp_m1_r: chan in; -// ram_rd_resp_m2_r: chan in; -// ram_rd_resp_m3_r: chan in; -// ram_rd_resp_m4_r: chan in; -// ram_rd_resp_m5_r: chan in; -// ram_rd_resp_m6_r: chan in; -// ram_rd_resp_m7_r: chan in; - -// config (terminator: chan out) { -// let (literals_ctrl_s, literals_ctrl_r) = chan("literals_ctrl"); -// let (literals_buf_ctrl_s, literals_buf_ctrl_r) = chan("literals_buf_ctrl"); -// let (literals_s, literals_r) = chan("literals"); - -// let (ram_rd_req_s, ram_rd_req_r) = chan[literals_buffer::RAM_NUM]("ram_rd_req"); -// let (ram_rd_resp_s, ram_rd_resp_r) = chan[literals_buffer::RAM_NUM]("ram_rd_resp"); -// let (ram_wr_req_s, ram_wr_req_r) = chan[literals_buffer::RAM_NUM]("ram_wr_req"); -// let (ram_wr_resp_s, ram_wr_resp_r) = chan[literals_buffer::RAM_NUM]("ram_wr_resp"); - -// spawn LiteralsDecoder( -// literals_ctrl_r, -// literals_buf_ctrl_r, literals_s, -// ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], -// ram_rd_req_s[4], ram_rd_req_s[5], ram_rd_req_s[6], ram_rd_req_s[7], -// ram_rd_resp_r[0], ram_rd_resp_r[1], ram_rd_resp_r[2], ram_rd_resp_r[3], -// ram_rd_resp_r[4], ram_rd_resp_r[5], ram_rd_resp_r[6], ram_rd_resp_r[7], -// ram_wr_req_s[0], ram_wr_req_s[1], ram_wr_req_s[2], ram_wr_req_s[3], -// ram_wr_req_s[4], ram_wr_req_s[5], ram_wr_req_s[6], ram_wr_req_s[7], -// ram_wr_resp_r[0], ram_wr_resp_r[1], ram_wr_resp_r[2], ram_wr_resp_r[3], -// ram_wr_resp_r[4], ram_wr_resp_r[5], ram_wr_resp_r[6], ram_wr_resp_r[7] -// ); - -// spawn ram::RamModel< -// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, -// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> -// (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); -// spawn ram::RamModel< -// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, -// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> -// (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); -// spawn ram::RamModel< -// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, -// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> -// (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); -// spawn ram::RamModel< -// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, -// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> -// (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); -// spawn ram::RamModel< -// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, -// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> -// (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); -// spawn ram::RamModel< -// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, -// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> -// (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); -// spawn ram::RamModel< -// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, -// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> -// (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); -// spawn ram::RamModel< -// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, -// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> -// (ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7]); - -// ( -// terminator, -// literals_ctrl_s, literals_data_s, -// literals_buf_ctrl_s, literals_r, -// ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], -// ram_rd_req_s[4], ram_rd_req_s[5], ram_rd_req_s[6], ram_rd_req_s[7], -// ram_rd_resp_r[0], ram_rd_resp_r[1], ram_rd_resp_r[2], ram_rd_resp_r[3], -// ram_rd_resp_r[4], ram_rd_resp_r[5], ram_rd_resp_r[6], ram_rd_resp_r[7], -// ) -// } - -// init { u32:0 } - -// next (state: u32) { -// // send literals -// let ok = if (state == u32:0) { -// for ((i, test_data), tok): ((u32, LiteralsData), token) in enumerate(TEST_DATA) { -// let tok = send(tok, literals_data_s, test_data); -// trace_fmt!("Sent #{} literals data, {:#x}", i + u32:1, test_data); -// tok -// }(tok) -// } else { tok }; - -// // send ctrl and read RAM content -// let tok = for ((i, test_ctrl), tok): ((u32, LiteralsPathCtrl), token) in enumerate(TEST_CTRL) { -// if (state == i * CYCLES_PER_RAM_READ) { -// let tok = send(tok, literals_ctrl_s, test_ctrl); -// trace_fmt!("Sent #{} literals ctrl, {:#x}", i + u32:1, test_ctrl); -// tok -// } else if (state == (i + u32:1) * CYCLES_PER_RAM_READ - u32:1) { -// for (addr, tok): (u32, token) in u32:0..u32:10 { -// let read_req = TestReadReq { -// addr: addr as uN[TEST_RAM_ADDR_WIDTH], -// mask: u1:1 -// }; - -// let tok = send(tok, ram_rd_req_m0_s, read_req); -// let tok = send(tok, ram_rd_req_m1_s, read_req); -// let tok = send(tok, ram_rd_req_m2_s, read_req); -// let tok = send(tok, ram_rd_req_m3_s, read_req); -// let tok = send(tok, ram_rd_req_m4_s, read_req); -// let tok = send(tok, ram_rd_req_m5_s, read_req); -// let tok = send(tok, ram_rd_req_m6_s, read_req); -// let tok = send(tok, ram_rd_req_m7_s, read_req); - -// let (tok, ram_rd_resp_m0) = recv(tok, ram_rd_resp_m0_r); -// let (tok, ram_rd_resp_m1) = recv(tok, ram_rd_resp_m1_r); -// let (tok, ram_rd_resp_m2) = recv(tok, ram_rd_resp_m2_r); -// let (tok, ram_rd_resp_m3) = recv(tok, ram_rd_resp_m3_r); -// let (tok, ram_rd_resp_m4) = recv(tok, ram_rd_resp_m4_r); -// let (tok, ram_rd_resp_m5) = recv(tok, ram_rd_resp_m5_r); -// let (tok, ram_rd_resp_m6) = recv(tok, ram_rd_resp_m6_r); -// let (tok, ram_rd_resp_m7) = recv(tok, ram_rd_resp_m7_r); -// trace_fmt!( -// "Received RAM read responses: [{:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}]", -// ram_rd_resp_m7.data, ram_rd_resp_m6.data, ram_rd_resp_m5.data, ram_rd_resp_m4.data, -// ram_rd_resp_m3.data, ram_rd_resp_m2.data, ram_rd_resp_m1.data, ram_rd_resp_m0.data, -// ); - -// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][7], ram_rd_resp_m0.data); -// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][6], ram_rd_resp_m1.data); -// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][5], ram_rd_resp_m2.data); -// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][4], ram_rd_resp_m3.data); -// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][3], ram_rd_resp_m4.data); -// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][2], ram_rd_resp_m5.data); -// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][1], ram_rd_resp_m6.data); -// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][0], ram_rd_resp_m7.data); - -// tok -// }(tok) -// } else { -// tok -// } -// }(tok); - -// send_if(tok, terminator, state == array_size(TEST_CTRL) * CYCLES_PER_RAM_READ, true); - -// state + u32:1 -// } -// } diff --git a/xls/modules/zstd/math.x b/xls/modules/zstd/math.x index c2c92a259f..f68131dcc1 100644 --- a/xls/modules/zstd/math.x +++ b/xls/modules/zstd/math.x @@ -16,7 +16,8 @@ import std; // Return given value with m first bits masked pub fn mask(n: bits[N], m: bits[M]) -> bits[N] { - n & (std::mask_bits() >> (N as bits[M] - m)) + let shift = if m as u32 > N { bits[M]:0 } else { N as bits[M] - m }; + n & (std::mask_bits() >> shift) } #[test] @@ -26,6 +27,10 @@ fn mask_test() { assert_eq(mask(u8:0b11111111, u4:2), u8:0b00000011); assert_eq(mask(u8:0b11111111, u4:4), u8:0b00001111); assert_eq(mask(u8:0b11111111, u4:8), u8:0b11111111); - assert_eq(mask(u8:0b11111111, u4:9), u8:0b00000000); // FIXME: sketchy result, I would expect - // 0b11111111 + assert_eq(mask(u8:0b11111111, u4:9), u8:0b11111111); + assert_eq(mask(u8:0b00000101, u4:0), u8:0b00000000); + assert_eq(mask(u8:0b00000101, u4:1), u8:0b00000001); + assert_eq(mask(u8:0b00000101, u4:2), u8:0b00000001); + assert_eq(mask(u8:0b00000101, u4:3), u8:0b00000101); + assert_eq(mask(u8:0b00000101, u4:4), u8:0b00000101); } diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD index afcc829bfe..3519ca1380 100644 --- a/xls/modules/zstd/memory/BUILD +++ b/xls/modules/zstd/memory/BUILD @@ -69,8 +69,6 @@ common_codegen_args = { "streaming_channel_data_suffix": "_data", "fifo_module": "", "materialize_internal_fifos": "true", - - # TODO: This should be adjusted when per channel separation of IO options is enabled "flop_inputs_kind": "skid", "flop_outputs_kind": "skid", } @@ -91,7 +89,6 @@ xls_dslx_test( tags = ["manual"], ) -# FIXME: Improve the proc to achieve CLOCK_PERIOD_PS AXI_READER_CLOCK_PERIOD_PS = "1700" axi_reader_codegen_args = common_codegen_args | { @@ -347,7 +344,6 @@ xls_dslx_test( library = ":axi_ram_reader_dslx", ) -# FIXME: Improve the proc to achieve CLOCK_PERIOD_PS AXI_RAM_READER_CLOCK_PERIOD_PS = "850" axi_ram_reader_codegen_args = common_codegen_args | { @@ -434,7 +430,6 @@ xls_dslx_test( tags = ["manual"], ) -# FIXME: Improve the proc to achieve CLOCK_PERIOD_PS MEM_READER_CLOCK_PERIOD_PS = "2600" mem_reader_codegen_args = common_codegen_args | { diff --git a/xls/modules/zstd/memory/axi_ram_reader.x b/xls/modules/zstd/memory/axi_ram_reader.x index 0b9c2d9cb0..93df37001d 100644 --- a/xls/modules/zstd/memory/axi_ram_reader.x +++ b/xls/modules/zstd/memory/axi_ram_reader.x @@ -30,8 +30,7 @@ enum AxiRamReaderStatus: u1 { READ_BURST = 1, } -// FIXME: add default value for RAM_DATA_W_PLUS1_LOG2 = {std::clog2(AXI_DATA_W + u32:1)} (https://github.com/google/xls/issues/992) -struct AxiRamReaderSync { +struct AxiRamReaderSync { do_recv_ram_resp: bool, read_data_size: uN[RAM_DATA_W_PLUS1_LOG2], read_data_offset: uN[RAM_DATA_W_PLUS1_LOG2], @@ -49,8 +48,7 @@ struct AxiRamReaderRequesterState { ram_rd_req_idx: u8, } -// FIXME: add default value for AXI_DATA_W_PLUS1_LOG2 = {std::clog2(AXI_DATA_W + u32:1)} (https://github.com/google/xls/issues/992) -struct AxiRamReaderResponderState { +struct AxiRamReaderResponderState { data: uN[AXI_DATA_W], data_size: uN[AXI_DATA_W_PLUS1_LOG2], } @@ -362,9 +360,7 @@ const INST_RAM_NUM_PARTITIONS = INST_RAM_DATA_W / INST_RAM_WORD_PARTITION_SIZE; const INST_BASE_ADDR = u32:0x8000; -proc AxiRamReaderInst< - FAKE_PARAM: u32 = {u32:0} // FIXME: remove after https://github.com/google/xls/issues/1415 is fixed -> { +proc AxiRamReaderInst { type AxiAr = axi::AxiAr; type AxiR = axi::AxiR; type ReadReq = ram::ReadReq; diff --git a/xls/modules/zstd/memory/mem_writer.x b/xls/modules/zstd/memory/mem_writer.x index f49d147785..60f099f75c 100644 --- a/xls/modules/zstd/memory/mem_writer.x +++ b/xls/modules/zstd/memory/mem_writer.x @@ -55,8 +55,7 @@ pub struct MemWriterDataPacket { enum MemWriterFsm : u2 { RECV_REQ = 0, SEND_WRITE_REQ = 1, - RECV_DATA = 2, - SEND_DATA = 3, + SEND_DATA = 2, } struct MemWriterState< @@ -140,10 +139,6 @@ proc MemWriterInternal< ..state } }, - _ => { - assert!(false, "Invalid state"); - state - } }; let raw_axi_st_frame = match(state.fsm) { diff --git a/xls/modules/zstd/ram_demux.x b/xls/modules/zstd/ram_demux.x index 78a0c594e5..fe395b7121 100644 --- a/xls/modules/zstd/ram_demux.x +++ b/xls/modules/zstd/ram_demux.x @@ -235,7 +235,7 @@ pub proc RamDemux< } } -// FIXME: This process wraps RamDemux with additional logic as a workaround +// This process wraps RamDemux with additional logic as a workaround // to prevent artificial responses on the write channel caused by RAM rewriting. pub proc RamDemuxWrapped< ADDR_WIDTH: u32, @@ -746,7 +746,7 @@ pub proc RamDemuxNaive< } } -// FIXME: This process wraps RamDemux with additional logic as a workaround +// This process wraps RamDemux with additional logic as a workaround // to prevent artificial responses on the write channel caused by RAM rewriting. pub proc RamDemuxNaiveWrapped< ADDR_WIDTH: u32, diff --git a/xls/modules/zstd/refilling_shift_buffer.x b/xls/modules/zstd/refilling_shift_buffer.x index 78f6f2982e..6931373ef7 100644 --- a/xls/modules/zstd/refilling_shift_buffer.x +++ b/xls/modules/zstd/refilling_shift_buffer.x @@ -79,7 +79,7 @@ proc RefillingShiftBufferInternal< DATA_W: u32, ADDR_W: u32, BACKWARDS: bool = {false}, INSTANCE: u32 = {u32:0}, LENGTH_W: u32 = {length_width(DATA_W)}, DATA_W_DIV8: u32 = {DATA_W / u32:8}, - BUFFER_W: u32 = {DATA_W * u32:2}, // TODO: fix implementation detail of ShiftBuffer leaking here + BUFFER_W: u32 = {DATA_W * u32:2}, BUFFER_W_CLOG2: u32 = {std::clog2(BUFFER_W) + u32:1}, >{ type MemReaderReq = mem_reader::MemReaderReq; @@ -175,7 +175,7 @@ proc RefillingShiftBufferInternal< // on send to buffer_data_in_s if the proc sending control requests isn't // receiving the data on the output channel fast enough, but this is true // of any proc that uses MemReader and we don't consider this an issue - let buf_will_have_enough_space = state.future_buf_occupancy <= DATA_W as BufferSize; // TODO: fix implementation detail of ShiftBuffer leaking here + let buf_will_have_enough_space = state.future_buf_occupancy <= DATA_W as BufferSize; let do_refill_cycle = state.fsm == Fsm::REFILLING && buf_will_have_enough_space; // send request to memory for more data under the assumption // that there's enough space in the ShiftBuffer to fit it @@ -442,7 +442,7 @@ const TEST_DATA_W = u32:64; const TEST_ADDR_W = u32:32; const TEST_LENGTH_W = length_width(TEST_DATA_W); const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; -const TEST_BUFFER_W = TEST_DATA_W * u32:2; // TODO: fix implementation detail of ShiftBuffer leaking here +const TEST_BUFFER_W = TEST_DATA_W * u32:2; const TEST_BUFFER_W_CLOG2 = std::clog2(TEST_BUFFER_W); proc RefillingShiftBufferTest { diff --git a/xls/modules/zstd/rtl/cocotb_public_vars.vlt b/xls/modules/zstd/rtl/cocotb_public_vars.vlt index 8a75c59ae6..cc9ad91801 100644 --- a/xls/modules/zstd/rtl/cocotb_public_vars.vlt +++ b/xls/modules/zstd/rtl/cocotb_public_vars.vlt @@ -9,7 +9,7 @@ public_flat_rd -module "xls_modules_zstd_comp_lookup_dec__ZstdDecoderInst__ZstdD public_flat_rd -module "xls_modules_zstd_fse_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__FseDecoder_0__64_15_32_1_64_7_next" -var "*" public_flat_rd -module "xls_modules_zstd_sequence_executor__ZstdDecoderInst__ZstdDecoder_0__SequenceExecutor_0__32_64_64_0_0_0_13_8192_65536_next" -var "*" public_flat_rd -module "xls_modules_zstd_fse_table_creator__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__SequenceDecoder_0__FseLookupDecoder_0__CompLookupDecoder_0__FseTableCreator_0__8_16_1_15_32_1_9_8_1_8_16_1_next_inst16" -var "*" -public_flat_rd -module "xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next" -var "*" +public_flat_rd -module "xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0__256_8_32_7_next" -var "*" public_flat_rd -module "xls_modules_zstd_huffman_ctrl__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanControlAndSequence_0__32_64_next" -var "*" public_flat_rd -module "xls_modules_zstd_block_header_dec__ZstdDecoderInst__ZstdDecoder_0__BlockHeaderDecoder_0__32_64_next" -var "*" public_flat_rd -module "xls_modules_zstd_raw_block_dec__ZstdDecoderInst__ZstdDecoder_0__RawBlockDecoder_0__32_64_next" -var "*" diff --git a/xls/modules/zstd/sequence_conf_dec.x b/xls/modules/zstd/sequence_conf_dec.x index 72c3e3ceee..13c1317405 100644 --- a/xls/modules/zstd/sequence_conf_dec.x +++ b/xls/modules/zstd/sequence_conf_dec.x @@ -152,8 +152,6 @@ pub proc SequenceConfDecoder { // max number of bytes that the header can have, see RFC8878 Section 3.1.1.3.2.1. length: uN[AXI_ADDR_W]:4, }); - // TODO: handle multiple receives on mem_rd_resp_r when AXI_DATA_W < 32 - const_assert!(AXI_DATA_W >= u32:32); let (tok, raw) = recv(tok, mem_rd_resp_r); let (header, length) = parse_sequence_conf(raw.data[:32]); let tok = send(tok, resp_s, Resp { diff --git a/xls/modules/zstd/sequence_dec.x b/xls/modules/zstd/sequence_dec.x index f51a6886c8..2b09669165 100644 --- a/xls/modules/zstd/sequence_dec.x +++ b/xls/modules/zstd/sequence_dec.x @@ -26,7 +26,6 @@ import xls.modules.zstd.ram_demux; import xls.modules.zstd.ram_mux; import xls.modules.zstd.refilling_shift_buffer; import xls.modules.zstd.fse_dec; -import xls.modules.zstd.shift_buffer; import xls.modules.zstd.fse_table_creator; @@ -36,6 +35,7 @@ type SequenceExecutorMessageType = common::SequenceExecutorMessageType; type BlockSyncData = common::BlockSyncData; type CommandConstructorData = common::CommandConstructorData; type CompressionMode = common::CompressionMode; +type Remainder = common::Remainder; enum SequenceDecoderStatus: u3 { OK = 0, @@ -53,22 +53,6 @@ pub struct SequenceDecoderResp { status: SequenceDecoderStatus, } -enum SequenceDecoderFSM: u3 { - IDLE = 0, - DECODE_SEQUENCE_HEADER = 1, - PREPARE_LL_TABLE = 2, - PREPARE_OF_TABLE = 3, - PREPARE_ML_TABLE = 4, - - ERROR = 7, -} - -struct SequenceDecoderState { - fsm: SequenceDecoderFSM, - req: SequenceDecoderReq, - conf_resp: sequence_conf_dec::SequenceConfDecoderResp, -} - struct FseLookupCtrlReq { ll_mode: CompressionMode, ml_mode: CompressionMode, @@ -152,7 +136,6 @@ pub proc FseLookupCtrlInternal { } } - pub proc FseLookupCtrl { type Req = FseLookupCtrlReq; type Resp = FseLookupCtrlResp; @@ -293,154 +276,191 @@ pub proc FseLookupCtrlInst { next(state: ()) {} } -const TEST_FLC_AXI_ADDR_W = u32:32; +#[test_proc] +proc FseLookupCtrlTest { + type Req = FseLookupCtrlReq; + type Resp = FseLookupCtrlResp; -//#[test_proc] -//proc FseLookupCtrlTest { -// -// type Req = FseLookupCtrlReq; -// type Resp = FseLookupCtrlResp; -// -// type Addr = uN[TEST_FLC_AXI_ADDR_W]; -// -// type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; -// type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; -// type FseLookupDecoderStatus = fse_lookup_dec::FseLookupDecoderStatus; -// -// terminator: chan out; -// -// req_s: chan out; -// resp_r: chan in; -// fld_req_r: chan in; -// fld_resp_s: chan out; -// demux_req_r: chan in; -// demux_resp_s: chan<()> out; -// -// init {} -// -// config( -// terminator: chan out, -// ) { -// let (req_s, req_r) = chan("req"); -// let (resp_s, resp_r) = chan("resp"); -// let (fld_req_s, fld_req_r) = chan("fld_req"); -// let (fld_resp_s, fld_resp_r) = chan("fld_resp"); -// let (demux_req_s, demux_req_r) = chan("demux_req"); -// let (demux_resp_s, demux_resp_r) = chan<()>("demux_resp"); -// -// spawn FseLookupCtrl( -// req_r, resp_s, -// fld_req_s, fld_resp_r, -// demux_req_s, demux_resp_r, -// ); -// -// ( -// terminator, -// req_s, resp_r, -// fld_req_r, fld_resp_s, -// demux_req_r, demux_resp_s, -// ) -// } -// -// next(state: ()) { -// -// // Decode all the tables -// // --------------------- -// -// // Start -// let tok = join(); -// let tok = send(tok, req_s, Req { ll: true, of: true, ml: true, addr: Addr:0 }); -// -// // Select LL ( u2:0 ) -// let (tok, demux_req) = recv(tok, demux_req_r); -// assert_eq(demux_req, u2:0); -// -// let tok = send(tok, demux_resp_s, ()); -// let (tok, fld_req) = recv(tok, fld_req_r); -// -// assert_eq(fld_req, zero!()); -// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); -// -// // Select OF ( u2:1 ) -// let (tok, demux_req) = recv(tok, demux_req_r); -// assert_eq(demux_req, u2:1); -// -// let tok = send(tok, demux_resp_s, ()); -// let (tok, fld_req) = recv(tok, fld_req_r); -// -// assert_eq(fld_req, zero!()); -// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); -// -// // Select ML ( u2:2 ) -// let (tok, demux_req) = recv(tok, demux_req_r); -// assert_eq(demux_req, u2:2); -// -// let tok = send(tok, demux_resp_s, ()); -// let (tok, _fld_req) = recv(tok, fld_req_r); -// -// assert_eq(fld_req, zero!()); -// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); -// -// // Stop -// let (tok, resp) = recv(tok, resp_r); -// assert_eq(resp, FseLookupCtrlResp {}); -// -// // Decode only LL and ML -// // --------------------- -// -// // Start -// let tok = join(); -// let tok = send(tok, req_s, Req { ll: true, of: false, ml: true, addr: Addr:0 }); -// -// // Select LL ( u2:0 ) -// let (tok, demux_req) = recv(tok, demux_req_r); -// assert_eq(demux_req, u2:0); -// -// let tok = send(tok, demux_resp_s, ()); -// let (tok, fld_req) = recv(tok, fld_req_r); -// -// assert_eq(fld_req, zero!()); -// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); -// -// // Select ML ( u2:2 ) -// let (tok, demux_req) = recv(tok, demux_req_r); -// assert_eq(demux_req, u2:2); -// -// let tok = send(tok, demux_resp_s, ()); -// let (tok, _fld_req) = recv(tok, fld_req_r); -// -// assert_eq(fld_req, zero!()); -// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); -// -// // Stop -// let (tok, resp) = recv(tok, resp_r); -// assert_eq(resp, FseLookupCtrlResp {}); -// -// -// // Decode only OF -// // --------------------- -// -// // Start -// let tok = join(); -// let tok = send(tok, req_s, Req { ll: false, of: true, ml: false, addr: Addr:0 }); -// -// // Select OF ( u2:1 ) -// let (tok, demux_req) = recv(tok, demux_req_r); -// assert_eq(demux_req, u2:1); -// -// let tok = send(tok, demux_resp_s, ()); -// let (tok, fld_req) = recv(tok, fld_req_r); -// -// assert_eq(fld_req, zero!()); -// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); -// -// // Stop -// let (tok, resp) = recv(tok, resp_r); -// assert_eq(resp, FseLookupCtrlResp {}); -// -// let tok = send(tok, terminator, true); -// } -//} + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; + type FseLookupDecoderStatus = fse_lookup_dec::FseLookupDecoderStatus; + + terminator: chan out; + + req_s: chan out; + resp_r: chan in; + fld_req_r: chan in; + fld_resp_s: chan out; + demux_req_r: chan in; + demux_resp_s: chan<()> out; + + init {} + + config( + terminator: chan out, + ) { + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (fld_req_s, fld_req_r) = chan("fld_req"); + let (fld_resp_s, fld_resp_r) = chan("fld_resp"); + let (demux_req_s, demux_req_r) = chan("demux_req"); + let (demux_resp_s, demux_resp_r) = chan<()>("demux_resp"); + + spawn FseLookupCtrl( + req_r, resp_s, + fld_req_s, fld_resp_r, + demux_req_s, demux_resp_r, + ); + + ( + terminator, + req_s, resp_r, + fld_req_r, fld_resp_s, + demux_req_r, demux_resp_s, + ) + } + + next(state: ()) { + // Start + let tok = join(); + let tok = send(tok, req_s, Req { + ll_mode: CompressionMode::COMPRESSED, + ml_mode: CompressionMode::RLE, + of_mode: CompressionMode::COMPRESSED, + }); + + // Select LL ( u2:0 ) + let (tok, demux_req) = recv(tok, demux_req_r); + assert_eq(demux_req, u2:0); + + let tok = send(tok, demux_resp_s, ()); + let (tok, fld_req) = recv(tok, fld_req_r); + + assert_eq(fld_req, zero!()); + let tok = send(tok, fld_resp_s, FseLookupDecoderResp { + status: FseLookupDecoderStatus::OK, + accuracy_log: AccuracyLog:9, + remainder: zero!(), + }); + + // Select OF ( u2:1 ) + let (tok, demux_req) = recv(tok, demux_req_r); + assert_eq(demux_req, u2:1); + + let tok = send(tok, demux_resp_s, ()); + let (tok, fld_req) = recv(tok, fld_req_r); + + assert_eq(fld_req, zero!()); + let tok = send(tok, fld_resp_s, FseLookupDecoderResp { + status: FseLookupDecoderStatus::OK, + accuracy_log: AccuracyLog:9, + remainder: zero!(), + }); + + // Select ML ( u2:2 ) + let (tok, demux_req) = recv(tok, demux_req_r); + assert_eq(demux_req, u2:2); + + let tok = send(tok, demux_resp_s, ()); + let (tok, _fld_req) = recv(tok, fld_req_r); + + assert_eq(fld_req, zero!()); + let tok = send(tok, fld_resp_s, FseLookupDecoderResp { + status: FseLookupDecoderStatus::OK, + accuracy_log: AccuracyLog:8, + remainder: zero!(), + }); + + // Stop + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, FseLookupCtrlResp { + ll_accuracy_log: u7:9, + ml_accuracy_log: u7:8, + of_accuracy_log: u7:9, + }); + + // Decode only LL and ML + + // Start + let tok = join(); + let tok = send(tok, req_s, Req { + ll_mode: CompressionMode::COMPRESSED, + ml_mode: CompressionMode::COMPRESSED, + of_mode: CompressionMode::REPEAT + }); + + // Select LL ( u2:0 ) + let (tok, demux_req) = recv(tok, demux_req_r); + assert_eq(demux_req, u2:0); + + let tok = send(tok, demux_resp_s, ()); + let (tok, fld_req) = recv(tok, fld_req_r); + + assert_eq(fld_req, zero!()); + let tok = send(tok, fld_resp_s, FseLookupDecoderResp { + status: FseLookupDecoderStatus::OK, + accuracy_log: AccuracyLog:5, + remainder: zero!(), + + }); + + // Select ML ( u2:2 ) + let (tok, demux_req) = recv(tok, demux_req_r); + assert_eq(demux_req, u2:2); + + let tok = send(tok, demux_resp_s, ()); + let (tok, _fld_req) = recv(tok, fld_req_r); + + assert_eq(fld_req, zero!()); + let tok = send(tok, fld_resp_s, FseLookupDecoderResp { + status: FseLookupDecoderStatus::OK, + accuracy_log: AccuracyLog:7, + remainder: zero!(), + }); + + // Stop + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, FseLookupCtrlResp { + ll_accuracy_log: u7:5, + ml_accuracy_log: u7:7, + of_accuracy_log: u7:9 + }); + + // Decode only OF + + // Start + let tok = join(); + let tok = send(tok, req_s, Req { + ll_mode: CompressionMode::PREDEFINED, + ml_mode: CompressionMode::PREDEFINED, + of_mode: CompressionMode::COMPRESSED + }); + + // Select OF ( u2:1 ) + let (tok, demux_req) = recv(tok, demux_req_r); + assert_eq(demux_req, u2:1); + + let tok = send(tok, demux_resp_s, ()); + let (tok, fld_req) = recv(tok, fld_req_r); + + assert_eq(fld_req, zero!()); + let tok = send(tok, fld_resp_s, FseLookupDecoderResp { + status: FseLookupDecoderStatus::OK, + accuracy_log: AccuracyLog:7, + remainder: zero!(), + }); + + // Stop + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, FseLookupCtrlResp { + ll_accuracy_log: u7:6, + ml_accuracy_log: u7:6, + of_accuracy_log: u7:7 + }); + + let tok = send(tok, terminator, true); + } +} pub proc SequenceDecoderCtrl< AXI_ADDR_W: u32, AXI_DATA_W: u32, @@ -449,8 +469,6 @@ pub proc SequenceDecoderCtrl< > { type Req = SequenceDecoderReq; type Resp = SequenceDecoderResp; - type State = SequenceDecoderState; - type FSM = SequenceDecoderFSM; type Status = SequenceDecoderStatus; type Addr = uN[AXI_ADDR_W]; @@ -679,174 +697,249 @@ const SDC_TEST_AXI_DATA_W = u32:64; const SDC_TEST_REFILLING_SB_DATA_W = {SDC_TEST_AXI_DATA_W}; const SDC_TEST_REFILLING_SB_LENGTH_W = refilling_shift_buffer::length_width(SDC_TEST_AXI_DATA_W); -//#[test_proc] -//proc SequenceDecoderCtrlTest { -// -// type Req = SequenceDecoderReq; -// type Resp = SequenceDecoderResp; -// type Status = SequenceDecoderStatus; -// -// type CompressionMode = common::CompressionMode; -// type Addr = uN[SDC_TEST_AXI_ADDR_W]; -// -// type SequenceConf = common::SequenceConf; -// type SequenceConfDecoderReq = sequence_conf_dec::SequenceConfDecoderReq; -// type SequenceConfDecoderResp = sequence_conf_dec::SequenceConfDecoderResp; -// type SequenceConfDecoderStatus = sequence_conf_dec::SequenceConfDecoderStatus; -// -// type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; -// type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; -// type FseLookupDecoderStatus = fse_lookup_dec::FseLookupDecoderStatus; -// -// type RefillingShiftBufferStart = refilling_shift_buffer::RefillStart; -// type RefillingShiftBufferError = refilling_shift_buffer::RefillingShiftBufferInput; -// type RefillingShiftBufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; -// type RefillingShiftBufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; -// -// type FseDecoderCtrl = fse_dec::FseDecoderCtrl; -// type FseDecoderFinish = fse_dec::FseDecoderFinish; -// -// terminator: chan out; -// -// sd_req_s: chan out; -// sd_resp_r: chan in; -// -// scd_req_r: chan in; -// scd_resp_s: chan out; -// -// fld_req_r: chan in; -// fld_resp_s: chan out; -// -// fse_demux_req_r: chan in; -// fse_demux_resp_s: chan<()> out; -// -// ll_demux_req_r: chan in; -// ll_demux_resp_s: chan<()> out; -// -// of_demux_req_r: chan in; -// of_demux_resp_s: chan<()> out; -// -// ml_demux_req_r: chan in; -// ml_demux_resp_s: chan<()> out; -// -// fd_rsb_start_req_r: chan in; -// fd_rsb_stop_flush_req_r: chan<()> in; -// fd_rsb_flushing_done_s: chan<()> out; -// -// fd_ctrl_r: chan in; -// fd_finish_s: chan out; -// -// init { } -// -// config(terminator: chan out) { -// let (sd_req_s, sd_req_r) = chan("sd_req"); -// let (sd_resp_s, sd_resp_r) = chan("sd_resp"); -// -// let (scd_req_s, scd_req_r) = chan("scd_req"); -// let (scd_resp_s, scd_resp_r) = chan("scd_resp"); -// -// let (fld_req_s, fld_req_r) = chan("fld_req"); -// let (fld_resp_s, fld_resp_r) = chan("fld_resp"); -// -// let (fse_demux_req_s, fse_demux_req_r) = chan("fse_demux_req"); -// let (fse_demux_resp_s, fse_demux_resp_r) = chan<()>("fse_demux_resp"); -// -// let (ll_demux_req_s, ll_demux_req_r) = chan("ll_demux_req"); -// let (ll_demux_resp_s, ll_demux_resp_r) = chan<()>("ll_demux_resp"); -// -// let (of_demux_req_s, of_demux_req_r) = chan("of_demux_req"); -// let (of_demux_resp_s, of_demux_resp_r) = chan<()>("of_demux_resp"); -// -// let (ml_demux_req_s, ml_demux_req_r) = chan("ml_demux_req"); -// let (ml_demux_resp_s, ml_demux_resp_r) = chan<()>("ml_demux_resp"); -// -// let (fd_rsb_start_req_s, fd_rsb_start_req_r) = chan("fd_rsb_start_req"); -// let (fd_rsb_stop_flush_req_s, fd_rsb_stop_flush_req_r) = chan<()>("fd_rsb_stop_flush_req"); -// let (fd_rsb_flushing_done_s, fd_rsb_flushing_done_r) = chan<()>("fd_rsb_flushing_done"); -// -// let (fd_ctrl_s, fd_ctrl_r) = chan("fd_ctrl"); -// let (fd_finish_s, fd_finish_r) = chan("fd_finish"); -// -// spawn SequenceDecoderCtrl< -// SDC_TEST_AXI_ADDR_W, SDC_TEST_AXI_DATA_W -// >( -// sd_req_r, sd_resp_s, -// scd_req_s, scd_resp_r, -// fld_req_s, fld_resp_r, -// fse_demux_req_s, fse_demux_resp_r, -// ll_demux_req_s, ll_demux_resp_r, -// of_demux_req_s, of_demux_resp_r, -// ml_demux_req_s, ml_demux_resp_r, -// fd_rsb_start_req_s, fd_rsb_stop_flush_req_s, fd_rsb_flushing_done_r, -// fd_ctrl_s, fd_finish_r, -// ); -// -// ( -// terminator, -// sd_req_s, sd_resp_r, -// scd_req_r, scd_resp_s, -// fld_req_r, fld_resp_s, -// fse_demux_req_r, fse_demux_resp_s, -// ll_demux_req_r, ll_demux_resp_s, -// of_demux_req_r, of_demux_resp_s, -// ml_demux_req_r, ml_demux_resp_s, -// fd_rsb_start_req_r, fd_rsb_stop_flush_req_r, fd_rsb_flushing_done_s, -// fd_ctrl_r, fd_finish_s, -// ) -// } -// -// next(state: ()) { -// let tok = join(); -// -// let tok = send(tok, sd_req_s, Req { -// start_addr: Addr:0x1000, -// end_addr: Addr:0x1012, -// }); -// -// let (tok, scd_req) = recv(tok, scd_req_r); -// assert_eq(scd_req, SequenceConfDecoderReq { addr: Addr: 0x1000 }); -// -// let scd_resp = SequenceConfDecoderResp { -// header: SequenceConf { -// sequence_count: u17:1, -// literals_mode: CompressionMode::PREDEFINED, -// offset_mode: CompressionMode::RLE, -// match_mode: CompressionMode::COMPRESSED, -// }, -// length: u3:5, -// status: SequenceConfDecoderStatus::OKAY -// }; -// let tok = send(tok, scd_resp_s, scd_resp); -// -// let (tok, demux_req) = recv(tok, fse_demux_req_r); -// assert_eq(demux_req, u2:2); -// let tok = send(tok, fse_demux_resp_s, ()); -// -// let (tok, fld_req) = recv(tok, fld_req_r); -// assert_eq(fld_req, FseLookupDecoderReq { -// addr: Addr:0x1005, -// }); -// -// let tok = send(tok, fld_resp_s, FseLookupDecoderResp {status: FseLookupDecoderStatus::OK}); -// -// let (tok, ll_demux) = recv(tok, ll_demux_req_r); -// assert_eq(ll_demux, u1:0); -// let tok = send(tok, ll_demux_resp_s, ()); -// -// let (tok, ml_demux) = recv(tok, ml_demux_req_r); -// assert_eq(ml_demux, u1:1); -// let tok = send(tok, ml_demux_resp_s, ()); -// -// let (tok, of_demux) = recv(tok, of_demux_req_r); -// assert_eq(of_demux, u1:1); -// let tok = send(tok, of_demux_resp_s, ()); -// -// let (tok, fd_ctrl) = recv(tok, fd_ctrl_r); -// assert_eq(fd_ctrl, zero!()); -// -// send(tok, terminator, true); -// } -//} +#[test_proc] +proc SequenceDecoderCtrlTest { + + type Req = SequenceDecoderReq; + type Resp = SequenceDecoderResp; + type Status = SequenceDecoderStatus; + + type CompressionMode = common::CompressionMode; + type Addr = uN[SDC_TEST_AXI_ADDR_W]; + + type SequenceConf = common::SequenceConf; + type SequenceConfDecoderReq = sequence_conf_dec::SequenceConfDecoderReq; + type SequenceConfDecoderResp = sequence_conf_dec::SequenceConfDecoderResp; + type SequenceConfDecoderStatus = sequence_conf_dec::SequenceConfDecoderStatus; + + type FseLookupDecoderReq = fse_lookup_dec::FseLookupDecoderReq; + type FseLookupDecoderResp = fse_lookup_dec::FseLookupDecoderResp; + type FseLookupDecoderStatus = fse_lookup_dec::FseLookupDecoderStatus; + + type RefillingShiftBufferStart = refilling_shift_buffer::RefillStart; + type RefillingShiftBufferError = refilling_shift_buffer::RefillingShiftBufferInput; + type RefillingShiftBufferOutput = refilling_shift_buffer::RefillingShiftBufferOutput; + type RefillingShiftBufferCtrl = refilling_shift_buffer::RefillingShiftBufferCtrl; + + type FseDecoderCtrl = fse_dec::FseDecoderCtrl; + type FseDecoderFinish = fse_dec::FseDecoderFinish; + + terminator: chan out; + + sd_req_s: chan out; + sd_resp_r: chan in; + + scd_req_r: chan in; + scd_resp_s: chan out; + + fld_req_r: chan in; + fld_resp_s: chan out; + + fld_demux_req_r: chan in; + fld_demux_resp_s: chan<()> out; + + ll_demux_req_r: chan in; + ll_demux_resp_s: chan<()> out; + + of_demux_req_r: chan in; + of_demux_resp_s: chan<()> out; + + ml_demux_req_r: chan in; + ml_demux_resp_s: chan<()> out; + + fd_rsb_start_req_r: chan in; + fd_rsb_stop_flush_req_r: chan<()> in; + fd_rsb_flushing_done_s: chan<()> out; + + fld_rsb_start_req_r: chan in; + fld_rsb_stop_flush_req_r: chan<()> in; + fld_rsb_flushing_done_s: chan<()> out; + + fd_ctrl_r: chan in; + fd_finish_s: chan out; + + init { } + + config(terminator: chan out) { + let (sd_req_s, sd_req_r) = chan("sd_req"); + let (sd_resp_s, sd_resp_r) = chan("sd_resp"); + + let (scd_req_s, scd_req_r) = chan("scd_req"); + let (scd_resp_s, scd_resp_r) = chan("scd_resp"); + + let (fld_req_s, fld_req_r) = chan("fld_req"); + let (fld_resp_s, fld_resp_r) = chan("fld_resp"); + + let (fld_demux_req_s, fld_demux_req_r) = chan("fld_demux_req"); + let (fld_demux_resp_s, fld_demux_resp_r) = chan<()>("fld_demux_resp"); + + let (ll_demux_req_s, ll_demux_req_r) = chan("ll_demux_req"); + let (ll_demux_resp_s, ll_demux_resp_r) = chan<()>("ll_demux_resp"); + + let (of_demux_req_s, of_demux_req_r) = chan("of_demux_req"); + let (of_demux_resp_s, of_demux_resp_r) = chan<()>("of_demux_resp"); + + let (ml_demux_req_s, ml_demux_req_r) = chan("ml_demux_req"); + let (ml_demux_resp_s, ml_demux_resp_r) = chan<()>("ml_demux_resp"); + + let (fd_rsb_start_req_s, fd_rsb_start_req_r) = chan("fd_rsb_start_req"); + let (fd_rsb_stop_flush_req_s, fd_rsb_stop_flush_req_r) = chan<()>("fd_rsb_stop_flush_req"); + let (fd_rsb_flushing_done_s, fd_rsb_flushing_done_r) = chan<()>("fd_rsb_flushing_done"); + + let (fld_rsb_start_req_s, fld_rsb_start_req_r) = chan("fld_rsb_start_req"); + let (fld_rsb_stop_flush_req_s, fld_rsb_stop_flush_req_r) = chan<()>("fld_rsb_stop_flush_req"); + let (fld_rsb_flushing_done_s, fld_rsb_flushing_done_r) = chan<()>("fld_rsb_flushing_done"); + + let (fd_ctrl_s, fd_ctrl_r) = chan("fd_ctrl"); + let (fd_finish_s, fd_finish_r) = chan("fd_finish"); + + spawn SequenceDecoderCtrl< + SDC_TEST_AXI_ADDR_W, SDC_TEST_AXI_DATA_W + >( + sd_req_r, sd_resp_s, + scd_req_s, scd_resp_r, + fld_req_s, fld_resp_r, + fld_demux_req_s, fld_demux_resp_r, + ll_demux_req_s, ll_demux_resp_r, + of_demux_req_s, of_demux_resp_r, + ml_demux_req_s, ml_demux_resp_r, + fd_rsb_start_req_s, fd_rsb_stop_flush_req_s, fd_rsb_flushing_done_r, + fld_rsb_start_req_s, fld_rsb_stop_flush_req_s, fld_rsb_flushing_done_r, + fd_ctrl_s, fd_finish_r + ); + + ( + terminator, + sd_req_s, sd_resp_r, + scd_req_r, scd_resp_s, + fld_req_r, fld_resp_s, + fld_demux_req_r, fld_demux_resp_s, + ll_demux_req_r, ll_demux_resp_s, + of_demux_req_r, of_demux_resp_s, + ml_demux_req_r, ml_demux_resp_s, + fd_rsb_start_req_r, fd_rsb_stop_flush_req_r, fd_rsb_flushing_done_s, + fld_rsb_start_req_r, fld_rsb_stop_flush_req_r, fld_rsb_flushing_done_s, + fd_ctrl_r, fd_finish_s + ) + } + + next(state: ()) { + let tok = join(); + + trace_fmt!("Sending SequenceDecoder request"); + let tok = send(tok, sd_req_s, Req { + start_addr: Addr:0x1000, + end_addr: Addr:0x1012, + sync: BlockSyncData { id: u32:1, last_block: false }, + literals_count: u20:1234, + }); + + let (tok, scd_req) = recv(tok, scd_req_r); + trace_fmt!("Received SequenceConfDecoder response"); + assert_eq(scd_req, SequenceConfDecoderReq { + addr: Addr: 0x1000 + }); + + let scd_resp = SequenceConfDecoderResp { + header: SequenceConf { + sequence_count: u17:1, + literals_mode: CompressionMode::PREDEFINED, + offset_mode: CompressionMode::RLE, + match_mode: CompressionMode::COMPRESSED, + }, + length: u3:5, + status: SequenceConfDecoderStatus::OKAY + }; + trace_fmt!("Sending SequenceConfDecoder response"); + let tok = send(tok, scd_resp_s, scd_resp); + + let (tok, fld_rsb_start_req) = recv(tok, fld_rsb_start_req_r); + trace_fmt!("Received RefillingShiftBuffer start request: {:#x}", + fld_rsb_start_req + ); + + let (tok, demux_req) = recv(tok, fld_demux_req_r); + trace_fmt!("Received FseLookupDecoder demux request: {:#x}", demux_req); + assert_eq(demux_req, u2:1); + + let tok = send(tok, fld_demux_resp_s, ()); + trace_fmt!("Sending FSELookupDecoder demux response {:#x}", ()); + + let (tok, fld_req) = recv(tok, fld_req_r); + trace_fmt!("Received FSELookupDecoder request"); + assert_eq(fld_req, FseLookupDecoderReq { + is_rle: true, + remainder: zero!() + }); + + trace_fmt!("Sending FSELookupDecoder response"); + let tok = send(tok, fld_resp_s, FseLookupDecoderResp { + status: FseLookupDecoderStatus::OK, + remainder: zero!(), + accuracy_log: AccuracyLog:8, + }); + + let (tok, demux_req) = recv(tok, fld_demux_req_r); + trace_fmt!("Received FseLookupDecoder demux request: {:#x}", demux_req); + assert_eq(demux_req, u2:2); + + let tok = send(tok, fld_demux_resp_s, ()); + trace_fmt!("Sending FSELookupDecoder demux response {:#x}", ()); + + let (tok, fld_req) = recv(tok, fld_req_r); + trace_fmt!("[TEST] Received FSELookupDecoder request"); + assert_eq(fld_req, FseLookupDecoderReq { + is_rle: false, + remainder: zero!() + }); + + trace_fmt!("Sending FSELookupDecoder response"); + let tok = send(tok, fld_resp_s, FseLookupDecoderResp { + status: FseLookupDecoderStatus::OK, + remainder: zero!(), + accuracy_log: AccuracyLog:7, + }); + + let (tok, _) = recv(tok, fld_rsb_stop_flush_req_r); + trace_fmt!("Received RefillingShiftBuffer stop flush"); + trace_fmt!("Sending RefillingShiftBuffer flushing done"); + let tok = send(tok, fld_rsb_flushing_done_s, ()); + + let (tok, ll_demux) = recv(tok, ll_demux_req_r); + trace_fmt!("Received LL demux request"); + assert_eq(ll_demux, u1:0); + trace_fmt!("Sending LL demux response"); + let tok = send(tok, ll_demux_resp_s, ()); + + let (tok, of_demux) = recv(tok, of_demux_req_r); + trace_fmt!("Received OF demux request"); + assert_eq(of_demux, u1:1); + trace_fmt!("Sending OF demux response"); + let tok = send(tok, of_demux_resp_s, ()); + + trace_fmt!("Received ML demux request"); + let (tok, ml_demux) = recv(tok, ml_demux_req_r); + assert_eq(ml_demux, u1:1); + trace_fmt!("Received ML demux response"); + let tok = send(tok, ml_demux_resp_s, ()); + + let (tok, fd_ctrl) = recv(tok, fd_ctrl_r); + trace_fmt!("Received Fse decoder ctrl"); + assert_eq(fd_ctrl, FseDecoderCtrl { + sync: BlockSyncData { + id: u32:1, + last_block: u1:0 + }, + sequences_count: u24:1, + literals_count: u20:1234, + of_acc_log: u7:8, + ll_acc_log: u7:6, + ml_acc_log: u7:7 + }); + + send(tok, terminator, true); + } +} pub proc SequenceDecoder< AXI_ADDR_W: u32, AXI_DATA_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, @@ -1824,66 +1917,6 @@ const SEQ_DEC_TESTCASES: (u32, u64[32], u32, SequenceExecutorPacket[64])[4] = [ zero!(), ... ] ), - // Test case N (WARNING: long test running time) - // 3 custom lookup tables with accuracy log 9, 8 and 9 - // decodecorpus -pdata.out -odata.in -s58745 --block-type=2 --content-size --literal-type=0 --max-block-size-log=7 - // ( - // u32:32, - // u64[32]:[ - // u64:0x0, u64:0x0, - // u64:0xFC0502602814A804, - // u64:0x505040131FF60604, - // u64:0xFE01C080140FE030, - // u64:0x4040E65B84521B01, - // u64:0x0, ... - // ], - // u32:7, - // SequenceExecutorPacket[64]:[ - // SequenceExecutorPacket { - // msg_type: SequenceExecutorMessageType::LITERAL, - // length: u64:0x0005, - // content: u64:0x0, - // last: false, - // }, - // SequenceExecutorPacket { - // msg_type: SequenceExecutorMessageType::SEQUENCE, - // length: u64:0x0004, - // content: u64:0x0006, - // last: false, - // }, - // SequenceExecutorPacket { - // msg_type: SequenceExecutorMessageType::SEQUENCE, - // length: u64:0x0004, - // content: u64:0x0002, - // last: false, - // }, - // SequenceExecutorPacket { - // msg_type: SequenceExecutorMessageType::LITERAL, - // length: u64:0x0011, - // content: u64:0x0, - // last: false, - // }, - // SequenceExecutorPacket { - // msg_type: SequenceExecutorMessageType::SEQUENCE, - // length: u64:0x0004, - // content: u64:0x000a, - // last: false, - // }, - // SequenceExecutorPacket { - // msg_type: SequenceExecutorMessageType::LITERAL, - // length: u64:0x002b, - // content: u64:0x0, - // last: false, - // }, - // SequenceExecutorPacket { - // msg_type: SequenceExecutorMessageType::SEQUENCE, - // length: u64:0x0006, - // content: u64:0x0023, - // last: true, - // }, - // zero!(), ... - // ] - // ), ]; type Base = u16; @@ -2626,5 +2659,4 @@ proc SequenceDecoderTest { send(tok, terminator, true); } - } diff --git a/xls/modules/zstd/shift_buffer.x b/xls/modules/zstd/shift_buffer.x index 4cf70d861f..96567d202a 100644 --- a/xls/modules/zstd/shift_buffer.x +++ b/xls/modules/zstd/shift_buffer.x @@ -68,9 +68,6 @@ pub proc ShiftBufferAligner< init {zero!()} next(state: State) { - // FIXME: Remove when https://github.com/google/xls/issues/1368 is resolved - type Inter = ShiftBufferPacket; - let tok = join(); let (tok0, data) = recv(tok, input_r); @@ -150,16 +147,16 @@ proc ShiftBufferAlignerTest { struct ShiftBufferStorageState { buffer: bits[BUFFER_WIDTH], // The storage element. - buffer_cnt: bits[LENGTH_WIDTH + u32:2], // Number of valid bits in the buffer. - read_ptr: bits[LENGTH_WIDTH + u32:2], // First occupied bit in the buffer when buffer_cnt > 0. - write_ptr: bits[LENGTH_WIDTH + u32:2], // First free bit in the buffer. + buffer_cnt: bits[LENGTH_WIDTH + u32:1], // Number of valid bits in the buffer. + read_ptr: bits[LENGTH_WIDTH + u32:1], // First occupied bit in the buffer when buffer_cnt > 0. + write_ptr: bits[LENGTH_WIDTH + u32:1], // First free bit in the buffer. cmd: ShiftBufferCtrl, // Received command of ShiftBufferCtrl type. cmd_valid: bool, // Field cmd is valid. } pub proc ShiftBufferStorage { type Buffer = bits[DATA_WIDTH * u32:3]; - type BufferLength = bits[LENGTH_WIDTH + u32:2]; // TODO: where does this "+ u32:2" come from? shouldn't it be number_of_bits_required_to_represent(DATA_WIDTH * u32:3)? + type BufferLength = bits[std::clog2(DATA_WIDTH * u32:3)]; type Data = bits[DATA_WIDTH]; type DataLength = bits[LENGTH_WIDTH]; type State = ShiftBufferStorageState; @@ -191,22 +188,16 @@ pub proc ShiftBufferStorage { type OutputPayload = ShiftBufferPacket; type OutputStatus = ShiftBufferStatus; type DataLength = bits[LENGTH_WIDTH]; - // trace_fmt!("state: {:#x}", state); const _MAX_BUFFER_CNT = (DATA_WIDTH * u32:3) as BufferLength; let shift_buffer_right = state.read_ptr >= (DATA_WIDTH as BufferLength); - // trace_fmt!("shift_buffer_right: {:#x}", shift_buffer_right); let shift_data_left = state.write_ptr >= (DATA_WIDTH as BufferLength) && !shift_buffer_right; - // trace_fmt!("shift_data_left: {:#x}", shift_data_left); let recv_new_input = state.write_ptr < (DATA_WIDTH * u32:2) as BufferLength; - // trace_fmt!("recv_new_input: {:#x}", recv_new_input); let has_enough_data = (state.cmd.length as BufferLength <= state.buffer_cnt); let send_response = state.cmd_valid && has_enough_data; - // trace_fmt!("send_response: {:#x}", send_response); let recv_new_cmd = !state.cmd_valid || send_response; - // trace_fmt!("recv_new_cmd: {:#x}", recv_new_cmd); let tok = join(); @@ -221,13 +212,6 @@ pub proc ShiftBufferStorage { state.write_ptr) }; - // if (shift_buffer_right) { - // trace_fmt!("Shifted data"); - // trace_fmt!("new_buffer: {:#x}", new_buffer); - // trace_fmt!("new_read_ptr: {}", new_read_ptr); - // trace_fmt!("new_write_ptr: {}", new_write_ptr); - // } else { () }; - // Handle incoming writes let (tok_input, wdata, wdata_valid) = recv_if_non_blocking(tok, inter, recv_new_input, zero!()); @@ -246,19 +230,10 @@ pub proc ShiftBufferStorage { (new_buffer, new_write_ptr) }; - // if (wdata_valid) { - // trace_fmt!("Received aligned data {:#x}", wdata); - // trace_fmt!("new_buffer: {:#x}", new_buffer); - // trace_fmt!("new_write_ptr: {}", new_write_ptr); - // } else { () }; - // Handle incoming reads let (tok_ctrl, new_cmd, new_cmd_valid) = recv_if_non_blocking(tok, ctrl, recv_new_cmd, state.cmd); - // if (new_cmd_valid) { - // trace_fmt!("Received new cmd: {}", new_cmd); - // } else {()}; let new_cmd_valid = if recv_new_cmd { new_cmd_valid } else { state.cmd_valid }; // Handle current read @@ -269,9 +244,6 @@ pub proc ShiftBufferStorage { data: math::mask((state.buffer >> state.read_ptr) as Data, state.cmd.length), }; - // trace_fmt!("rdata: {:#x}", rdata); - // trace_fmt!("new_read_ptr: {}", new_read_ptr); - (rdata, new_read_ptr) } else { (zero!(), new_read_ptr) @@ -279,9 +251,6 @@ pub proc ShiftBufferStorage { let tok = join(tok_input, tok_ctrl); send_if(tok, output, send_response, rdata); - // if (send_response) { - // trace_fmt!("Sent out rdata: {:#x}", rdata); - // } else {()}; let new_buffer_cnt = new_write_ptr - new_read_ptr; @@ -299,7 +268,7 @@ pub proc ShiftBufferStorage { } const STORAGE_TEST_DATA_WIDTH = u32:64; -const STORAGE_TEST_LENGTH_WIDTH = length_width(STORAGE_TEST_DATA_WIDTH); +const STORAGE_TEST_LENGTH_WIDTH = std::clog2(STORAGE_TEST_DATA_WIDTH + u32:1); const STORAGE_TEST_DATA_WIDTH_X2 = STORAGE_TEST_DATA_WIDTH * u32:2; #[test_proc] @@ -468,7 +437,7 @@ pub proc ShiftBuffer { const INST_DATA_WIDTH = u32:64; const INST_DATA_WIDTH_X2 = u32:128; -const INST_LENGTH_WIDTH = std::clog2(INST_DATA_WIDTH) + u32:1; +const INST_LENGTH_WIDTH = std::clog2(INST_DATA_WIDTH + u32:1); proc ShiftBufferInst { type Input = ShiftBufferPacket; @@ -520,7 +489,7 @@ proc ShiftBufferStorageInst { } const TEST_DATA_WIDTH = u32:64; -const TEST_LENGTH_WIDTH = std::clog2(TEST_DATA_WIDTH) + u32:1; // TODO: other places in the code use length_width(TEST_DATA_WIDTH) which is clog2(TEST_DATA_WIDTH + 1) instead, why clog2(TEST_DATA_WIDTH) + 1 here? +const TEST_LENGTH_WIDTH = std::clog2(TEST_DATA_WIDTH + u32:1); #[test_proc] proc ShiftBufferTest { diff --git a/xls/modules/zstd/zstd_dec_cocotb_cli.py b/xls/modules/zstd/zstd_dec_cocotb_cli.py index e8b7fa1d37..7e4052a045 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_cli.py +++ b/xls/modules/zstd/zstd_dec_cocotb_cli.py @@ -19,7 +19,7 @@ import pathlib from xls.modules.zstd.zstd_dec_cocotb_common import run_test, check_decoder_compliance from xls.modules.zstd.zstd_dec_detailed_test import detailed_testing_routine -from multiprocessing import cpu_count + @cocotb.test(timeout_time=int(os.getenv("ZSTD_DEC_COCOTB_CLI_TIMEOUT", "5000")), timeout_unit="ms") async def zstd_cli_test(dut): @@ -27,13 +27,17 @@ async def zstd_cli_test(dut): print("input_name: ", input_name) await detailed_testing_routine(dut, input_name) + def usage(): - print(f"usage: {os.path.basename(sys.argv[0])} /abs/path/to/input.zst [timeout_is_ms]") - sys.exit(1) + print( + f"usage: {os.path.basename(sys.argv[0])} /abs/path/to/input.zst [timeout_is_ms]" + ) + sys.exit(1) + if __name__ == "__main__": help = "-h" in sys.argv or "--help" in sys.argv - bad_params = len(sys.argv) not in (2,3) + bad_params = len(sys.argv) not in (2, 3) if bad_params or help: usage() @@ -45,20 +49,24 @@ def usage(): if not check_decoder_compliance(sys.argv[1]): print(f"error: '{sys.argv[1]}' is not suitable for the decoder parameters") sys.exit(1) - + # cocotb doesn't perserve global vars nor sys.argv # we work it around by passing arguments through env os.environ["ZSTD_DEC_COCOTB_CLI_INPUT"] = sys.argv[1] if len(sys.argv) == 3: os.environ["ZSTD_DEC_COCOTB_CLI_TIMEOUT"] = sys.argv[2] - + test_module = [pathlib.Path(__file__).stem] - run_test(test_module, build_args=[ - "-Wno-fatal", - "-Wwarn-ASSIGNIN", - "--trace-fst", # trace in more space-efficient format than vcd - "--no-public-flat-rw", - "-O3", - "--assert", - ], sim="verilator") + run_test( + test_module, + build_args=[ + "-Wno-fatal", + "-Wwarn-ASSIGNIN", + "--trace-fst", # trace in more space-efficient format than vcd + "--no-public-flat-rw", + "-O3", + "--assert", + ], + sim="verilator", + ) diff --git a/xls/modules/zstd/zstd_dec_cocotb_common.py b/xls/modules/zstd/zstd_dec_cocotb_common.py index d18f6e8668..31cfa8a9d4 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_common.py +++ b/xls/modules/zstd/zstd_dec_cocotb_common.py @@ -94,7 +94,6 @@ class NotifyStruct(xlsstruct.XLSStruct): BASE_W = 16 -# TODO replace this with fse_table_record from ./cocotb/ @xlsstruct.xls_dataclass class FseTableRecord(xlsstruct.XLSStruct): base: BASE_W @@ -809,7 +808,7 @@ async def randomized_testing_routine( (axi_buses, cpu, clock) = prepare_test_environment(dut) measurements = [] frame_id = 0 - seed = 2 # FIXME: Dehardcode + seed = 2 for test_case in range(test_cases): if expected_fse_lookups is not None: await test_fse_lookup_decoder(dut, clock, expected_fse_lookups) @@ -822,7 +821,6 @@ async def randomized_testing_routine( if expected_huffman_weights is not None: await test_huffman_weights(dut, clock, expected_huffman_weights) - # FIXME: use delete_on_close=False after moving to python 3.12 with tempfile.NamedTemporaryFile(delete=False) as input_file: # Generate ZSTD frame to temporary file data_generator.GenerateFrame(seed, block_type, input_file.name, literal_type) diff --git a/xls/modules/zstd/zstd_dec_cocotb_test.py b/xls/modules/zstd/zstd_dec_cocotb_test.py index 9b6f260730..b452a60e1d 100644 --- a/xls/modules/zstd/zstd_dec_cocotb_test.py +++ b/xls/modules/zstd/zstd_dec_cocotb_test.py @@ -17,11 +17,9 @@ from xls.modules.zstd.cocotb import data_generator from xls.modules.zstd.zstd_dec_cocotb_common import ( randomized_testing_routine, - pregenerated_testing_routine, run_test, test_csr, test_reset, - FseTableRecord ) @cocotb.test(timeout_time=50, timeout_unit="ms") @@ -48,1181 +46,14 @@ async def zstd_rle_frames_test(dut): await randomized_testing_routine(dut, test_cases, block_type) -# Tests with pregenerated inputs -# -# block type and literal type in arguments and file names reflect what was used -# for generating them. The file names also contain the value of seed but the -# bazel environment is not hermetic in terms of reproducible results with the -# same seed value -# The tests are disabled by default as none of them passes currently. -# Use them to verify progress in specific parts of the decoder. - -# TODO the workdir / data relation is weird. How to pass this better? -PREGENERATED_FILES_DIR = "../xls/modules/zstd/data/" - - -@cocotb.test(timeout_time=2000, timeout_unit="ms") -async def pregenerated_compressed_raw_1(dut): - input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_raw_1.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=2000, timeout_unit="ms") -async def pregenerated_compressed_raw_2(dut): - input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_raw_2.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=2000, timeout_unit="ms") -async def pregenerated_compressed_rle_1(dut): - input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_rle_1.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def pregenerated_compressed_rle_2(dut): - input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_rle_2.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def pregenerated_compressed_random_1(dut): - input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_random_1.zst" - test_cases = 1 - - expected_huffman_weights = [ - [ - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x01000100, - 0x06000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000010, - 0x00100030, - 0x00700000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000001, - 0x00010003, - 0x00080000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x10001000, - 0x50000000, - ], - [ - 0x02000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000010, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00003000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00010000, - ], - ] - - expected_huffman_codes = [ - [ - {"code": 0x00, "length": 8, "symbol": 0x31}, - {"code": 0x01, "length": 8, "symbol": 0x35}, - {"code": 0x20, "length": 3, "symbol": 0x39}, - {"code": 0x02, "length": 8, "symbol": 0x6E}, - {"code": 0x03, "length": 8, "symbol": 0x72}, - {"code": 0x08, "length": 6, "symbol": 0x76}, - {"code": 0x40, "length": 2, "symbol": 0x7A}, - {"code": 0x04, "length": 8, "symbol": 0xAF}, - {"code": 0x05, "length": 8, "symbol": 0xB3}, - {"code": 0x0C, "length": 6, "symbol": 0xB7}, - {"code": 0x80, "length": 1, "symbol": 0xBB}, - {"code": 0x06, "length": 8, "symbol": 0xF0}, - {"code": 0x07, "length": 8, "symbol": 0xF4}, - {"code": 0x10, "length": 4, "symbol": 0xF8}, - ], - [ - {"code": 0x02, "length": 2, "symbol": 0x01}, - {"code": 0x00, "length": 3, "symbol": 0x66}, - {"code": 0x04, "length": 1, "symbol": 0x9C}, - {"code": 0x01, "length": 3, "symbol": 0xCB}, - ], - ] - - expected_fse_huffman_lookups = [ - [ - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0016), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0018), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001C), - FseTableRecord(symbol=0x03, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001E), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0001), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0002), - FseTableRecord(symbol=0x08, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0003), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0004), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0005), - FseTableRecord(symbol=0x01, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0006), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0007), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0008), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0009), - FseTableRecord(symbol=0x07, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000A), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000B), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000C), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000D), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000E), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000F), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0010), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0011), - FseTableRecord(symbol=0x06, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0012), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0013), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0014), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0015), - ], - [ - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001E), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0001), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0002), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0003), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0004), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0005), - FseTableRecord(symbol=0x03, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0006), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0007), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0008), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0009), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000A), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000B), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000C), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000D), - FseTableRecord(symbol=0x02, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000E), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000F), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0010), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0011), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0012), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0013), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0014), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0015), - FseTableRecord(symbol=0x01, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0016), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0017), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0018), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0019), - ], - [ - FseTableRecord(symbol=0x00, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x01, num_of_bits=0x04, base=0x0000), - FseTableRecord(symbol=0x02, num_of_bits=0x05, base=0x0020), - FseTableRecord(symbol=0x03, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x05, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x06, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x08, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x0A, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x0D, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x10, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x13, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x16, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x19, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x1C, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x1F, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x21, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x23, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x25, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x27, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x29, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x2B, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x2D, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x01, num_of_bits=0x04, base=0x0010), - FseTableRecord(symbol=0x02, num_of_bits=0x04, base=0x0000), - FseTableRecord(symbol=0x03, num_of_bits=0x05, base=0x0020), - FseTableRecord(symbol=0x04, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x06, num_of_bits=0x05, base=0x0020), - FseTableRecord(symbol=0x07, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x09, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x0C, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x0F, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x12, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x15, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x18, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x1B, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x1E, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x20, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x22, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x24, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x26, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x28, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x2A, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x2C, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x01, num_of_bits=0x04, base=0x0020), - FseTableRecord(symbol=0x01, num_of_bits=0x04, base=0x0030), - FseTableRecord(symbol=0x02, num_of_bits=0x04, base=0x0010), - FseTableRecord(symbol=0x04, num_of_bits=0x05, base=0x0020), - FseTableRecord(symbol=0x05, num_of_bits=0x05, base=0x0020), - FseTableRecord(symbol=0x07, num_of_bits=0x05, base=0x0020), - FseTableRecord(symbol=0x08, num_of_bits=0x05, base=0x0020), - FseTableRecord(symbol=0x0B, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x0E, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x11, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x14, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x17, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x1A, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x1D, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x34, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x33, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x32, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x31, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x30, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x2F, num_of_bits=0x06, base=0x0000), - FseTableRecord(symbol=0x2E, num_of_bits=0x06, base=0x0000), - ], - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_huffman_codes=expected_huffman_codes, - expected_huffman_weights=expected_huffman_weights, - expected_fse_huffman_lookups=expected_fse_huffman_lookups, - ) - - -@cocotb.test(timeout_time=2000, timeout_unit="ms") -async def pregenerated_compressed_random_2(dut): - input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_random_2.zst" - await pregenerated_testing_routine(dut, input_name) - - -# Tests with predefined FSE tables and Huffman-encoded literals - - -@cocotb.test(timeout_time=350, timeout_unit="ms") -async def fse_huffman_literals_predefined_sequences_seed_107958(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "fse_huffman_literals_predefined_sequences_seed_107958.zst" - ) - - expected_huffman_weights = [ - [ - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x10000000, - 0x00004000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00010000, - 0x00000005, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000020, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x03000000, - ] - ] - - expected_fse_huffman_lookups = [ - [ - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0018), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001E), - FseTableRecord(symbol=0x01, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0001), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0002), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0003), - FseTableRecord(symbol=0x05, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0004), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0005), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0006), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0007), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0008), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0009), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000A), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000B), - FseTableRecord(symbol=0x04, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000C), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000D), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000E), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000F), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0010), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0011), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0012), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0013), - FseTableRecord(symbol=0x02, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0014), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0015), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0016), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0017), - ] - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_huffman_weights=expected_huffman_weights, - expected_fse_huffman_lookups=expected_fse_huffman_lookups, - ) - - -@cocotb.test(timeout_time=350, timeout_unit="ms") -async def fse_huffman_literals_predefined_sequences_seed_204626(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "fse_huffman_literals_predefined_sequences_seed_204626.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=350, timeout_unit="ms") -async def fse_huffman_literals_predefined_sequences_seed_210872(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "fse_huffman_literals_predefined_sequences_seed_210872.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=350, timeout_unit="ms") -async def fse_huffman_literals_predefined_sequences_seed_299289(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "fse_huffman_literals_predefined_sequences_seed_299289.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=350, timeout_unit="ms") -async def fse_huffman_literals_predefined_sequences_seed_319146(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "fse_huffman_literals_predefined_sequences_seed_319146.zst" - ) - - expected_huffman_codes = [ - [ - {"code": 0x300, "length": 2, "symbol": 0x07}, - {"code": 0x60, "length": 5, "symbol": 0x0E}, - {"code": 0x0A, "length": 9, "symbol": 0x15}, - {"code": 0x00, "length": 10, "symbol": 0x1C}, - {"code": 0x180, "length": 3, "symbol": 0x25}, - {"code": 0x30, "length": 6, "symbol": 0x2C}, - {"code": 0x0C, "length": 9, "symbol": 0x33}, - {"code": 0x01, "length": 10, "symbol": 0x3A}, - {"code": 0xC0, "length": 4, "symbol": 0x43}, - {"code": 0x18, "length": 7, "symbol": 0x4A}, - {"code": 0x02, "length": 10, "symbol": 0x51}, - {"code": 0x100, "length": 4, "symbol": 0x61}, - {"code": 0x20, "length": 7, "symbol": 0x68}, - {"code": 0x03, "length": 10, "symbol": 0x6F}, - {"code": 0x80, "length": 5, "symbol": 0x7F}, - {"code": 0x10, "length": 8, "symbol": 0x86}, - {"code": 0x04, "length": 10, "symbol": 0x8D}, - {"code": 0x200, "length": 3, "symbol": 0x96}, - {"code": 0x40, "length": 6, "symbol": 0x9D}, - {"code": 0x0E, "length": 9, "symbol": 0xA4}, - {"code": 0x05, "length": 10, "symbol": 0xAB}, - {"code": 0x280, "length": 3, "symbol": 0xB4}, - {"code": 0x50, "length": 6, "symbol": 0xBB}, - {"code": 0x06, "length": 10, "symbol": 0xC2}, - {"code": 0x07, "length": 10, "symbol": 0xC9}, - {"code": 0x140, "length": 4, "symbol": 0xD2}, - {"code": 0x28, "length": 7, "symbol": 0xD9}, - {"code": 0x08, "length": 10, "symbol": 0xE0}, - {"code": 0xA0, "length": 5, "symbol": 0xF0}, - {"code": 0x14, "length": 8, "symbol": 0xF7}, - {"code": 0x09, "length": 10, "symbol": 0xFE}, - ] - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_huffman_codes=expected_huffman_codes, - ) - - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def fse_huffman_literals_predefined_sequences_seed_331938(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "fse_huffman_literals_predefined_sequences_seed_331938.zst" - ) - - expected_huffman_codes = [ - [ - {"code": 0x00, "length": 9, "symbol": 0x13}, - {"code": 0x20, "length": 5, "symbol": 0x1B}, - {"code": 0x01, "length": 9, "symbol": 0x32}, - {"code": 0x10, "length": 6, "symbol": 0x3A}, - {"code": 0x100, "length": 2, "symbol": 0x42}, - {"code": 0x02, "length": 9, "symbol": 0x51}, - {"code": 0x18, "length": 6, "symbol": 0x59}, - {"code": 0x180, "length": 2, "symbol": 0x61}, - {"code": 0x08, "length": 7, "symbol": 0x78}, - {"code": 0x80, "length": 3, "symbol": 0x80}, - {"code": 0x0C, "length": 7, "symbol": 0x97}, - {"code": 0xC0, "length": 3, "symbol": 0x9F}, - {"code": 0x04, "length": 8, "symbol": 0xB6}, - {"code": 0x40, "length": 4, "symbol": 0xBE}, - {"code": 0x06, "length": 8, "symbol": 0xD5}, - {"code": 0x60, "length": 4, "symbol": 0xDD}, - {"code": 0x03, "length": 9, "symbol": 0xF4}, - {"code": 0x30, "length": 5, "symbol": 0xFC}, - ] - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_huffman_codes=expected_huffman_codes, - ) - - -@cocotb.test(timeout_time=350, timeout_unit="ms") -async def fse_huffman_literals_predefined_sequences_seed_333824(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "fse_huffman_literals_predefined_sequences_seed_333824.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -# Test cases crated manually to allow working with small sizes of inputs. - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def pregenerated_compressed_minimal(dut): - input_name = PREGENERATED_FILES_DIR + "pregenerated_compressed_minimal.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def pregenerated_uncompressed(dut): - input_name = PREGENERATED_FILES_DIR + "pregenerated_uncompressed.zst" - await pregenerated_testing_routine(dut, input_name) - - -# Test cases with predefined FSE tables and RAW/RLE literals - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_literals_predefined_sequences_seed_406229(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_406229.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_literals_predefined_sequences_seed_411034(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_411034.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_literals_predefined_sequences_seed_413015(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_413015.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_literals_predefined_sequences_seed_436165(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_436165.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_literals_predefined_sequences_seed_464057(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_464057.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_literals_predefined_sequences_seed_466803(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_literals_predefined_sequences_seed_466803.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def raw_literals_predefined_sequences_seed_422473(dut): - input_name = ( - PREGENERATED_FILES_DIR + "raw_literals_predefined_sequences_seed_422473.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def raw_literals_predefined_sequences_seed_436965(dut): - input_name = ( - PREGENERATED_FILES_DIR + "raw_literals_predefined_sequences_seed_436965.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def raw_literals_predefined_sequences_seed_462302(dut): - input_name = ( - PREGENERATED_FILES_DIR + "raw_literals_predefined_sequences_seed_462302.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_raw_literals_predefined_sequences_seed_408158(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_raw_literals_predefined_sequences_seed_408158.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_raw_literals_predefined_sequences_seed_499212(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_raw_literals_predefined_sequences_seed_499212.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -# Tests with inputs that correspond to the values in arrays defined in -# data/*.x files - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def comp_frame(dut): - input_name = PREGENERATED_FILES_DIR + "comp_frame.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=500, timeout_unit="ms") -async def comp_frame_fse_comp(dut): - input_name = PREGENERATED_FILES_DIR + "comp_frame_fse_comp.zst" - - expected_fse_lookups = [ - { - "ll": [ - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0002), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0004), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0006), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0008), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x000A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x000C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x000E), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0010), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0012), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0014), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0000), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0002), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0004), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0006), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0008), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x000A), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x000C), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x000E), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0010), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0012), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0014), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0016), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0018), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001E), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0020), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0022), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0024), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0026), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0028), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0016), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0018), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x001A), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x001C), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x001E), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0020), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0022), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0024), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0026), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0028), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x002A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x002A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x002C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x002E), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0030), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0032), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0034), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0036), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0038), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x003A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x003C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x003E), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x002C), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x002E), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0030), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0032), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0034), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0036), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x0038), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x003A), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x003C), - FseTableRecord(symbol=0x06, num_of_bits=0x01, base=0x003E), - ], - "of": [ - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x0018), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x001C), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x0020), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x0024), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x0028), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x002C), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x0030), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x0034), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x0014), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x0018), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x001C), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x0020), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x0024), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x0028), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x002C), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x0014), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x0018), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x001C), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x0020), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x0024), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x0028), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x002C), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x0038), - FseTableRecord(symbol=0x01, num_of_bits=0x02, base=0x003C), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0000), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0002), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0004), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0006), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0008), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x0030), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x0034), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x0038), - FseTableRecord(symbol=0x02, num_of_bits=0x02, base=0x003C), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x0000), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x0002), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x0004), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x0030), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x0034), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x0038), - FseTableRecord(symbol=0x05, num_of_bits=0x02, base=0x003C), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x0000), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x0002), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x0004), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x000A), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x000C), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x000E), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0010), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0012), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0014), - FseTableRecord(symbol=0x01, num_of_bits=0x01, base=0x0016), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x0006), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x0008), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x000A), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x000C), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x000E), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x0010), - FseTableRecord(symbol=0x02, num_of_bits=0x01, base=0x0012), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x0006), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x0008), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x000A), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x000C), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x000E), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x0010), - FseTableRecord(symbol=0x05, num_of_bits=0x01, base=0x0012), - ], - "ml": [ - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x0018), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x001C), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x0020), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x0024), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x0028), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x002C), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x0030), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x0034), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x0014), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x0018), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x001C), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x0020), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x0024), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x0028), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x002C), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x0014), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x0018), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x001C), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x0020), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x0024), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x0028), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x002C), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x0038), - FseTableRecord(symbol=0x00, num_of_bits=0x02, base=0x003C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0002), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0004), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0006), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0008), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x0030), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x0034), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x0038), - FseTableRecord(symbol=0x15, num_of_bits=0x02, base=0x003C), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x0000), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x0002), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x0004), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x0030), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x0034), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x0038), - FseTableRecord(symbol=0x1C, num_of_bits=0x02, base=0x003C), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x0000), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x0002), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x0004), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x000A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x000C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x000E), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0010), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0012), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0014), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0016), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x0006), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x0008), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x000A), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x000C), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x000E), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x0010), - FseTableRecord(symbol=0x15, num_of_bits=0x01, base=0x0012), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x0006), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x0008), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x000A), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x000C), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x000E), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x0010), - FseTableRecord(symbol=0x1C, num_of_bits=0x01, base=0x0012), - ], - } - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_fse_lookups=expected_fse_lookups, - ) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def comp_frame_fse_repeated(dut): - input_name = PREGENERATED_FILES_DIR + "comp_frame_fse_repeated.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def comp_frame_huffman(dut): - input_name = PREGENERATED_FILES_DIR + "comp_frame_huffman.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def comp_frame_huffman_fse(dut): - input_name = PREGENERATED_FILES_DIR + "comp_frame_huffman_fse.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def raw_literals_compressed_sequences_seed_903062(dut): - input_name = ( - PREGENERATED_FILES_DIR + "raw_literals_compressed_sequences_seed_903062.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def raw_literals_rle_sequences_seed_700216(dut): - input_name = PREGENERATED_FILES_DIR + "raw_literals_rle_sequences_seed_700216.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=1000, timeout_unit="ms") -async def rle_literals_compressed_sequences_seed_701326(dut): - input_name = ( - PREGENERATED_FILES_DIR + "rle_literals_compressed_sequences_seed_701326.zst" - ) - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=200, timeout_unit="ms") -async def rle_literals_rle_sequences_seed_2(dut): - input_name = PREGENERATED_FILES_DIR + "rle_literals_rle_sequences_seed_2.zst" - await pregenerated_testing_routine(dut, input_name) - - -@cocotb.test(timeout_time=2000, timeout_unit="ms") -async def treeless_huffman_literals_compressed_sequences_seed_400077(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "treeless_huffman_literals_compressed_sequences_seed_400077.zst" - ) - - expected_huffman_weights = [ - [ - 0x10000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000200, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00300000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000004, - 0x00000000, - 0x00010000, - ] - ] - - expected_huffman_codes = [ - [ - {"code": 0x00, "length": 4, "symbol": 0x00}, - {"code": 0x02, "length": 3, "symbol": 0x3D}, - {"code": 0x04, "length": 2, "symbol": 0x7A}, - {"code": 0x08, "length": 1, "symbol": 0xB7}, - {"code": 0x01, "length": 4, "symbol": 0xC3}, - ] - ] - - expected_fse_huffman_lookups = [ - [ - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0018), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001E), - FseTableRecord(symbol=0x01, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0001), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0002), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0003), - FseTableRecord(symbol=0x04, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0004), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0005), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0006), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0007), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0008), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0009), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000A), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000B), - FseTableRecord(symbol=0x03, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000C), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000D), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000E), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000F), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0010), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0011), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0012), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0013), - FseTableRecord(symbol=0x02, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0014), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0015), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0016), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0017), - ] - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_huffman_codes=expected_huffman_codes, - expected_huffman_weights=expected_huffman_weights, - expected_fse_huffman_lookups=expected_fse_huffman_lookups, - ) - - -@cocotb.test(timeout_time=2000, timeout_unit="ms") -async def treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025( - dut, -): - input_name = ( - PREGENERATED_FILES_DIR - + "treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400025.zst" - ) - - expected_huffman_weights = [ - [ - 0x11111111, - 0x11111111, - ] - ] - - expected_huffman_codes = [ - [ - {"code": 0x00, "length": 4, "symbol": 0x00}, - {"code": 0x01, "length": 4, "symbol": 0x01}, - {"code": 0x02, "length": 4, "symbol": 0x02}, - {"code": 0x03, "length": 4, "symbol": 0x03}, - {"code": 0x04, "length": 4, "symbol": 0x04}, - {"code": 0x05, "length": 4, "symbol": 0x05}, - {"code": 0x06, "length": 4, "symbol": 0x06}, - {"code": 0x07, "length": 4, "symbol": 0x07}, - {"code": 0x08, "length": 4, "symbol": 0x08}, - {"code": 0x09, "length": 4, "symbol": 0x09}, - {"code": 0x0A, "length": 4, "symbol": 0x0A}, - {"code": 0x0B, "length": 4, "symbol": 0x0B}, - {"code": 0x0C, "length": 4, "symbol": 0x0C}, - {"code": 0x0D, "length": 4, "symbol": 0x0D}, - {"code": 0x0E, "length": 4, "symbol": 0x0E}, - {"code": 0x0F, "length": 4, "symbol": 0x0F}, - ] - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_huffman_codes=expected_huffman_codes, - expected_huffman_weights=expected_huffman_weights, - ) - - -@cocotb.test(timeout_time=2000, timeout_unit="ms") -async def treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061( - dut, -): - input_name = ( - PREGENERATED_FILES_DIR - + "treeless_huffman_literals_predefined_rle_compressed_sequences_seed_400061.zst" - ) - - expected_huffman_weights = [ - [ - 0x11111111, - 0x11111111, - ] - ] - - expected_huffman_codes = [ - [ - {"code": 0x00, "length": 4, "symbol": 0x00}, - {"code": 0x01, "length": 4, "symbol": 0x01}, - {"code": 0x02, "length": 4, "symbol": 0x02}, - {"code": 0x03, "length": 4, "symbol": 0x03}, - {"code": 0x04, "length": 4, "symbol": 0x04}, - {"code": 0x05, "length": 4, "symbol": 0x05}, - {"code": 0x06, "length": 4, "symbol": 0x06}, - {"code": 0x07, "length": 4, "symbol": 0x07}, - {"code": 0x08, "length": 4, "symbol": 0x08}, - {"code": 0x09, "length": 4, "symbol": 0x09}, - {"code": 0x0A, "length": 4, "symbol": 0x0A}, - {"code": 0x0B, "length": 4, "symbol": 0x0B}, - {"code": 0x0C, "length": 4, "symbol": 0x0C}, - {"code": 0x0D, "length": 4, "symbol": 0x0D}, - {"code": 0x0E, "length": 4, "symbol": 0x0E}, - {"code": 0x0F, "length": 4, "symbol": 0x0F}, - ] - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_huffman_codes=expected_huffman_codes, - expected_huffman_weights=expected_huffman_weights, - ) - - -@cocotb.test(timeout_time=2000, timeout_unit="ms") -async def treeless_huffman_literals_rle_sequences_seed_403927(dut): - input_name = ( - PREGENERATED_FILES_DIR - + "treeless_huffman_literals_rle_sequences_seed_403927.zst" - ) - - expected_huffman_weights = [ - [ - 0x00003001, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x02001000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000020, - 0x01000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x80010010, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000700, - 0x10000000, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00600100, - 0x00000000, - 0x00000000, - 0x00000000, - 0x00000005, - 0x00100000, - ] - ] - - expected_huffman_codes = [ - [ - {"code": 0x0C, "length": 6, "symbol": 0x04}, - {"code": 0x00, "length": 8, "symbol": 0x07}, - {"code": 0x08, "length": 7, "symbol": 0x29}, - {"code": 0x01, "length": 8, "symbol": 0x2C}, - {"code": 0x0A, "length": 7, "symbol": 0x4E}, - {"code": 0x02, "length": 8, "symbol": 0x51}, - {"code": 0x80, "length": 1, "symbol": 0x70}, - {"code": 0x03, "length": 8, "symbol": 0x73}, - {"code": 0x04, "length": 8, "symbol": 0x76}, - {"code": 0x40, "length": 2, "symbol": 0x95}, - {"code": 0x05, "length": 8, "symbol": 0x98}, - {"code": 0x20, "length": 3, "symbol": 0xBA}, - {"code": 0x06, "length": 8, "symbol": 0xBD}, - {"code": 0x10, "length": 4, "symbol": 0xDF}, - {"code": 0x07, "length": 8, "symbol": 0xE2}, - ] - ] - - expected_fse_huffman_lookups = [ - [ - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0012), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0014), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0016), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x0018), - FseTableRecord(symbol=0x05, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001A), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001C), - FseTableRecord(symbol=0x00, num_of_bits=0x01, base=0x001E), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0000), - FseTableRecord(symbol=0x08, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0001), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0002), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0003), - FseTableRecord(symbol=0x03, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0004), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0005), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0006), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0007), - FseTableRecord(symbol=0x07, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0008), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0009), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000A), - FseTableRecord(symbol=0x02, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000B), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000C), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000D), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000E), - FseTableRecord(symbol=0x06, num_of_bits=0x05, base=0x0000), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x000F), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0010), - FseTableRecord(symbol=0x00, num_of_bits=0x00, base=0x0011), - FseTableRecord(symbol=0x01, num_of_bits=0x05, base=0x0000), - ] - ] - - await pregenerated_testing_routine( - dut, - input_name, - expected_huffman_codes=expected_huffman_codes, - expected_huffman_weights=expected_huffman_weights, - expected_fse_huffman_lookups=expected_fse_huffman_lookups, - ) - - -# Tests with inputs generated randomly on test execution - - @cocotb.test(timeout_time=5000, timeout_unit="ms") async def zstd_compressed_frames_test(dut): - test_cases = 1 - block_type = data_generator.BlockType.COMPRESSED - literal_type = data_generator.LiteralType.RAW - await randomized_testing_routine(dut, test_cases, block_type, literal_type) + test_cases = 1 + block_type = data_generator.BlockType.COMPRESSED + literal_type = data_generator.LiteralType.RAW + await randomized_testing_routine(dut, test_cases, block_type, literal_type) + if __name__ == "__main__": - test_module = [pathlib.Path(__file__).stem] - run_test(test_module, sim="icarus") + test_module = [pathlib.Path(__file__).stem] + run_test(test_module, sim="icarus") diff --git a/xls/modules/zstd/zstd_dec_detailed_test.py b/xls/modules/zstd/zstd_dec_detailed_test.py index 5cbcc562b2..cb30a1525e 100644 --- a/xls/modules/zstd/zstd_dec_detailed_test.py +++ b/xls/modules/zstd/zstd_dec_detailed_test.py @@ -7,20 +7,17 @@ from pprint import pformat from cocotb.utils import get_sim_time -from cocotb.triggers import RisingEdge, ClockCycles, Event, Edge from cocotbext.axi.sparse_memory import SparseMemory from cocotbext.axi.axi_channels import AxiWMonitor from xls.modules.zstd.cocotb import data_generator -from xls.modules.zstd.cocotb.channel import XLSChannel, XLSChannelMonitor +from xls.modules.zstd.cocotb.channel import XLSChannel from xls.modules.zstd.cocotb.xlsstruct import xls_dataclass, XLSStruct from xls.modules.zstd.zstd_dec_cocotb_common import ( - configure_decoder, start_decoder, reset_dut, run_test, - prepare_test_environment, check_ram_contents, - reverse_expected_huffman_codes, fields_as_array, FseTableRecord, - print_fse_ram_contents, check_status, check_output, get_clock_time, - CLOCK_PERIOD_PS + configure_decoder, start_decoder, reset_dut, prepare_test_environment, + reverse_expected_huffman_codes, fields_as_array, print_fse_ram_contents, + check_status, check_output, get_clock_time, CLOCK_PERIOD_PS ) from xls.modules.zstd.cocotb.memory import AxiRamFromFile from xls.modules.zstd.perf_report import report_test_result @@ -293,8 +290,6 @@ async def detailed_testing_routine(dut, encoded_file = Path(pregenerated_path) - # TODO: Make this a dict - BLOCK_HEADER_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_block_header_dec__ZstdDecoderInst__ZstdDecoder_0__BlockHeaderDecoder_0__32_64_next_inst2 BLOCK_HEADER_REQ_CHANNEL_NAME = "zstd_dec__bh_req" BLOCK_HEADER_RESP_CHANNEL_NAME = "zstd_dec__bh_resp" @@ -334,7 +329,7 @@ async def detailed_testing_routine(dut, HUFFMAN_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_decoder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanDecoder_0_next_inst26 HUFFMAN_DECODER_DONE_CHANNEL_NAME = "zstd_dec__decoder_done" - HUFFMAN_LITERALS_WEIGHT_CODE_BUILDER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0_next_inst20 + HUFFMAN_LITERALS_WEIGHT_CODE_BUILDER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_code_builder__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__WeightCodeBuilder_0__256_8_32_7_next_inst20 HUFFMAN_LITERALS_WEIGHT_CODES_CHANNEL_NAME = "zstd_dec__code_builder_codes" HUFFMAN_WEIGHTS_DECODER_INST = dut.ZstdDecoder.xls_modules_zstd_huffman_weights_dec__ZstdDecoderInst__ZstdDecoder_0__CompressBlockDecoder_0__LiteralsDecoder_0__HuffmanLiteralsDecoder_0__HuffmanWeightsDecoder_0__32_64_4_8_16_1_8_32_1_9_8_1_8_16_1_6_32_8_next_inst28 @@ -556,7 +551,6 @@ async def detailed_testing_routine(dut, printc(f"[block {block_cnt}] Verified FSE Huffman weights, score: {score}") score +=1 - weight_dec_resp = await huffman_weights_resp.recv_as(HuffmanWeightsDecoderResp) score +=1 printc(f"[block {block_cnt}] Decoded Huffman weights, score: {score}") From 355fc0aa7ec19200084956d8a5667f17fd8cbd18 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 18 Dec 2025 22:33:19 +0100 Subject: [PATCH 077/159] modules: zstd: update README Co-authored-by: Wojciech Sipak Signed-off-by: Robert Winkler --- xls/modules/zstd/README.md | 876 ++++++++---------- .../zstd/img/huffman-weights-decoder.png | Bin 0 -> 3747653 bytes xls/modules/zstd/img/literals-decoder.png | Bin 0 -> 3638136 bytes xls/modules/zstd/img/sequence-decoder.png | Bin 0 -> 3838460 bytes .../zstd/img/zstd-compress-block-decoder.png | Bin 0 -> 3546448 bytes xls/modules/zstd/img/zstd-decoder-wrapper.png | Bin 0 -> 3572476 bytes xls/modules/zstd/img/zstd-decoder.png | Bin 0 -> 3566268 bytes 7 files changed, 362 insertions(+), 514 deletions(-) create mode 100644 xls/modules/zstd/img/huffman-weights-decoder.png create mode 100644 xls/modules/zstd/img/literals-decoder.png create mode 100644 xls/modules/zstd/img/sequence-decoder.png create mode 100644 xls/modules/zstd/img/zstd-compress-block-decoder.png create mode 100644 xls/modules/zstd/img/zstd-decoder-wrapper.png create mode 100644 xls/modules/zstd/img/zstd-decoder.png diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index c0785c1fc7..2085b05aec 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -1,120 +1,170 @@ -# ZSTD decoder +# ZSTD Decoder -The ZSTD decoder decompresses the correctly formed ZSTD frames and blocks. It -implements the [RFC 8878](https://www.rfc-editor.org/rfc/rfc8878.html) -decompression algorithm. An overview of the decoder architecture is presented in -the diagram below. The decoder comprises: +The ZSTD Decoder is a hardware module that decompresses Zstandard (ZSTD) frames +that are correctly formed according to the format specified in +[RFC 8878](https://datatracker.ietf.org/doc/html/rfc8878). -* Memory Readers -* Memory Writer, -* Control and Status Registers, -* Frame Header Decoder, -* Block Header Decoder, -* 3 types of processing units: RAW-, RLE-, and Compressed Block Decoders, -* Command Aggregator, +# Quickstart -The Decoder interacts with the environment through a set of ports: +To run the DSLX simulation of the entire ZSTD decoder use: +``` +bazel run -- //xls/modules/zstd:zstd_dec_dslx_test --logtostderr +``` -* Memory Interface (AXI) -* CSR Interface (AXI) -* Notify line +The Verilog files can be obtained with: +``` +bazel build //xls/modules/zstd:zstd_dec_verilog +``` -The software controls the core through registers accessible through the `CSR -Interface`. The CSRs are used to configure the decoder and to start the decoding -process. +To run the Verilog simulation with cocotb use: +``` +bazel test //xls/modules/zstd:zstd_dec_cocotb_test +``` -ZSTD frames to decode are placed in a memory that should be connected to -decoder's `Memory Interface`. +Note that the generated Verilog alone is not sufficient to use the decoder. +See the [top-level wrapper](#top-level-wrapper) section for details. -Once the decoding process is started, the decoder: +# General Overview -1. Reads the configuration from the CSRs, -2. Decodes the Frame Header, -3. Decodes the Block Header, -4. Decodes the Block Data with the correct processing unit picked based on the - Block Type from the Block Header, -5. Aggregates the processing unit results in the correct order into a stream - and routes it to the history buffer, -6. Assembles the data block outputs based on the history buffer contents and - updates the history, -7. Prepares the final output of the decoder and writes it to the memory, -8. (Optional) Calculates checksum and compares it against the checksum read - from the frame.[^1] +This chapter provides a general overview of the ZSTD decoder IO interface, +configuration process and simplified description of it's operation. -![brief data flow diagram of ZstdDecoder](img/ZSTD_decoder.png) +## IO Interface -## Registers description +The decoder interacts with the environment via: -The ZSTD Decoder operation is based on the values stored in a set of CSRs -accessible to the user through the AXI bus. The registers are defined below: +- Memory Interface (AXI manager compatible) for reading input frames + and writing output data +- CSR Interface (AXI subordinate compatible) for configuration and control +- Notify line for signaling completion or errors -| Name | Address | Description | -| ---- | ------- | ----------- | -| Status | 0x0 | Keeps the code describing the current state of the ZSTD Decoder | -| Start | 0x8 | Writing `1` when the decoder is in the `IDLE` state starts the decoding process | -| Input Buffer | 0x10 | Keeps the base address for the input buffer that is used for storing the frame to decode | -| Output Buffer | 0x18 | Keeps the base address for the output buffer, ZSTD Decoder will write the decoded frame into memory starting from this address. | +Additionally, the ZSTD Decoder requires external RAM memories to store +intermediate results generated during the decompression process. -### Status codes +## Configuration -The following is a list of all available status codes that can be written in the -`Status` register. +The decoder is controlled through a set of Control and Status Registers (CSRs), +which are used to: -| Name | Value | Description | -| ---- | ------- | ----------- | -| IDLE | 0 | Previous decoding finished successfully. The decoder waits for the configuration and writes to the `Start` register. | -| RUNNING | 1 | Decoding process is started | -| READ_CONFIG_OK |2 | Successfully read configuration from the CSRs | -| FRAME_HEADER_OK | 3 | Successfully decoded frame header | -| FRAME_HEADER_CORRUPTED | 4 | Frame header data is not valid | -| FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE | 5 | The `WindowSize` parameter read from the frame header is not supported in the decoder | -| BLOCK_HEADER_OK | 6 | Successfully read the header of the Zstd data block | -| BLOCK_HEADER_CORRUPTED | 7 | Block type is `Reserved` | -| BLOCK_HEADER_MEMORY_ACCESS_ERROR | 8 | Failure in communication with the memory | -| RAW_BLOCK_OK | 9 | Successfully decoded raw data block | -| RAW_BLOCK_ERROR | 10 | Failure in communication with the memory | -| RLE_BLOCK_OK | 11 | Successfully decoded RLE data block | - -## Controlling the decoder from the software - -The configuration done by the software must be carried out when the decoder is -in the `IDLE` state. It is the only time when the decoder will be able to take -the configuration values from the CSRs and use those in the decoding process. - -The software should first read the `Status` register to confirm that the decoder -is in the `IDLE` state. - -Then, the software has to reserve the memory for the input buffer and write the -frame to decode there. The address of the buffer should be written into `Input -Buffer` register so that the decoder will know where to look for the frame to -decode. - -The next step is to reserve the memory space for the decoded frame where the -Decoder will write the decompressed data. The address to that buffer should be -written to the `Output Buffer` register. - -Finally, it is possible to start the decoding process by writing `1` to the -`Start` register. This orders the Decoder to read the configuration CSRs and -start reading and decoding data stored in the input buffer. The Decoder -transitions to the `RUNNING` state and then to other states that describe the -status of the last operation finished in the decoder (see #status-codes for -other possible status codes) which will be visible in the `Status` register. - -When the decoding process is finished the Decoder transitions back to the `IDLE` -state and signals this on the `Notify` IRQ line. The decoded data is stored -under the address configured previously in the `Output Buffer` register. - -In case an error occurs during the decoding process it is also signaled on the -`Notify` IRQ line and the error code is written to the `Status` CSR. - -## ZSTD decoder architecture - -### Top level Proc - -This state machine is responsible for controlling the operation of the whole -decoder. It uses the configuration data from the CSRs, connects all underlying -modules and sends processing requests to those based on the state of the -machine. The states defined for the processing of the ZSTD frame are as follows: +- Specify adresses of input and output buffers +- Start the decoding process +- Monitor the decoder state + +More details abot registers and controling the decoder from software +can be found in [Registers description](#registers-description) and +[Controlling from software](#controlling-from-software). + +## Decoding process + +Once the decoding process is started, the decoder: + +1. Reads the configuration from the CSRs +1. Decodes the Frame Header +1. Decodes the Block Header +1. Runs the dedicated block decoder unit based on the block type +1. Aggregates the processing unit results in the correct order into a stream and routes it to the sequence execution unit +1. Assembles the data block outputs based on the history buffer contents and updates the history +1. Prepares the final output of the decoder and writes it to the memory + +# Architecture Overview + +This section provides more detailed information about the architecture of +the ZSTD decoder contained in this directory. + +## About ZSTD format + +A Zstandard file consists of one or more independently decodable frames. +Each frame starts with a magic number followed by a frame header that specifies +decoding parameters, including window size, the presence of a content checksum, +and an optional original content size. Frame data is encoded as +a sequence of blocks, each consisting of a block header and block data. + +There are three block types: RAW blocks, which contain uncompressed data; +RLE-compressed blocks, which consist of repetitions of a single symbol; +and compressed blocks, which encode literals and sequences that are used to +restore data using previously decoded bytes. + +A compressed block comprises literals and sequences. Literals are data that +cannot be expressed in terms of previously decoded historic data and must be +stored explicitly. There are three types of literals: +RAW, RLE, and Huffman-encoded. The Huffman tree itself can be transmitted in +a raw uncompressed form or in an FSE-compressed form. Sequences describe how to +combine historical data with literals to reconstruct the original block content. +They consist of three values: literal length, match length, and offset. +Sequences are always compressed using FSE, but the FSE decoding tables can be +provided in various forms, including predefined tables from RFC 8878, +or encoded using RLE or FSE. + +Decoded sequences and literals are combined in a step called sequence execution, +which uses previously decoded data from a sliding history buffer to restore +the original contents of the block. + +There are many additional details required to decode a ZSTD file correctly; +all of them are specified in [RFC 8878](https://datatracker.ietf.org/doc/html/rfc8878). +However, this short description provides enough insight to understand +the ZSTD decoder contained in this directory. + +## Top-level overview + +The following diagrams presents the top-level view of the ZSTD decoder: +![diagram of zstd decoder](img/zstd-decoder.png) + +The structure of the decoder reflects the shape of the ZSTD frame. +The similarities between the frame structure are the ZSTD decoder could be +seen on every level of the design hierarchy. + +At the top level, the `ZstdDecoderInternal` proc encapsulates the control logic +for the entire decoding pipeline. It starts with reading the csr configuration +that carries the information about the addresses of the input and output buffers. +Then the main part of the decompression starts. + +First the control block invkes the `FrameHeaderDecoder` to extract the basic +properties of the encoded file and to verify that the decoder can process it. +Besides the standard requirement for a decoder to have sufficient size of +the history buffer (configurable), this implementation requires also +information about the decompressed frame size. + +This component, like many others in the system, accesses input data through +a `MemoryReader`, which fetches data directly over the system bus. +For each block, the control logic triggers decoding of the block header via +the `BlockHeaderDecoder` followed by execution of the corresponding block decoder. +The selected block decoder produces input for the `SequenceExecutor`, +which is responsible for executing the decoded sequences. + +Two types of packets can be sent to the sequence execution stage: +- Packets generated by RAW and RLE block decoders, containing literals + that are directly copied to both the history buffer and the output. +- Packets generated by compressed block decoder, which require + history-buffer lookups during sequence execution. + +The fully decompressed frame is emitted from the `SequenceExecutor` and written +to memory using the `MemoryWriter`, which stores the output via the system bus. + +## Top-level wrapper + +Note that the design generated from the ZstdDecoder top-module is not sufficient +to run the design in hardware, as the decoder requires external RAMs. +Because of that a simple wrapper is provided in the `rtl` directory. + +Additionally the wrapper is used to transalte the signals generated from +XLS channels to output matching with AXI interfaces. All the external +interfaces are collected with a third party +[AXI interconnect](https://github.com/alexforencich/verilog-axi), so that +the design exposes only two interfaces - one for subordinate +and the other for manager site of the bus. + +The diagram below shows the structure of the Verilog wrapper: +![verilog-wrapper](img/zstd-decoder-wrapper.png) + +## Description of individual modules + +### ZstdDecoderInternal + +This process implements the main control logic for the decoder and manages +the entire decompression flow. It uses configuration data from the CSRs, +coordinates all internal modules, and issues processing requests according to +the current state. The state machine below defines the steps involved in +processing a ZSTD frame: ```mermaid stateDiagram @@ -161,459 +211,257 @@ stateDiagram ERROR --> IDLE ``` -After going through the initial stage of reading the configuration from the -CSRs, the decoder sends the processing requests to the underlying parts of the -decoder. The processing requests contain the addresses in the memory where -particular parts of the encoded ZSTD frames reside. The decoder, based on -responses from consecutive internal modules, calculates offsets from the base -address that was written to `Input Buffer` CSR and forms the requests for the -next internal modules, e.g.: for `BlockHeaderDecoder` or any of the processing -units (`RawBlockDecoder`, `RleBlockDecoder`, `CompressedBlockDecoder`). - -Each of the internal modules waits for the processing request. Once received, -the module fetches the data from the memory starting from the address received -in the processing request. `MemReader` procs are used by those modules to -communicate with the external memory through the AXI interface. Internal modules -decode the acquired parts of the frame and return responses with the results -back to the top level proc. - -The processing units also output the decoded blocks of data through a -stream-based interface to the `SequenceExecutor` proc. This proc performs the -last step of the decoding before the final output is sent out back to the memory -under the address stored in the `Output Buffer` CSR by the `MemWriter` proc. -Once the decoding process is completed and the decoded frame is written back to -the memory, the decoder sends the `Notify` signal and transitions back to the -`IDLE` state. - -### Internal modules - -#### FrameHeaderDecoder - -This proc receives requests with the address of the beginning of the ZSTD frame. -It then reads the frame data from the memory and starts parsing the frame -header. If the magic number is not detected or the frame header is invalid, the -proc will send a response with an error code. Otherwise, it will put the frame -header into internal DSLX representation, calculate the length of the header and -send those as a response with `OKAY` status. - -#### BlockHeaderDecoder - -ZSTD block header size is always 3 bytes. BlockHeaderDecoder always reads 4 -bytes of data. It extracts the information on block type, size and whether the -block is the last one in the ZSTD frame and puts that data in the response. The -additional byte is also placed in the response as an optimization for the -RleBlockDecoder. - -#### RawBlockDecoder - -This proc passes the data read from the memory directly to its output channel. -It preserves the block ID and attaches a tag, stating that the data contains -literals and should be placed in the history buffer unchanged, to each data -output. - -#### RleBlockDecoder - -This proc receives a tuple (s, N), where s is an 8-bit symbol and N is an -accompanying `symbol_count`. It does not have to read the 8-bit symbol from the -memory because `BlockHeaderDecoder` did that before and passed the symbol in the -processing request to the `RleBlockDecoder`. The proc produces `N*s` repeats of -the given symbol. This step preserves the block ID and attaches the literals tag -to all its outputs. - -#### CompressedBlockDecoder - -This part of the design is responsible for decoding the compressed data blocks. -It ingests the bytes stream, and internally translates and interprets incoming -data. Only this part of the design creates data chunks tagged both with -`literals` and/or `copy`. This step preserves the block ID. More in-depth -description can be found in [Compressed block decoder -architecture](#compressed-block-decoder-architecture1) paragraph of this doc. - -#### Commands aggregator (DecMux) - -This stage takes the output from either RAW, RLE or CompressedBlockDecoder and -sends it to the History buffer and command execution stage. This stage orders -streams based on the ID value assigned by the top level proc. It is expected -that single base decoders (RAW, RLE, compressed block decoder) will be -continuously transmitting a single ID to the point of sending the `last` signal -which marks the last packet of currently decoded block. That ID can change only -when mux receives the `last` signal or `last` and `last_block` signals. - -It works as a priority mux that waits for a stream with the expected ID. It -continues to read that stream until the `last` signal is set, then it switches -to the next stream ID. - -The command aggregator starts by waiting for `ID = 0`, after receiving the -`last` signal it expects `ID = 1` and so on. Only when both `last` and -`last_block` are set the command aggregator will wait for `ID = 0`. - -#### History buffer and command execution (SequenceExecutor) - -This stage receives data which is tagged either `literals` or `copy`. This stage -will show the following behavior, depending on the tag: - -* `literals` - * Packet contents placed as newest in the history buffer, - * Packet contents copied to the decoder's output, -* `copy` - * Wait for all previous writes to be completed, - * Copy `copy_length` literals starting `offset _length` from the newest in - history buffer to the decoder's output, - * Copy `copy_length` literals starting `offset _length` from the newest in - history buffer to the history buffer as the newest. - -### Compressed block decoder architecture {#compressed-block-decoder-architecture1} - -This part of the design is responsible for processing the compressed blocks up -to the `literals`/`copy` command sequence. This sequence is then processed by -the history buffer to generate the expected data output. An overview of the -architecture is provided in the diagram below. The architecture is split into 2 -paths: the literals path and the sequence path. Architecture is split into 3 -paths: literals path, FSE encoded Huffman trees and sequence path. Literals path -uses Huffman trees to decode some types of compressed blocks: Compressed and -Treeless blocks. - -![data flow diagram of compressed block decoder](img/ZSTD_compressed_block_decoder.png) - -#### Compressed block dispatcher +### AxiCsrAccessor -This proc parses literals section headers to calculate block compression format, -Huffman tree size (if applicable based on compression format), compressed and -regenerated sizes for literals. If compressed block format is -`Compressed_Literals_Block`, dispatcher reads Huffman tree header byte from -Huffman bitstream, and directs expected number of bytes to the Huffman tree -decoder. Following this step, the proc sends an appropriate number of bytes to -the literals decoder dispatcher. +This proc provides an AXI interface to the CSRs of the Decoder. +Any read / write operation on the AXI is translated to a read / write operation +on the CSRs. -After sending literals to literals decompression, it redirects the remaining -bytes to the sequence parsing stages. +### CsrConfig -#### Command Constructor +This proc implements the Control and Status Registers (CSRs). It's responsible +for storing the state of the registers and handling read / write operations. -This stage takes literals length, offset length and copy length. When `literals -length` is greater than 0, it will send a request to the literals buffer to -obtain `literals length` literals and then send them to the history buffer. Then -based on the offset and copy length it either creates a match command using the -provided offset and match lengths, or uses repeated offset and updates the -repeated offset memory. Formed commands are sent to the Commands aggregator -(mux). +### FrameHeaderDecoder -#### Literals path architecture +This proc receives the address of the start of a ZSTD frame, reads the frame +data from memory, and parses the frame header. If the magic number is invalid +or the header is corrupted, it responds with an error code. Otherwise, +it converts the header into an internal DSLX representation, calculates its +length, and responds with `OKAY` status. -![data flow diagram of literals decoder](img/ZSTD_compressed_block_literals_decoder.png) +### BlockHeaderDecoder -##### Literals decoder dispatcher +It extracts the block type, size, and whether it is the last block in the frame, +and returns this information in the response. +ZSTD block headers are 3 bytes, but this proc always reads 4 bytes. +The extra byte is included to optimize processing for the RleBlockDecoder. -This proc parses and consumes the literals section header. Based on the received -values it passes the remaining bytes to RAW/RLE/Huffman tree/Huffman code -decoders. It also controls the 4 stream operation mode [4-stream mode in -RFC](https://www.rfc-editor.org/rfc/rfc8878.html#name-jump_table). +### DecoderMux -All packets sent to the Huffman bitstream buffer will be tagged either -`in_progress` or `finished`. If the compressed literals use the 4 streams -encoding, the dispatcher will send the `finished` tag 4 times, each time a fully -compressed stream is sent to the bitstream buffer. +This stage collects outputs from `RAW`, `RLE`, or `CompressedBlockDecoder` +and forwards them to the history buffer and command execution stage. +Streams are processed in ID order assigned by the top-level proc. +Each decoder sends a single ID until the last packet of the block. +The aggregator waits for the expected ID, reads it until the last signal, +then moves to the next ID, starting from 0 and wrapping back after both `last` +and `last_block` are set. -##### RAW Literals +### RamPassthrough -This stage simply passes the incoming bytes as literals to the literals buffer. +This proces is a simple wrapper for routing both read and write side of +RAM interface through a single proc, so the ram rewritting step available +in XLS toolchain can rewrite it to proper RAM interface in verilog. -##### RLE Literals +### SequenceExecutor -This stage works similarly to the [RLE stage](#rleblockdecoder) for RLE data -blocks. +This block is responsible for managing `HistoryBuffer` using the data obtained +from `RawBlockDecoder`, `RleBlockDecoder` and `CompressBlockDecoder`. +The data comming from RAW and RLE block are sent directy to the output and +history buffer. Wheras data from `CompressBlockDecoder` (sequences and literals) +are processed first in the +[sequence execution](https://datatracker.ietf.org/doc/html/rfc8878#name-sequence-execution) step. -##### Huffman bitstream buffer +### RawBlockDecoder -This stage takes data from the literals decoder dispatcher and stores it in the -buffer memory. Once the data with the `finished` tag set is received, this stage -sends a tuple containing (start, end) positions for the current bitstream to the -Huffman codes decoder. This stage receives a response from the Huffman codes -decoder when decoding is done and all bits got processed. Upon receiving this -message, the buffer will reclaim free space. +This proc forwards the block data directly to its output channel. +It preserves the block ID and tags all data as literals to be placed +unchanged in the history buffer. -##### Huffman codes decoder +### RleBlockDecoder -This stage receives bitstream pointers from the Huffman bitstream buffer and -Huffman tree configuration from the Huffman tree builder. It accesses the -bitstream buffers memory to retrieve bitstream data in reversed byte order and -runs it through an array of comparators to decode Huffman code to correct -literals values. +This proc receives a tuple `(s, N)` where `s` is an 8-bit symbol and `N` is +the repeat count. The symbol is provided by `BlockHeaderDecoder`. +The proc outputs `N` repetitions of the symbol, preserves the block ID, +and tags all outputs as literals. -##### Literals buffer +### CompressBlockDecoder -This stage receives data either from RAW, RLE or Huffman decoder and stores it. -Upon receiving the literals copy command from the Command Constructor for `N` -number of bytes, it provides a reply with `N` literals. +This proc includes the `LiteralsDecoder` and `SequenceDecoder`, +which decode literals and sequences, along with control logic that manages +the decompression process. Both decoders use multiple sub-procs. -#### FSE Huffman decoder architecture +The diagram below illustrates the structure of the `CompressedBlockDecoder`: +![Compress Block Decoder](img/zstd-compress-block-decoder.png) -![data flow diagram of weight decoders](img/ZSTD_compressed_block_Huffman_decoder.png) +## LiteralsDecoder -##### Huffman tree decoder dispatcher +LiteralsDecoder block is used to decode literals, it comprises of a +dedicated `LiteralsHeaderDecoder` responsible for decoding the header of the +literals section, `RawLiteralsDecoder`, `RleLiteralsDecoder` and `HuffmanLiteralsDecoder` +for decoding three types of the literals that can be present in the literals section +and `LiteralsBuffer` for storing the decoded literals. The control +logic controling the decoding process is encapsulated in `LiteralsDecoderCtrl` block -This stage parses and consumes the Huffman tree description header. Based on the -value of the Huffman descriptor header, it passes the tree description to the -FSE decoder or to direct weight extraction. +![LiteralsDecoder](img/literals-decoder.png) -##### FSE weight decoder +## RawLiteralsDecoder -This stage performs multiple functions. +This proc is responsible for fetching literals from a given address in memory +and streaming them out as literal tokens. -1. It decodes and builds the FSE distribution table. -2. It stores all remaining bitstream data. -3. After receiving the last byte, it translates the bitstream to Huffman - weights using 2 interleaved FSE streams. +## RleLiteralsDecoder -##### Direct weight decoder +This proc expands repeated symbols into literal data. On request, it receives +the information about the symbol to be repeated and the regenerated size. +It streams the symbol multiple times to satisfy the regenerated size. -This stage takes the incoming bytes and translates them to the stream of Huffman -tree weights. The first byte of the transfer defines the number of symbols to be -decoded. +## HuffmanLiteralsDecoder -##### Weight aggregator +This is a module that wires together procs needed to decode Huffman-encoded +literals: `HuffmanControlAndSequence`, `HuffmanWeightsDecoder`, `WeightPreScan`, +`WeightCodeBuilder`, `HuffmanDataPreprocessor`, `HuffmanDecoder`, along with +procs needed for memory access. -This stage receives tree weights either from the FSE decoder or the direct -decoder and transfers them to Huffman tree builder. This stage also resolves the -number of bits of the final weight and the max number of bits required in the -tree representation. This stage will emit the weights and number of symbols of -the same weight before the current symbol for all possible byte values. +## HuffmanWeightsDecoder -##### Huffman tree builder +This proc decodes Huffman tree weights from memory. It supports both RAW and +FSE formats. It fetches the Huffman tree description from memory, decodes it, +and writes the decoded weights into an internal RAM. -This stage takes `max_number_of_bits` (maximal length of Huffman code) as the -first value, then the number of symbols with lower weight for each possible -weight (11 bytes), followed by a tuple (number of preceding symbols with the -same weight, symbol's_weight). It's expected to receive weights for all possible -byte values in the correct order. Based on this information, this stage will -configure the Huffman codes decoder. +![HuffmanWeightsDecoder](img/huffman-weights-decoder.png) -#### Sequence path architecture +## LiteralsBuffer -![data flow diagram of sequence decoder](img/ZSTD_compressed_block_sequence_decoder.png) +This module provides interfaces to store and retrieve literals. It handles all +types of decoded literals (RAW, RLE, and Huffman), and stores them in RAM. The +module offers a uniform interface to access these literals, treating all types +consistently. -##### Sequence Header parser and dispatcher +## SequenceDecoder -This stage parses and consumes `Sequences_Section_Header`. Based on the parsed -data, it redirects FSE description to the FSE table decoder and triggers -Literals FSE, Offset FSE or Match FSE decoder to reconfigure its values based on -the FSE table decoder. After parsing the FSE tables, this stage buffers -bitstream and starts sending bytes, starting from the last one received as per -ZSTD format. Bytes are sent to all decoders at the same time. This stage -monitors and triggers sequence decoding phases starting from initialization, -followed by decode and state advance. FSE decoders send each other the number of -bits they read. +This proc is responsible for decoding sequences. Its work consists of decoding +the sequence header and decoding FSE lookups for LL, OF and ML into execution +commands. -##### Literals FSE decoder +![SequenceDecoder](img/sequence-decoder.png) -This stage reconfigures its FSE table when triggered from [sequence header parse -and dispatcher](#sequence-header-parser-and-dispatcher). It initializes its -state as the first FSE decoder. In the decode phase, this stage is the last one -to decode extra raw bits from the bitstream, and the number of ingested bits is -transmitted to all other decoders. This stage is the first stage to get a new -FSE state from the bitstream, and it transmits the number of bits it used. -##### Offset FSE decoder +# Registers description -This stage reconfigures its FSE table when triggered from [sequence header parse -and dispatcher](#sequence-header-parser-and-dispatcher). It initializes its -state as the second FSE decoder. In the decode phase, this stage is the first -one to decode extra raw bits from bitstream, and the number of ingested bits is -transmitted to all other decoders. This stage is the last decoder to update its -FSE state after the decode phase, and it transmits the number of used bits to -other decoders. - -##### Match FSE decoder - -This stage reconfigures its FSE table when triggered from [sequence header parse -and dispatcher](#sequence-header-parser-and-dispatcher). It initializes its -state as the last FSE decoder. In the decode phase, this stage is the second one -to decode extra raw bits from the bitstream, and the number of ingested bits is -transmitted to all other decoders. This stage is the second stage to update its -state after the decode phase, and the number of used bits is sent to all other -decoders. - -## Testing methodology - -Testing of the `ZSTD decoder` is carried out on two levels: - -* Decoder components -* Integrated decoder - -Each component of the decoder is tested individually in DSLX tests. Testing on -the DSLX level allows the creation of small test cases that test for positive -outcomes of a given part of the design. When need be, those test cases can be -also modified by the user to better understand how the component operates. - -Tests of the integrated ZSTD decoder are carried out on DSLX and Verilog levels. -The objective of those is to verify the functionality of the decoder as a whole. -Testing setup for the ZSTD decoder is based on comparing the simulated decoding -results against the decoding of the reference library. Currently, due to the -restrictions from the ZSTD frame generator, it is possible to test only the -positive cases (decoding valid ZSTD frames). - -Verilog tests are written in Python as -[cocotb](https://github.com/cocotb/cocotb) testbench. - -ZstdDecoder's main communication interfaces are the AXI buses. Due to the way -XLS handles the codegen of DSLX channels that model the AXI channels, the -particular ports of the AXI channels are not represented correctly. This -enforces the introduction of a Verilog wrapper that maps the ports generated by -XLS into proper AXI ports (see AXI peripherals [README](memory/README.md) for -more information). Additionally, the wrapper is used to mux multiple AXI -interfaces from `Memory Readers` and `Memory Writer` into a single -outside-facing AXI interface (`Memory Interface`) that can be connected to the -external memory. The mux is implemented by a third-party [AXI -Crossbar](https://github.com/alexforencich/verilog-axi). - -![diagram of interfaces of decoder and its wrapper](img/ZSTD_decoder_wrapper.png) - -**Figure: Zstd decoder wrapper connection diagram.** - -Cocotb testbench interacts with the decoder with the help of a -[cocotbext-axi](https://github.com/alexforencich/cocotbext-axi) extension that -provides AXI bus models, drivers, monitors and RAM model accessible through AXI -interface. Cocotb AXI Manager is connected to the decoder's `CSR Interface` and -is used to simulate the software's interaction with the decoder. - -The Basic test case for the ZstdDecoder is composed of the following steps: - -1. The testbench generates a ZSTD frame using the - [decodecorpus](https://github.com/facebook/zstd/blob/dev/tests/decodecorpus.c) - utility from the [zstd reference library](https://github.com/facebook/zstd). -2. The encoded frame is placed in an AXI RAM model that is connected to the - decoder's `Memory Interface`. -3. The encoded frame is decoded with the zstd reference library and the results - are represented in the decoder's output format as the expected data from the - simulation. -4. AXI Manager performs a series of writes to the ZstdDecoder CSRs to configure - it and start the decoding process. -5. Testbench waits for the signal on the `Notify` channel and checks the output - of the decoder stored in the memory against the expected output data. -6. Test case succeeds once `Notify` is asserted, all expected data is received - and the decoder lands in `IDLE` state with status `OKAY` in the `Status` CSR. - -Additionally, pregenerated test cases are provided in the [data](data/) subdirectory. `*.zst` files contain frames encoded using the ZSTD library. Supplementary `*.log` files provide additional info regarding the contents of each frame. Among the aforementioned test cases, those generated using `decodecorpus` follow the `__seed_` naming convention. - -### Failure points - -#### User-facing decoder errors - -The design will fail the tests under the following conditions: - -* Straightforward failures: - * Top Level State Machine transitions to `ERROR` state - * Simulation encounters `assert!()` or `fail!()` statements - * The decoding result from the simulation has a different size than the - results from the reference library - * The decoding result from the simulation has different contents than the - results from the reference library -* Failures caused by incorrect intermediate results (only in the selected tests): - * Incorrect decoding of the FSE table - * Incorrect Huffman weights - * Incorrect Huffman codebook - -Currently, all mentioned conditions lead to an eventual test failure. - -#### Failures in ZSTD Decoder components - -It is important to note that some of the errors (e.g. errors in magic number or -frame header decoding) are easy to trigger in the integration test cases by -manual modification of the generated ZSTD frames. However, the majority of the -errors require modification of the deeper parts of the raw ZSTD frame which is -significantly harder. Because of that, it is better to rely on DSLX tests for -the individual components where inputs for the test cases are smaller, easier to -understand and modify when needed. - -The components of the ZSTD decoder can fail on `assert!()` and `fail!()` -statements or propagate specific error states to the Top Level Proc and cause it -to transition to the `ERROR` state. Upon entering the `ERROR` state, the decoder -will write a specific error code to the `Status` CSR and send a `Notify` signal -to the output. The interacting software can then read the code from the register -and properly handle the error. - -The following enumeration will describe how to trigger each possible ZSTD -Decoder error. - -The `ERROR` state can be encountered under the following conditions when running -Top Level Proc Verilog tests but also in DSLX tests for the specific components: - -* Corrupted data on the frame header decoding stage - * Provide data for the decoding with the first 4 bytes not being the valid -`Magic Number` (0xFD2FB528) - * Set the `Reserved bit` in the frame header descriptor - * Set `Window Size` in frame header to value greater than `max window size` -calculated from current `WINDOW_LOG_MAX` (by default in Top Level Proc tests -`Window Size` must be greater than `0x78000000` to trigger the error) -* Corrupted data during Block Header decoding - * Set the `Block Type` of any block in the ZSTD frame to `RESERVED` - -The `assert!()` or `fail!()` will occur in: - -* RawBlockDecoder - * Receive `BlockDataPacket` with `ID` different than the previous packet - which did not have the `last` flag set -* DecoderMux - * At the beginning of the simulation or after receiving - `ExtendedBlockDataPacket` with `last` and `last_block` (decoding new - ZSTD frame) set receive on channels `raw_r`, `rle_r` and `cmp_r` - `ExtendedBlockDataPackets` without any of those having `ID==0` - * Receive `ExtendedBlockDataPacket` with a smaller `ID` than any of the - previously processed packets during the current ZSTD frame decoding -* SequenceExecutor - * Receive `SequenceExecutorPacket` with `msg_type==SEQUENCE` and `content` - field with value: `0` - -There are also several `impossible cases` covered by `fail!()`. -Those are mostly enforced by the type checker for the `match` expressions to -cover unreachable cases. -This is done for example in: - -* Frame header decoder -* SequenceExecutor - -#### Positive test cases - -If the results of decoding with `libzstd` are valid, the test runs the same -encoded frame through the simulation of DSLX design. The output of the -simulation is gathered and compared with the results of `libzstd` in terms of -its size and contents. - -Encoded ZSTD frame is generated with the function `GenerateFrame(seed, btype, -output_path)` from -[data_generator](https://github.com/antmicro/xls/blob/main/xls/modules/zstd/cocotb/data_generator.py) -library. This function takes as arguments the seed for the generator, an enum -that codes the type of blocks that should be generated in a given frame and the -output path to write the generated frame into a file. The available block types -are: - -* RAW -* RLE -* COMPRESSED -* RANDOM - -The function returns a vector of bytes representing a valid encoded ZSTD frame. -Such generated frame can be passed to DSLX and cocotb testbenches to be decoded -in the simulation and compared against the results from the reference library. - -Verilog tests are available in the `zstd_dec_cocotb_test.py` file and can be -launched with the following Bazel command: - -```shell -bazel run -c opt -- //xls/modules/zstd:zstd_dec_cocotb_test --logtostderr -``` - -#### Negative test cases +The ZSTD Decoder operation is based on the values stored in a set of CSRs +accessible to the user through the AXI bus. The registers are defined below: -Currently, `decodecorpus` does not support generating ZSTD frames with subtle -errors that trigger failure points provided in the ZSTD Decoder. Because of -that, it is not possible to efficiently provide valuable negative tests for the -integrated ZSTD Decoder. +| Register | Address | Description | +|---------------|---------|------------------------------------| +| Status | 0x00 | Current decoder state | +| Start | 0x08 | Write 1 in IDLE to start decoding | +| Input Buffer | 0x10 | Base address of input frame | +| Output Buffer | 0x18 | Base address of output buffer | -The alternatives for writing negative tests include: +### Status codes -* Generating a well-known valid ZSTD frame from a specific generator seed and -then tweaking the raw bits in this frame to trigger the error response from the -decoder +The following is a list of all available status codes that can be written in the +`Status` register. -[^1]: Checksum verification is currently unsupported. +| Name | Value | Description | +|--------------------------------------|-------|-------------------------------------------------- | +| IDLE | 0 | Decoder idle, waiting for configuration and start | +| RUNNING | 1 | Decoding in progress | +| READ_CONFIG_OK | 2 | Configuration read successfully | +| FRAME_HEADER_OK | 3 | Frame header decoded | +| FRAME_HEADER_CORRUPTED | 4 | Invalid frame header | +| FRAME_HEADER_UNSUPPORTED_WINDOW_SIZE | 5 | Unsupported window size | +| BLOCK_HEADER_OK | 6 | Block header decoded | +| BLOCK_HEADER_CORRUPTED | 7 | Reserved block type | +| BLOCK_HEADER_MEMORY_ACCESS_ERROR | 8 | Memory access error | +| RAW_BLOCK_OK | 9 | RAW block decoded | +| RAW_BLOCK_ERROR | 10 | RAW block memory error | +| RLE_BLOCK_OK | 11 | RLE block decoded | + +# Controlling from software + +Software configuration must be performed while the decoder is in +the `IDLE` state, which is the only state in which CSRs are read and applied. +The software should first read the `Status` register to confirm the IDLE state. +It must then allocate memory for the input buffer, write the ZSTD frame to it, +and store its base address in the `Input Buffer` register. +Next, the software must allocate memory for the output buffer and writes +its base address to the `Output Buffer` register. + +Decoding is started by writing `1` to the `Start` register. +The decoder reads the configuration, transitions to the RUNNING state, +and begins decoding the input data. Upon successful completion, +the decoder returns to the `IDLE` state, asserts the `Notify` IRQ line, +and writes the decoded data to the output buffer. If an error occurs, +the decoder asserts the `Notify` IRQ line and writes the corresponding error +code to the `Status` register. + +# Testing methodology + +Testing is performed at two levels: decoder components and the +integrated decoder. + +Individual components are tested using DSLX tests on various, +usually small inputs that test dedicated scenarios. Most of the proc should +have dedicated tests in DSLX. + +Integration tests for the entire decoder are performed at both +DSLX and Verilog levels by comparing the decoder output against +the ZSTD reference library. Due to limitations of the frame generator, +only valid ZSTD frames are currently tested. + +Verilog tests use [cocotb](https://github.com/cocotb/cocotb) testbenches +with [cocotbext-axi](https://github.com/alexforencich/cocotbext-axi) extension +to model AXI memory and interact with CSR interfaces. The AXI manager from +the cocotb extension is used to interface with the decoder's CSRs +to simulate software control. + +A basic integration test: + +1. Generates a ZSTD frame using [decodecorpus](https://github.com/facebook/zstd/blob/dev/tests/decodecorpus.c) +1. Places the frame in AXI-connected memory. +1. Produces the expected output using the original [zstd library](https://github.com/facebook/zstd). +1. Configures and starts the decoder via CSRs. +1. Waits for `Notify` signal and compares memory output with the expected result. +1. Checks that the decoder returns to `IDLE` state with a `OKAY` in the `Status` CSR + +## Failure points + +The tests report a failure if any of the following conditions occur: + +- The top-level state machine enters the `ERROR` state due + to corrupted input data. +- An `assert!()` or `fail!()` is triggered during simulation, + indicating an internal error or incorrect decoder configuration. +- The decoded output size or content differs from the output of the reference + zstd library. +- Intermediate decoding results are incorrect, such as an invalid FSE table or + incorrect Huffman weights or codes. +- The input data requires a larger history buffer than supported by + the current decoder configuration, indicating a mismatch between + the test file and decoder setup. + +### Failures trigered by internal components + +Some errors, such as invalid magic numbers or frame headers, can be triggered +by modifying generated ZSTD frames. However most errors require deeper changes +to the compressed frame, so DSLX component tests are preferred for focused testing. + +Components may trigger `assert!()` or `fail!()` or propagate error states to +the top-level controller, causing it to enter the `ERROR` state. +In this state, the decoder writes an error code to the `Status` CSR and +asserts the `Notify` signal. + +The `ERROR` state can occur under these conditions: +- Frame header corruption + - Invalid magic number (not 0xFD2FB528) + - Reserved bit set + - Window size exceeding `WINDOW_LOG_MAX` (0x78000000) +- Block header corruption + - Block type set to `RESERVED` +- Raw block memory access error + +Assertions and failures occur in the following modules: + +- `DecoderMux`: `ExtendedBlockDataPacket` with an ID less than any previously processed packet, or missing ID 0 at the start of a frame +- `HuffmanDecoder` when input data cause invalid state transition +- `HuffmanFseDecoder` when the FSE-encoded stream of weight contains 8 or more bits of padding +- `AxiCsrAccessor` on access to non-existing CSR +- `CompLookupDecoder` when the accuracy log of FSE table to decode is bigger then the maximal allowed value +- `FseTableCreator` when thee corruption is detected during decoding of FSE lookup +- `FseDecoder` when the FSE-encoded stream of sequences contains 8 or more bits of padding +- `RefillingShiftBuffer` when internal state of the proc is incorrect + +Several `impossible cases` are also covered by `fail!()`, they should never +be triggered and were added to satisfy the type checking system. diff --git a/xls/modules/zstd/img/huffman-weights-decoder.png b/xls/modules/zstd/img/huffman-weights-decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..387e94a9ffed7a63f98951845558f0a82a285e84 GIT binary patch literal 3747653 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
(?|Sm51KfREPhQ&m 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

a=YL<>vgFJF^-B{I2tV^( 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

9~SVr4Y^~( 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 McXiVX7sl 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

    BbEy5#&c^73K8exL05 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

    L{kdI(U~uus1w=qQ-~1H>=kNkm|JngU*rX; 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%0A2j+BOP6y_6U`_|-bYM;g=5%0A2j+BOP6y_6 zU`_|-bYM;g=5%0A$LDZ57_#z+(?PMse*)9tA4S{q{znn6^*>sITS|48nyZxxaYx^& z?vUWAE3L$*JHkEu+&D6jxL-w?V1{I zdbMpka)-Bqpn6RB1%=4*(zE+YMpzhlQOag zQCKx;iL9O$sp3@(-xJB|Ioha{cplCNFlt0=jVJ4>w{F|@E=z)<6vFL5-71{EgM(sH z3MQpsQpyH=ETuCNY_Jhg)gBPMxf)I@8-wZd!@B5=Yf&`VV^T_BHAZP4lTt7#1?U=M z6%j!&DFu^KR*5N-z5Zqun)K*kLTE$@`Mk@V>5u%r*YnIt##MoNsiy*|zhTJr2yE^I2`uv$ur1tvg_lS<69E6~5oDHU8I4}@sIXF*vQ3Pay zu(Ss!d%EB#Zmn&jUb3^uwo(!Hn{Mw&5nQZz@}Yh{D`&n(iM-p0Of{yvUfSOQ5Jxcf zoI&k-VYI@e6ry)LUK9c*rC?GD$Oa~*^x}iiNlkef^Lvu#;>4OrX|utP`w93Q!Dr-7P-*s02N-{- zT=+B5Qm)5xPp0c}DDgbCtZlhn)fxyL-i#X(yR9*3wDyV_bx%=cS?tq~56Fh6#0{FDpCC30n#P|bY!ugks;n|{ z#LHKHA##*Z8^7B69!F7tQ3QQ7(YJP8q?QJoB*U7%5Hh|6MLYtdR6L+e2+LrLcpeWh zi^^^v6f5qan-h2zfbt+HCInUql=v3w;gf^5Yp9CS<)H%*_!xiake%kFI2Js%>*byu!kRS!| z!*?wUac6{dsHwAOV)5_(ixQ?8&gSNX6Vg%jXAmYx}Dt6fhTXfe;*YmCxW#WjQ z(}yX9EBHhDN>-PT8-3ER1de-t1Il>bK{!F%}4VLf$d zBdcy;A^uKjo6B0u)tyKO!px%;#ZqW9x{sO}+}qgR?d`2;OSTDzin6;cbV!5h=|kP? z_PN1XIfR{IG##QX%7-l!8{mu{E{GzWs5nTDFbXe79CXTj0or1mH7HofW@mQxu4U&C zqkOw02)x8aD528MIkV}}GChid^zmlPz{@#NrK=bNkte_V($d+(Tj!{1b~Wgl8zsyV zNmgpv8sok}XO{pHK+}{(=R5j((Sq{!17sj8NV9D(i=IBh;z2(1)*rWkC+dD&J*Zr3 z&DdvqXs#WeP6M}4_n#wBm%Bp=D8UWe7bmb@K^VdD{^rX zLDry|qRp~}`6lSVSnB?O)FCVQSOiK^_tLuRGb?{nB1z`v^8(@C{1}f+BN0QdSTgQAPR=?fP|e z1iZ5=QMN6|9_KbFh>}&{1Y6ycAC?LQ@-TPEP&jZEwmj_D2?9_hm$+zcC&?-I{`7qk zz+NR&49-P#n!~1d#18_Q?#luV7Qpu^Q!J1WHIumihTg&bw}BnG0M|q@*Ja@g*6@kt zeSOgO$Y*HS-oZw^N4O%iBEP*S(UN5^_5DSkJ`)=~Y!D}Ul<>%}h1)PaJ&s)yj(%CJ zfZd0s+2gUEZe(w*(k9ufMeh^#fiECW=v{~UFFd(?ySRufVYi$1CEpIN*RgZY@aA@K z(p&LE*&II@wvthfY)?=KlKj7TJ)A$w9TKsXzAJkh}EJEi${OT-x7?%)QwnE?VdPZW>Y%m1-WA zjUJCz#}5Be8L)e<>+PimunGZgkP&P z9&V~oLB%PF$8Km1ZHbOt7|6_gzvGleuCd^Wzx>qSqNhrI8;>yJUE3eo`h6<$fhV4$ zt}l5+N`dSmC2q-pam`grH^Q;xb|%VQKTp6F$;ap0*~@qG@Ti|szW>Fn631~mBs#9M zO3=HpO71K@+WcRpC_q=hd%3h}nyu8TB=`T&PQOk#K?8mq*j*xDko4!1QQ69?v=YRF zmyi(Yu)>^~Q4q&>D?KqGt^YR?N)N5)-oR5tE~|I7$Fj;*b6G8gjL+nhepGVDrVwkV z5X<-Y90i3Rbm@bGMjz)sQ=_eiz^SAYTt+^8gyHCR<5ew&6}C4@uJATDaOMV7u-nt8H@X1 zW?yAi-`J1ywM{2k%S|OxpU~=R)%zssuZ$;Ex$=m=n(2H_M*o643`zdir{u73z95_* z?G8Wbmqk7^$ZXb2^y?nhXSBh8!;vP5LdJik41TwFws7xXZ-L+DMn9(un!SNK8E5A9 zMJqj_+Ut>BJ$6$kbxu`zFx9t@t+d2QD=U&>Dp}OmtH>QjgG!bZeA&k@ZvEcVFM09* zVq6SGKezgQ|C}OtMr6U7L16qe_WjMMIcp>zT5R#qf=aG&5gRjP-UpdmcnFSf)m#(; zT<%c?@vr?T%Co*rfP7O9zj%O*(m?NxkAFb9_2ceba6Pi_-QNn4Im)0b->TQ(Plm`A zYjYOr7SH7OuV(0Pw&07i{aQ;>(gU9sBH!DyB8+YLEq#&j{kzrOd=|>`WXEssE}rsr zFYiy^hB|7LZ7!nF!z;08anb1>cRM6V*^j* zm8C;h`Pjc^Y;tW|&d1(t!~aoZlXnx9O5T^Ag^f+_9G45%ZnlbxW_yjkzZwjb8ym1S zL?u60uJ7}2A8c&Cvo*bYVB$ym5ZMIeCpzde#YL9#;>LBwhPK<(9Hg9q+F8 zdG@S#ROR}-4ER%5=Y4l!)5ldAJ&EgX+KQq=zhrBz(856-#I(P(vlp>?O_RQDXHoes zgGP&>0J;dQ@~GsWw6xD9|3^0R!(rIoc;zk=VZU4!qjg{G3J?Y^KR3tYqi?gP8SAb) zQl2F$G99!{RE)i(PkvI4xAvrTGd|<4fVU64e5S+x&vgZW2EI`u_lrKkOaB3Qt~d9* z>OZ*An|OQA%hKzyWko+^w({!Gu<~oy7F?I*y`i?CFv*4a{c5EJPi){#iT^Ub`+YGT zSO((CwL~G=pXe^QpRMA*6%OPh5gtZ<71=bDS$jMz0QACJUH(7lpxp>+Umh5KvxQtV*=sDz?aUH5FJv{(B>mMMoT6NT%m<|xX6%dC2i$xI zd2{|s)&w#Of5@6-M?yoekYV~)t;zfST5L+dh!wwY8Tt6Pw8GY`H^(w?w4FU8*q8R&w4Rs-=-_bJX(dieo%(z(Z z1B>-w>|7~@3PoJicLlz`=?IiN`EHhEeQihJN&f*^j6E(i!k_Q6xKV>w`JOd+H%&m% z=~D`K6PS7WR%ZqdZLdPxizUN9GeGF_tODLF!xr;sBu)U|? zmxm1=O``d#*!E$ghY}ZLy{<+meQ>!uj8T7VLAhG7;4LK)*n(bkdmf9DB$o$Lf7OC= z|0N5WcXmCtp!vcpC527sl?jDe+FzMux!v1{ZRq=K=$9!weUcx>QRs(7p@q4;BN zptsJF`3KGCZSU^AIjIj$Ipry@rx9okroYK+NI%Ym7Pan$)uxNEtvKboGvI$QjeGlj z=bddO>$!|=P-_2|M*k0oq3|GnnZ?Z}Z~B&TXx^Ux0~I^u)3O02evEf4`GR=PHoHW~ zgW1c`1m+($vudVBK6HAu+ukWn=fPE>x0#~o`d4!$qEYjgIIU3dZynwATXiwFHTBaP z%aQI&w!c5l06`iS<0Aj@X8z&h5#KX4;Wg#4JmfGNIQV3gAWzZ0}ESL_dx z{*F^<$o7&UtAo|uHfV)xi)-xU_6i0@99GX&h{8c6<{hb-ph#$3w9%Jn?Gbzn-MNa%J?${F- z7x!8GX;9^>-Or7|Z?!uY z9rs%S4$0EFqWqKqr^u&&YfF>&ot1p3BH@k8B?2#qE>Zh)yyZVPee*MEX3%EOlN|p` z1xzdrpJEIL!+3yAr;Yi?mK^QyZ+WLSzc#eYO&ZQ zgM7nPxclLi2!GHe`?^)C;9!DJRj_qPt{AcN4ZW`XRzBa!DvDGKzJiwZ;urH=KlI`M zsz~*voLLoS7t05lKOFGaHJaD~{2T|6%*|lAM)h-w0$yX!vn(2_B)Xm?j;hc516h+5UWZ?mS$YlJoV<`*ZU(?)fm#jg_ zDhH)dW&2bV5`VDwSY$G<+VpoUV4(A!xvy#!-!XKfY!xZ4FoKzptyb6g@=u7Lu!gg}kbV`VJ|T+a@y4!j=NmhXOxr z9y6i&wR!x_x(gs!uw0&L&f8i3UOw15e&^gu(O>vg%^~C*-d$J$mE#Zfdzm@ppneet ziF%YMK*#<1<`BG@i?{pzgG3e;`4rZW$n{cT4XX_IgR9-ICGsNWLa%&ZyO`fd$u2@Y zA`Ts+JMRBx2;hNtvpM^*i1sCYdwCkNeI*fwI@=-Yt! zf@c50UUQyPf_b{1CQz%D3K+!yOnCTouC#};d>kIWHaxs&Ed^|2AQyZT(&eTBBO}!9v;%ya}jVwt!JS- zzHbPaYur)@2txyK2>6Z=@S=);$NrI(TCUEq#X}>{Z;m0QZ#LL`u>Nz-+W4zXwDFrE z;X`&bOPAJ2>OFcNo7meX_S@UTJZ1w51X2Avb*m3i;)@wnj;a2jikj`;HkwbXeTqYV zOCLUeQ(wo2e>_>`+lS6#{qi~bC9BzchYRw-{zk4Oo($TkRR1%BHlFsRK>yvX>@*&< zaXo0`qW<2|Xzz_3K^9|X@(#Pd<3;D^&y!T&+5+Wbh%FFIwoVIZMtj%PJsw8OZlM$2 zcP-1aVZ{>2aEk7`9%^R|+dJ53yL`piJ;OE}I=6y$_E#fXHs^g%9xZG>n~uU!4rp$@odD{THW9rfEvLOmOr# zj{DrXBf!mqi6x#H;+U;`W^&`~oqhtQrdIS4R(btx7+uyU8; zO{WAN2N=b7Sk}Me4ze(%T-j)o_sXG=`8e1|rkxLGL}hNjo*FsJi$2Yc=FUQuBY%+B zr-f+X&v>E5Y56G9QF&5vA7S8blQ=ucYj5E%j;Xxac$oa5lswv{_0tS|U*B5Utz0QW zoYuKY>tqJw9rBkqR&Z>q#k@qh{k+SQY=1{WujJxu`MGLrM}1>$n+ZtTC9eQF9~x%Y z_D8mE70fu#-EOiKmYzVOf)8~EAfO7A)l%Y?3>ep3wR9sKOKxYP-1YN3(2G^K88h{@ zFIKcs9<*Gd`!9ObA3pU5Xm#;2_1fhx%-%tX|6_$G{&Ja5M<5DWd9U)y%%Ogdw~g{d zvmAo$>s`$ELb7AHX+#x^&_{2C5N0W3Jl5~ zdEuoOHtntccyV1Idj9bu5--co#r~CK{8wA&2mM98JWRlGG4)rbt9V;Ml&k_L*y^7A zz$Ovml^&wJrWtIvpl;Cog2ICcBRBJ5zfKT@uqnzfuI(f_=0i&#J}CoNl{$x z0T(7Ld-8wBAHJ(S9=lykZBn%B@8KYiY^34btutP^%S6~Om#^}bg@Mb@&GGo?+w5t^ zy6cXVtHhUm&MSr{4QVl;tF59;(MR@!OMQZk{LXC;R>KMiAn+rpQ0s?|qYn zC`Gk@$LQ1a z2M_JR|3VM!!`-Gl%T`Vo-tNi$aQ_N92Y;Vg%tGw=#pb?2{Yja7{o4|l9qC_}!1sFu zSo|*aG9a0k-`rfjs^jfRSK7^7cpl|HPK8IY@zc0u6n~=b_MgEXdgpHlDNc=FrN%R5 z_6`R!@7leCVi5{GY4osje=Gm?cU(*+4}|h_=UMu~x%^t0@h1)Zrrl)o=lA&@-oN5X zy_mFD%YcLm?DHJg!J99SaHq|&0>*7c0AWG@I6nf z|JwP?F0i;v*#1itp^SJBxuiDn4pZT#}FU0nX+t!<{I(H8~-?Tm?Pm0nj?Kg8LC;%+NHt71~aPp)) z$Rm3izZ~T8(YE>)+KKbT>RA_lZsffYYl@?d_LaMfoWDbJyxQqTM(IOyCpYCU@2x3W zl?yP*-yg9>xw0rXpWj7)y`x#=(Hc;oE5+Qgxl%85{Hq(zYtCMHJWrg#yWE<6XB(1t z%l`LmNcCa*f`?@OJFtQO(dviKO2FTmmf>HrA(wFX5p?fDPQW%KGoj_EPNd7! z%h-mz(uQ30*KgSns`@yw^_Oi(o=5x6Hl)C>e*q(cf`ZGS!XE_867eVz1vARl9J5=)Lq{#6GBt z@<4ht81r8)CAb+^Wcuj+M&VLQP>gi@;85aQ>-{g-iSe8DXHNVMtuQZ5^KHkJPD(|OzRzwWq~o7I1L z?QN+83GDMvRQ|X0_VEf^>euul0p1ckns>@a_@qMW!!=TxEX z*vpTt2`pxoDO3^@Tk3-!=bFrn74)d&)Csg}rGDYTCedyeM zx0_mpgPD#i_dnMTA4|i~4}!k`$s6UPxkmEmUY^gdn3%h0PH&xSE3nun7hk^%92)l5=A&L?OvEI%_rm24TL3uNpq0BLD|7ictLkuF*#yxhPX=J zr&4+ebS>9R<(I&)xhnrUZ{cZR_v9@+p8N2V#qx2=>VY)#hI!9n&Xi*20+@UWI!dg| z#NV%KPtUjHci5J~i^-evFIx}uSwV8b0%S@gJDKzN^OlOfx+jD39+F4K%qW_D5yuSRm zMM_0j_oSkjy~;xo`S(A3__rPI>n2riirmwWZ@!cK0Y38TD;|pHP{0O_+YCH3 zqc*tkmEAr-E5KSqQVo8}iz*IU)sgFR4{drm0w{y*Alb?%j%pq_mS)|4y;pe4t$pm3 zd`!TZkAW^*^L)|Y_eYDAN|98{O$U{~(~^H$^az%No39sP=HhgvM#uS7{SRK|t;yd% z);s;rPqAKZ&miA#nNm| z`tslA?>+CGUUgL#@8e?*U(B@dwxfSDXEl$%N`{@EH?gx?gjwYdh?^Sh(jwoX!LYLm zy8rU~=Tp$R)_#+N@YpGQa3Wq!g2Ab(|9YzGaY#uPOt%ko2CkB0*Ju3d&cKx~Q(XSr z>}jDSo~EjTR%{7 zKGr->&iK_8=i?~q{_@|Zd!AREkE5s$K0H|r0(#rke>2UQ`v#xva{lMXd0ge6Ai4bN zaeUuWb3Pu_OBHh`exT;O(hb*V{OX$Xap&&+1ACk9d0ulqcE~^Y@MHn+e%sZ5GtF6y zyDnv*x_d8iL#PO&h^*%nxa)Sbgp$GdKhZh#P~P-uok0}wSha9@FjeXdBAMKa`r5rg zg?mwIKVzUG-fBJ2p8hYge(IJLA*?ji_*y@GJ>J}ZvA35(#LK!^_fZ2}La!!8r0Ad6 zwe0_g8(_K4^Zkj>>jp@osuuVqzF`Z@ue{U(OByUor{8NZ+Af7Ef$yIr1WpIUC~zG3Uhue_vvB73*Dc32WO^xlSZ>()I0i){ahV5lTC>P@i)K(Lc zZ3dPNUL1s{gllE%ZaUK$M&Y$y%mqI8X#Tc`E?~rkxK(mps@;B6Zw+oK%$0}ygl%xp zVP~{<9PFdZ=-^e7gy4^|G!Op(UIG1sHi&;XK>!&ZJ#^zAW&l1ZtT5xtrpi+HUGIeU zlJ1YrQbKl%N7+pU4UK&SuKihh_>*1bf7zG>q*2Xc6ZwAK) zPJza%g#m>BF?s>40Keb>i7?m~OqzK4c=^l1D--Wmq7ekouY}H~XIE0-hCREIh92eT zSAy&L>`Igie0Jqar$ht1p53wX(rs2=x=mD@`Rri<*7Cy2mu?eDNAAf{VP|N+Fb=k;*f66Yj_+1_VnS;doJhA^ zfZ7xdHgCm_r-SHZEj3dXzZiUdQ-{2|2H`>Z*X_W!VX9M*{yZ#44+?3kynL9xF+_nZ zaAD=FG;=vSMHF`Xt)t+JYXU=%Lj_+}h8Gi1Qoq02y0qzUYU)dP`10wBo4Lm$mYn8) zPE*4m1x7;uLHmDFp?R;{vAQm-5?0q2K;`pV7pv>%)pao(qnuy(j_UeebH3~~gMWgI zRVm>G4yOrAQa4#$kjcar(y+D#i{ib3R>s*bC5`+Y2RD9+{_Fhd-@6L=_j23&9v#@o zUSGE(Jpw-snoX%e;YRe#Ttlz*hMu~c6baA0>~9wNhOg9!p7R~EEBD;(1_hQBl?BTv zRE9uVvFyVSeVh0$gaBEHtT-;TC?&GPAa;`=a#1mjk_FOtM=!oZ*#a|0A7Q!S1qS>% z$BFe57H5eBC0pkQ*5149jaI`}zU>FdjYl7TLMIVq9ey0M>nk*9Z$T%!`S>99j3Z3o zl5o}fAe$=cwXg4K+96))A*z&y1cC#91r)ZprtV!PR2ZPUbfK?{mW&`tAW87B0q?mE zT9E{3^0CYAd>o=YN)`p_#h0af1nr$~0xK;&fg}Z=osd#Oi0~=VoeKR?O0?@vz{5{( ztgd9$flugvzR8Qc27e{O?oBH2LFDLAoi#>dZEiYdqjgyLUc49!ggqC$W_`-&GuXbd z)~gGvvvIqj)HLXAz1`omnnO7@SGGEV+>XI;M1Q<>cXR086Y7hpYHC}VZ>g(JMHVX! zcpC@%ATL=lu+wlPO$U8lm0Gs0?Prp6BBc zwsM>$jd)k@Z|0+El|BxsYqtocnmCu@{#vcqqlGtV>~w|^_f&9X zJiR^YOQ)UCTn~dD%xiF%_7#^Es#>=$?mO}!(83eztSUld9Q1mj5Hov0N8+bJkE${d z6)*8PvLnX*aZ0CN({GD?j@xQIn6ouKZc-(PyUSQ>Eze>S>e^zo2okR;x7GqVojZLv zMrAH8=Db?B)CCi6Rt=3KTgUc+tje4tcBXDnX>(?)o*2B^l!-Llj%A)FWmlC~bls4L z&5-X69XmcR2xiz+rhBv1UXS^v!BQJNg6U~PJ?)wrZ+f+DJ93BhUhI%*GaQ+7Cho`V zaA>uZsY>oD;f`#{o1?BqEh0))m)R;>J!+kMr;{?W2T@oxX^E_!7OCP@4Br#U>N(n| zlz1M_2LhF9t?^`C_10~>-er;E6~gU6-71{EgM(J*yPmZi@8vD%GR0EHpo9c1Nowto zIju>mGus*oS*`kWXU7w*X3AK)wN5vx#j3qTTh@m3u2=Vu!9ktZoq-;TIz0}9xocMZ zp{%V4$1zr2L0XTX*;Z1Zf+=usf)2wg&;U z1;Jfc%Adls(%tfd6gD_dVKvg)eqRh)D!($P@Cy05tgAgql|5;A5>nHz`TFeW@w~un z8`Nl^uBD-HR_0{iQ{A9%t%T!A;gwWt2X4DJ3x>{gxTA-=V9*NT1j;ZJm6kb|)j1#c zL9NXi)01hQcW2+~oYQ*}EUsS!6PTY+p31aks=+4THi9)^8+6imZuVlqMBr+k(1 zkM-kzIy}{yK`N{Ssj=&(u|`zJ>VdcBu%p@J2I~GC4Y)IWBUn3`aYs$$$I`hY^}0Uo ziZ&e$d*{PJR;T)SYPZhJx<2V_*4(KvSD5p%KU$c|cC{g4>Kwsi85b_Pe{@2vw>Vkj zX2`78yWL`P2qkjXhoevZh*Z|m_Ha@*1{fnDj*q0j={2>L(%FcDI2@ARv@T5M)I124 zRg<8r;t^fm<^4=gZF{(LSY^#mr@=-6rwjyHfLTlwu9|4OiOe$Ng)9>T+U=vI(y;E# z^`2%yM5r7za}jKZ1AejZA%&{=f#eT1n}b!IX&Z~D&x_Qw=$R+c4rqPNmRcK?)( zmC8#tT!!Ep<_0zX&PcGqMnqM6K=9^jIIV09rq2)SqBpKZ(O^%<71S~h^rXFqzm4k9 zFk_jkFGkQlc#r}Q*%xi1rWK82*Ja_!sX>sC? z{mF*yocUAd+&p#Wu)j;5?HNmGb0>lQ*UdH4Zi+mIj?>?aJd^63baXwl+P;~cZ>9M) z(vk?b3D*GI-k%%Y=R_rHO@BpL4ZhMj)%NCz&F*u|t_?J6WnD=-XL;WhYl$)DT+!v> z828WRY{^p!9b0dV2vZ6U9g2l(&)<*VI~d~{qs{q4cg&<6x~C$aphLFcI%}lq_q@@3 z;ThZE`uf@y^=|pPQfuCdvu9GH? zO!MQwd*h$%XVpD_v3~J>;Mp$x?3p}y9_X0m>we*R=KD6F4a;N?-rYf?vwIq=LpX*N z!m%pQ=GOXbSb_GO!1E<~lOffg2p+TpzvoZg&SZG*OMKFwHQBC69D9@1y1O>n-b^Jm zYM8yR`*V1ElOC-Mcuq2^s$cIW+C*czE zYq-$KhF+{4Dtq404(38{G6@H!@4>TWSB89bAK5uJ=o}h6S#Vs)g)~gMXOrli2tr%8 z{LZX-?18LkXTk4D{7GADRBt99^K)#pb67X%R-(<(bMX>wW_cZ2*e^~^)b^e1U5A1y zZ)(kT6WZ#dFltU`Ei2NNPAO2{NImw2z|nUjkK zK$4wl0sk20+c?cej{pfH`o6M;mVGdwWt)^jEkN5n-KH04GjYE8Fj*&yxr$9TT= z^e59zA6g2uoTMl9+{Kt6phfweiNkh%Iq6DH+MV!3Uvh}EVF*LSQrajKyE_(CuR1|AFtXR~_L-*EQ^QYdJID#ac zdde{EPmsg}WN#d&-F1*c%QzrSjP@OfpkeD;MWv?4d>PlFpL^oP)}#9LtiiiA;XT9q z&)x^@cdyyel688>?ZU-Q6lX+d{mlRG-}vWV$3JKgq21mo+S#N;X^#IH6c5~ z_Q!Q{fU+%u-|CErA^HeE5<9G->^k}^&V}Y|-i1$93I6G)X`^9ORvWZVphvBVsx~Ua zKod>`eIUdA9QqWRywDoSifZ?wgr3`6V>Yv(w?%0T*5uGhcxgP;hjeAklPgiLt;RcX zFGfu4z@OM{YaTJQrWP?b8|`w01x1rQ zhZ|Owj6K~3%NE@B-dnTgnRVnDU+3xuOII{WWq5hQ5B;U>oaMP=+v3Jucp+II@|)dc z+FLq`W6VU0I}Aqg-am-qd6IfHuYcfOj&G*Y)@j?qh^aRqW8iDdY~AF?O6TAo$W$8) zPBGP3aEiG<*C6~e4!p(*8Y5Jl$sY2Cg4FXGMsnIivkq%3zLm<3-#3I>gQBgLwk{Qx3Qi_YO~JE9yy2) zOR46}sqSh`xyOlP@A)y)=rpOe+gixbm^xEvB1)y6w%t=juO%bf2$#0#cb%iDtmMON z)Lii5Xd2p^gEUIzrLfRwCL*ZK84f0xOuyZ&H4hbKo9C=&gdg?=3~RWca}E(t|J!-Q`}O+0jaCM_!i~nyMS4yet77CAPYN zPw;!3h3F5*pPdfusZ8ZV-C-qO9FyI1{cuduW+YC@nPXJb#+FA(+P>R1<5jBgn^goy z5gMkvbVl)F4Sip57xJF3!qa8g2@h|sb?|A1x6|%cot$Wp8zGRL;a+GEh8(a4oJeR5 zW-uI2@Ke-P#aDHz9?q8Xa#bI7j!u7=@|*RTK1Q6&$peQtvS)qYwV2kDU!OW|$4pjy zOWdpjC_+`bHeXF!HlI%Qjdx)C=489AHeF4hY`T1_b?NOj`9lh`dkWfmb~1%px{xQP ze)4zPne8;&zOY`_)@^-;u4T1EPpQ*C$#cl#>^IP1I9+*hcfreBwl`pfRI&wlq(avn zL|#=otWG27Aa0{;-be6Cl%6S`;{o(U3hQx|7>(z`cwx8)W78T6t;3AwHq&NBj@Au! zSL-);p4%u*T^su*zYtXPRM4B}w>%Yh=qVwcyJ5#!)jDp|uWOgLv`Y?LEk$o%JRPC` zXeiavZetWrCf=@Dvw4qKwW)1IFN!JSM zmQtu#h;~r6EG*<$i=5u6-B1Fqn2lC16eJ@Xl^d=u7A>nK-Bs@#%?Z`nxf!DTYdf&}9%ohen$Z zc~s-8RAhAxe;jKe&DG|*&o3FONu1!>sKOXloKaUzs5Lr{N+qj9B>1CAN9@!Nmv`Ys zs6$5L>T6z^C97yRI-1f7TA^Ta>S*;5^!7AWq&1LXL2@`#de*{NR(6iFY|E0gP}k$Wm@2IvY78MgBY4ZE%~CjY0Qmnjb!RhvOt#w2 zFT^1-|R)9ebv z_O8-qWV+KFK+Qv6XpnKek@nTL3)k({_=BU1hmqCi#0fiFhKzDd>j!??=Ph>?pXgMp z^TU`IQ@P{ONFh1BS!d4e#~o#BZ+Bshs8iz5jiZXZaKzzm#qm=|wE4bH*B#<4(@t~W z*>W^bb=S;V4J6r69bJetA#sET&xLv|Tr#RW-5g{J^lm>G@f$`w_#8B1VLzH`U}lxM z5RK&Ho*MzrS&(|pj6!n6v?NJu&bF0KSEz%{;*mD-pbK-@=Y37yN5r@{amIVzWRQ;l zT9xRnwu6yYuQ`05pF`2dkZTqylSk?!@-T5u0xLL z=?J_V1Fn>wMQQUOF5I5$NimYg2GZG~JeT{iWGK7sV$$eqaNPP(;EtmWKc3HYaq8B# zyr=cz{$apQ-5sbKMMV?JD0P|a z-dG^@hs1&o_|eR-juFI!vnM zCKeWHpG8`3LQ}1dFdKD`gs>6D+nFYgyw1T+J9Yjv=)|xW1F3uXL+&^c7B_es04W2b zSvjh$E`@xRfiF9q(3T=sWW4Quc07h~CRGM&DLAuw3odLPZpQr+ix9qv^9I-G!|xylYE*zAD3Yd42U!r?RAB1fRDPZwRlj_5*v#9 z8ZprZy+d`2to_z*kDy_9tI9d)q@d))QOy>0r(rX#c;M5E=zI`1m6IIOdVe<~TpoN< zx9%h+vJ%4YS#?k0u$t0hly*+1{hrr8cUrT#WlEev@SAPmg_Gg(IB$n5vd9EyCih3(S`j$UqQVA@>f)c5j0_~oS1ffmJ<6l99%K>2tnq%o>9rzr#=3@q}VF)cJz%xYSJ^=13U zW06Z+NBbDCU5E~6z8a~?M`q4W>#VnmMKZk?>Yeu5P1TBx-ac}4KawH z&IGPL9pU?4STlQzlN6y%C#^s)#iO~S=kI+O<0|hX)i$NGGd$4c<&!!NS8s>#fZJ;Q zt&rx!zHFqGF#3nuIB#}ttVvuIl;;5}4=if)X?Nq6f|G@(#tI;%g+rKDHWoSok9Z|{ zG11b`vNYK>s2M_{&1ksd_SbLMmW}ec@_cocTeU-vLo-kNm+hCh`*e2@L-@S*=NGJ) zg32mOZVdP5-P7F?WQee}!^td+Gwc@p(_!w>Uc%}*1s07|TqZ?B?=pO_p)Zt^S)aP2 z|K>C^Yh4aT>FVCg^W8n{-dr;8UFotpigfI@jbsTNE1aWjYZ#z8pcb@LJ7~_?(b6?`7(u;BI`9#3lJ{W>y8jzyjxf)aE|coun^XL8%j@2YIh^FBJ$4+NII zv&9@??=F3mXk8Ctz}elUuu}*_Bhe6%UiCXuwr09j>nZkwcx?2!K1R3U!^lznu65NfE5~l zIPLdW`Z`2e#_eF_(MtU%>mU-FwR5Wg7rWsU#Iuw?m~4FA+jrj zi*uL#fuqIF!2%b4ln??ve9!?a(>b?&aPL~2o)6lZzj0M`sP+O^-43BQqQ~~k$oI-G zJ-R=&Qwf&3AYn5XJz6(j3aHWsQgjKAE&1!DG3Xs5P~MJMqv6gAiqpCTF@A4D`gOiQ zlmmYEm@oIL9ag?EnD;wq0q*~F7#Tt5p;Jnqauz{a` zVolF*=2v}^&wcsWg9wtgu>%H)O_8vTJXN^7*4?d~GHB0+Jf1dGaz>)+c4QpEPh znEIyRp0Hab!s{h;SJbi1W!}fMrc)J7^*M~s*YL%?1>RcJl+{S-*PBLDNJGr|QPS)Q zSJ8;wsKTw>dtiJJB`o}q_&#)37nWG%dSq79p%y?{9v*c6P{0ZhsJMM=EfzjnT7pz* z?QpmWxBLKG#C4hM0$Yq6V;6*Zooh(lNbX``dA%D!9dPVEA<*kHCWB6>C%v;pO#9LIBilC8;P22VJ;Gu`o1mY30!&L$Yt^coMAJ2)2I zxfZNN;)|AIWh2(j_0EpB1<`9tb`871N2@i1u0qJFCq8hit{0T|)xEc=dv{##DJ}=r z)$mA@#!8H$r-GH~98_uLYM@wda1SDY7POU*5QQpUuj`TJviJUh=qK27C4Um2RfS=0 zF=*_S`7m6ceYV*`NW60iit+{l){je#4o)D=$EZBU2mH;Ia85w5Cv3bDW%?rGdY($vBvIW7>;4SrQNjaXyB}NF*Fhg|wf*tBi&zxIJ9p{V ztOXa}z7ElA>%DnYt9}{O7FNsG9HkWS=>&x9sP>*#{T4x(Czz}1R5SB_54O_lGYUK< z^ROTc%-m{BoK{qL+4^vPb)d?@3Pi37(V7=+`7pz<`p`}g@EV_tU=pD=36oMll@Y|z zthX21j8J>7U_Lto-i>jU8NwK4+@dPB$5lLX!Q3W9;}#mJ@M_!3dlskH>k2`0CCk#= zx_vPV41^WqoU71Eg?BG6ljWa`MB7^IDjy6M42EjwwuOv)rEfj$&`EICfYpk6 z?`sPT7hp<@V=W1fp4ubPbYE>-32aNz$|)Yn<74EsPly%h`_%~*r`&p5k$LWq`we$k zB{o|3+R4DlER<<|z-<#0Nr8Ju$L2`g*{8hDC>Z`&=U`+Gt3xYnZ&-of+!pwZ%HTnl zC~P_=LkAK32C#xf$)U*jQB@W@nss^y*A@=e;023>-}!jXrDv+{m(_*cfKwr~3hNPU zEJH&u?ouWW2K7SPKi|6*EZ|G-6KY4>n(<`;$=+(Rx%kl{<7U)?t8A_bChN!U%4iNGVvz43nlN7 zW38>y*K515Vgng3tYtW3uWwxn^eK1yGX2GfKtG~u4h}s*IK04p?1Augl+X*Dunz2& z9p?n!EuWV#s=H(`kLamCVUzOi=Z{J6wKl4-&dMyb?XgR?>MV6!cGWx-<5YN`yE4~z zI0-sEK#f+tWy|AHDYpWx3cm?99435_+Y4C7l>MiigaGC;;T*}wqeELP72%54$@JJ`11m20jF1u@3Mql_sPxr(4-1yG6#4gcRQ>oK{I z3MKS&Z|>c>^xD|07OZunV#b|7IjL8~`q8c2Y|dOhu;9n|yfu4@FU)W{-JeM%Ef*NA zq3yH>y^;r=2fRX>19!gP)AwYz=)`Mu9nK5*L=(Am6k_yC^lE2@zo>;U0CxaRrBwGlb_^M9A)5C8=c&PF8@*L{!yA1WxL%v zAiG9UE3^V^(V?ElFy}>0kqbl2A?07-J~+V)Ez{#AurR4c09KdO3P+}Sf-Zjk__O*E zd=Gcf-5KO}vISxSx4nKZXNpv9_~!;RBICWQuE(d88+2x8?t#aLAbmZqm=Uzn5v;hA zwh1;)cAtFf)jx3y`$&=I{@`~V*ptBuUTwB(zx>kg;4U5epkW5fON9|Gn2uRRI^5^V zgt=kLC_L3@iP&|N!$hFWUL5clU~!S9_B*`<{EEN(b!+yoggz*1VR#tk4CysHr37!< zvM38OS0XC3Hg-G4C@1TMyT)kXqt#$G?G1Mb*044`_=^f5k7TD4Q<&A$z1J0JzqmaN zEi`OgErJR0Je@E$+Vj?VKP(kEROdTukDy$FBk?WI*igV0Y^&-t7NHMjp$xa9w2!yp zxofc8C^t*QQA-}0i+w2`rQ|Dme!8~9&9fvK57c_5YPSapJPfcBh>tb*cA?Z@C71ly zX2v}#?hf7e%@c1mG*Ll3i{oakOlrNd7y9W6Po9a@3BIx3((1k;TQe3h3e6EKHbhOBCS8CNjExA~AKFmzYoAC>DWgdF+b(7wx>vlQ} zd#4Cor(fsSLzr-0WV5gFsCL?{-F3r@(_@9pBi?|LudEjMjG@On(ld|{==Ev0$rg8ACQ5a-knZX_1|8N%9+>HnwjH|* z*X=XuS!ghJuRtD|xSrrzT$fPgr(kGt&va!ED@zi(+edn|8P*D5D2iQYCI?Dz9@V&X zi+$UVeFqXp2HS-4nm@MAAY<&rskKk7rhl|PK3R^)0&}6R%h#;$6DhZO=?3xJEu#!4 z0c_uNthE!0PW(|muVmEOwa!8H5crK3=ta}vK2vyUSdy_T-yUA}feSwFAS}r^CQnwy zGmmuFvs3)QX!-P9Ew@fg*NMS_D>5d2>WqO|{y0Fu^yclmH5jmGizSDzVt$OYJ#YrQO)x6-`1!Fwjw2d$8Kl&SHV6tk!#~(m4!u(JC&7C@@mlK@N z#S(K!g4Qx%Yt>GhL2v_kHV5!n3OUbZ_;X^h&+u0mcord*>^={APle;#F2s+g6Dq5< z()X{HnTHejo}i@h4b7|Hv2~%uFkQ5{Z&1aGX6tZk%BYY8nZKYRPzd+y3U20RHwyX$ zgZ2&$G~!{Fu_H}84TjMz|J+rd63|B!bzoZCs4Z@=*ZdHTJe@hoq|pTh_XJ%L3E+Ie z>dq^3kdX=%E9=ko&U%md%|E5uW-_=4Oqo$T@EjpvPRrhi7SY%SjujHQR|T zI2=bl$PBPg;;-i`g~Se%9#^Z4o2`v!W}^lx3%s^;6Tpi2U?9b z>R{3$H%k=&-YVT!#EpHt9Ivn6b}*Z%WlS`@-C>M;m!<_r_Vaep(PMq68?B|J7D zjD!+iUU@OI)Cx63Iea|p7w5rh$mSdFI>8}c=IPVPK>>o<~|nw1bX!a;~x1D0XXrQp;= zcKD~OLrj;3uyzgB$vHRb{pActK1wcLH*5R3*2-~UunK|YV6eh>t{EGA+gl5|V=M=e zvR=s_B||b>8A6Qe-aMO>5__e*;;oI%vKokFW^WX@AVEvE+74;5KZm;^d~ydbGsd1R z>4UXly-jXro0WFBvQ}ntX1M0=ssSb$P5BgbFd{{$1-1fLC0y*dk}t`{qVR>+S2V2d zqYz5?Rny6nz3qYri9IEu#L-QYFW~L1298}K%Cr)m+3X}ai}%rIv=U{0(D~>41=mTi zDa5LISYMq{>vSzNwX4Rw~(i<3<;MNGCgHIUc;>Br&;?*!M(>qVGXj53QYEFEw ztMvspNMm@E1}n5MhQM{g`L#?I16>^Y*&>;TDO$6d)41a(ukjXlvLXA#YLs$)vip0C zW{8iN7B9N7vFJ^BxS226ln(v~7J+q|qFqs@+zNuR;G`RaH45CaZp~o4ZBmS(uq zhc2_Rngm*VsNzyS&7E|cAhM55KY}c2`Dg>Cl;r0T{C@4gTddA z5L9ECu{c_}0@ztwx_dXB>L?PB?e zj4K$c2F|)VANgZ^%CT^#_l4)2%YTt~L|+q$}LY zeMa3=>SurrLTHk372AJ~O~BO-2aJ^6!-IX)F7L_QQwCyzk{IiBO2;DF+H~2nJ`7ys zC62x8$A^thu)RJ$zpfM2xG#g!0E-~jUGUOJT2fAc8&Y7KkYhv~J6PtQA3=so2Y<^i zxqF6cq4cX2@~>uso~9YAjbs3vv^@;Xcd0zYhZ&%4IY=@V=bQt4{Bf~uiLU| zxg6I;{(PaZD!-))whBO`W^rR(U*K2Vx0&jH zJTB|jg#JV57U*qEu#TN^{edCU`gv-av+72(c(ETVT)ODeHM;3-t3*8UJKZ`=!It5X z55pW`e{}3|3_m)IlXny7OdXZg_S&;6tO$EB;=f8D;lL7ffv}ahD?Y^rmW;D<a$1NPY|Q`QqyL=Rc0%X#|pw(QEJ3-aajepoCp(=_zRGCx%d&)f$&hm@JIubDs(om(d0fi}vSp*`a=^UC((gyXWSxaR=;` zIP0j|cFlmoe)H*IGsB~63u4DNRy-#|uCs0v?Lj^X`z?!(3^;~;3huHwWO)QJhUju* zo)*i>=C0bG7YMXGWE6yyYI}p>IYf&a{F)>9mbTV%gl-fvz>JH|P*!UAZ)_(*l(^kk zkp2VqH4liHdYz1u3|&aRff>mkJrGTh?7??<)z%HA+oh=WtKm=`jk;==x0QL`)*mhj z^MYI5aEHRLtNhi;vu0oYrx7U-$%D`dynFKTa?A(p~S1U%uIZbdScyv0Dqm=D^0Xp1i zLfv*0*?=)=i0OP)-V?^Z>^?$~VV18KOxK2yFr1M1;^={+JkxaA#SIjGx9L^w?nOhano_IfX-83V z0$K#tj$!aAbn8|T(<*Ga9Sn$0Fmlq+fsb4d!U%X&L0R=dAi|I-y~0CrmrH(EL8m>5 z?8Cy(4N>$R#KGp8D(KAiaYK|ew$>ZZX)ZG5+;7iXe!L1>Ry()P(>fGyQzYa1G*b;_ zAbrI=I{R<|&lE%Hh~7bn#yv;cd~vs1aIXmHG(!I!4)y$ca9LAN8uS{7+AUflhEpm# zer978Go>fR5_pJ<(s4%)Kdqgs5G$z-*CWrOt-+kDPY0Hx1b0pqwSWc$z1!?j!hX`n z>rJ6~^qzbQ!rovH?pM)#sZYBhd7OLjnWym8V8@O2rOka`)Zl-BRmCcfAmPDau{)ZG zeJ>-F%w|fuMU*Db*hvd6UqM}iUTK4mQD$1X=oxvO*FF0cORc=$vXsN~tS)b*t)DkvYw(~- znEetWqYN+}zoMYd4IJsJ7iQPpAi_kLAWkraPfJ5|yvhqNMeLvlFu;4g6Rdqu3?t7& zISSy1Q;Y4**=5c)wp)fhR*E?6^*oJ1G(Ngbzqwuy=cQ;NieTJzdcM!Wv_DtSs|wz2 ztwj%5IHqC)&ZC&^b+kSmTL>T8+6q{EJ-*x3Y$`+8`yOI#Ko^3pP=KI1cmrQYgSH3f ziH#&y*>Dq&pG6wHNDq0{8lV#qj-g|AT9KZl(LM+PO zw^MtD1XzVuTg&q358+A46&tMI$e}ZXX29B+)&tglv^Fny&8~bv>KirB#F>?8K4 zXI(5)?+s=*9kDUGO?rFfl%YDdV28@Fs&METzJuP;SV;`nWAC`LD#Pah$qH=qI^gEP zD+-J*C7U0T{I%#hjDQZTu`MZlYaIe}k!w*93(cVUR@odR6>8|?=ft3x)D7Cd>+~3b zj43WG^vJ>vi{oK{i=fT8SM1@``=DV#GgKCDi0;snn%9QH8lHcL*$p3kZUX^ohrv5u z(W4VrQ-ZK;Q#u>nubwk5kAkUB`U84CA(u)mFH&L8%2{cs`R$4v(58diaS8q2<*e>e z+{e}C4a$bH1)KhfJ=rHJ4+LL|qb(Oq4>}x=lMG|yc{*H|p=u5g(bU3t4m333Fo(-6 zf+-({8cp^+2lLgQg{ZO^94&8MG#wG7sExo(q{TGk5`uI;iawzcq^z+Wo8wXG8%}VVh0NX5Kc*Q>A(S#&=iDKKVD#$q@=-Rk8Km2)j=usN4?XL zU?k2e7Qm9rLAaZzT@I@*Kc5?jWuL%jQmPcJo*Hj>L9mA;Dm&ic(nTqhyWm`gb7@e< zQLpjD9RcFy?8>D;Jz6z~M}+q1eNCN$$;2~va4$!Jovuph;S0u3m=3q}9fSm)tBMT* z^x#fMG~ZvHgrXHh51yMnJb`F{UMgx=m?qN68gQ~c3eu-O43l9bRWP&sgL z*Hs5mF*{xy3gKu(LDqvt+Q4-{_tDYL9r3$gkHMaL#%62P%y1k1B~?lYfKphA?@r$J zQ9?y9L05yQJVD8^F~IfKALBB3L$$Oo4`&1pg4(&G)#@(1uGfSDdQTKE~1&p0IKM#=%@fq_JHUL!vA z{<7|)Hw@?@P!s%$t2b4&?G986#fz$JmZuj2!J$BpgWkIvJHZg;T~X?!RA^~d7l^_N zjnRQa;gND|p7Q83B$8z!dWr9L)V{~0cH|BB+k~r;ZF8t)#w8yn@!Ji7|)X;nNR2mSAW{9N( z(HBB4d(HlQ?N28&%r+a;r1beyvc3qjaCZFe+%=6*A;niFi`iT~Emv2`@ye~54QI^| zg&c#|dYAjsc4=KLX}nZAl;%zZ=MMOo;jU73lpqpbbmy~+cu7a2_7+*F%160Bt>J)R z6!)U*9YuC^fKkw9g$$Ur0>&Ms{fJEzIi3ku0)G&g^Thzv1KYjIE^N6Cle)$vW8pK? z1!Cn6FsTrB=n&6$^4M^Wui_q=Na?uMp!a3OwhbG*{p?J5X!&D~)FoB6Z-~iuU*UUj zu$I_F$7#+WCEu`I?W-ktpa-1X@wCo-3Zhi$SUs6zvxi7Zh*~m_)Y^qA@2TdcUoBEC z;J}jQyeJw^^XK8P2dnw-B>~T`Um%ppPIvF&0!r4SH1|AA-#DN9PT0~$*U%Hg+>AUp zpIfj;9~At8yigZcim^T9Ct$-J0*KucCb>pEtFG;LedCVOUiHPW306D7etXlFZ7ry{ zO>l*SWUh)g*wm!^>ag8oa?5$B8tUmwsxdf4wQM>a%$ibii#*J1Jh_82=<>HnXoARN) zlJ43x?0DMm3?x}am>!0qjHQwA7e?K=0qzU=uizYRyThDe103PtyN zJsz)*J#P0*HbpQKmwvSEsur(D7`xKdbYbP?uAX5o*fu~V*0_=*#LP`tee1w-Qm)n$ z%<#g^j+@H0(m;SIjOrj@nF!Mk_dAx7anfjLg#tTCZUwC6~Uuudns($S

    qhYee#Z21y4?xShl!*m{W>Eaq;D%|~{ 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 <{5j%3KxOKxVq|Qn8@Jc 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

    6b?r>CM zqoQKoR2>T1*<(KYT;mA0#J3G#_s&S_*(5E%9dc*<N=bs8Lt}rXN*`FcQtXVLuTf`1#C$ z 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 literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/img/literals-decoder.png b/xls/modules/zstd/img/literals-decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..225e56017bee42fa76cc4bf183a29356d5018399 GIT binary patch literal 3638136 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