diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..93f15a3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +name: ci + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + verilator-cocotb: + name: Verilator + cocotb tests + runs-on: ubuntu-24.04 + steps: + - name: Checkout (with submodules) + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Verilator build deps + run: | + sudo apt-get update + sudo apt-get install -y \ + git make autoconf flex bison \ + libfl2 libfl-dev zlib1g zlib1g-dev \ + help2man perl ccache + + - name: Cache Verilator install + id: verilator-cache + uses: actions/cache@v4 + with: + path: ~/.local/verilator + key: verilator-v5.040-${{ runner.os }}-x64 + + - name: Build Verilator 5.040 from source (only on cache miss) + if: steps.verilator-cache.outputs.cache-hit != 'true' + run: | + git clone --branch v5.040 --depth 1 \ + https://github.com/verilator/verilator.git /tmp/verilator + cd /tmp/verilator + autoconf + ./configure --prefix=$HOME/.local/verilator + make -j$(nproc) + make install + + - name: Add Verilator to PATH and verify + run: | + echo "$HOME/.local/verilator/bin" >> $GITHUB_PATH + $HOME/.local/verilator/bin/verilator --version + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install cocotb + run: | + python -m pip install --upgrade pip + pip install cocotb cocotb-bus pytest + cocotb-config --version + + - name: Run inner_jib_top tests (end-to-end with sum_ints.asm) + run: | + cd verif/inner_jib_top + make diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26480da --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ + +# verif outputs +verif/.venv +verif/**/sim_build/ +verif/**/results.xml +verif/**/*.vcd +verif/**/*.fst +**/__pycache__/ +*.pyc +.pytest_cache/ diff --git a/.gitmodules b/.gitmodules index d3943c9..cd21f07 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,8 @@ [submodule "mast"] path = mast - url = git@git.pop.coop:pop/MAST.git + # Use HTTPS so GitHub Actions CI (which has no SSH key for git.pop.coop) + # can clone the submodule. Local developers with SSH set up can still + # override via: + # git config submodule.mast.url git@git.pop.coop:pop/MAST.git + url = https://git.pop.coop/pop/MAST.git + branch = main diff --git a/README.md b/README.md index 81e065c..9655e68 100644 --- a/README.md +++ b/README.md @@ -4,22 +4,39 @@ > First Sail of the PopSolutions fleet. The validation tape-out. +[![ci](https://github.com/popsolutions/InnerJib7EA/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/popsolutions/InnerJib7EA/actions/workflows/ci.yml) + **InnerJib7EA** is the first silicon product of PopSolutions Sails. SKU **POPC_16A** — embedded entry single-board RISC-V accelerator with 16 GB DDR5, targeting edge AI inference and on-device fine-tuning. This is a deliberately small first silicon: monolithic die in Skywater 130nm via Google Open MPW shuttle, low cost, low risk. The goal is to validate the -end-to-end design flow (RTL → simulation → synthesis → P&R → tape-out → driver -→ application) with the smallest possible blast radius. Lessons from -InnerJib7EA inform the chiplet-based ForeTopsail7EA and MainTopsail7EA that -follow. +end-to-end design flow with the smallest possible blast radius. ## Status -Starting (2026-05). RTL integration in progress. See open issues. +**Sprint C (`inner_jib_top.sv` end-to-end integration) landed.** A real +RISC-V program (`mast/examples/direct/sum_ints.asm`) now runs through: + +``` +upstream core.sv -> core_axi4_adapter -> axi4_master_simple -> axi4_mem_model +``` + +The cocotb test pre-populates instruction memory through the mem model's +loader back-door (added in [MAST#8](https://github.com/popsolutions/MAST/pull/8)), +sets the core's PC, and asserts ena. The core then fetches instructions, +executes the loop, and emits the expected output sequence `[0, 5, 4, 3, 2, +1, 0, 15]` before halting. + +This is the first program of any kind running on PopSolutions silicon- +equivalent hardware in simulation. Every subsequent program (factorial, +primes, matrix multiply, eventually GGML kernels) lands on the same path. -## Quick spec (target — to be locked via ADR in this repo) +See [`docs/adr/0001-spec.md`](docs/adr/0001-spec.md) for the locked POPC_16A +specification. + +## Quick spec | Parameter | Target | |---|---| @@ -28,44 +45,57 @@ Starting (2026-05). RTL integration in progress. See open issues. | DRAM | 16 GB DDR5-4800 SO-DIMM (single channel) | | Host | PCIe Gen4 x4 (via LitePCIe) | | TDP | < 25 W | -| Form factor | Mini-ITX SBC + M.2 accelerator variant | +| Form factor | M.2 22110 NGFF accelerator card | | Reference workload | GGML int4 inference of TinyLlama-1.1B | | BOM target | R$ 800–1500 | -## How this repo relates to MAST +## Run the testbench locally + +One-time setup: + +```bash +git clone --recursive git@git.pop.coop:pop/InnerJib7EA.git +cd InnerJib7EA +~/.pyenv/versions/3.12.10/bin/python3 -m venv mast/verif/.venv +source mast/verif/.venv/bin/activate +pip install cocotb cocotb-bus pytest +deactivate +ln -s ../mast/verif/.venv verif/.venv +``` + +Run: + +```bash +source verif/.venv/bin/activate +cd verif/inner_jib_top +make +``` + +Expected output ends with: + +``` +** TESTS=2 PASS=2 FAIL=0 SKIP=0 ** +``` + +## Relationship to MAST InnerJib7EA vendors [`popsolutions/MAST`](https://github.com/popsolutions/MAST) as a git submodule under `mast/`. MAST holds the shared IP (RISC-V core, -compute unit, memory controller, AXI4 interconnect, verification harness). -This repo holds only product-specific integration: top-level Verilog, -configuration, PCB design, datasheets, product tests. +compute unit, AXI4 subsystem, verification harness). This repo holds only +product-specific integration: top-level Verilog (`src/inner_jib_top.sv`), +spec ADRs, eventually PCB design files, datasheets, product tests. -When InnerJib7EA tape-outs to silicon, the MAST submodule is frozen at the -specific MAST release used. That submodule pin is the reproducibility -contract. +When InnerJib7EA tape-outs to silicon, the MAST submodule pin is frozen at +the specific MAST release used. That pin is the reproducibility contract. ## License Same dual-license model as MAST. See -[`popsolutions/MAST/NOTICE.md`](https://github.com/popsolutions/MAST/blob/main/NOTICE.md): - -- Hardware contributions: CERN-OHL-S v2 (commercial dual-license available) -- Software contributions: Apache 2.0 -- Documentation: CC-BY-SA 4.0 +[`mast/NOTICE.md`](https://github.com/popsolutions/MAST/blob/main/NOTICE.md). ## Contributing -See [`popsolutions/MAST/CONTRIBUTING.md`](https://github.com/popsolutions/MAST/blob/main/CONTRIBUTING.md). +See [`mast/CONTRIBUTING.md`](https://github.com/popsolutions/MAST/blob/main/CONTRIBUTING.md) +and the cooperative-affiliate-only policy in +[`mast/GOVERNANCE.md`](https://github.com/popsolutions/MAST/blob/main/GOVERNANCE.md). DCO sign-off required on every commit (`git commit -s`). - -## Roadmap - -See open issues. Major milestones for InnerJib7EA: - -1. Lock spec (this repo, ADR-001-spec) -2. Top-level Verilog integration with MAST submodule -3. Verilator simulation runs end-to-end (TinyLlama-1.1B inference) -4. RTL synthesis area/timing report -5. Skywater 130nm Open MPW shuttle submission -6. First silicon validation -7. Driver + GGML backend hand-off to Spanker7EA diff --git a/mast b/mast index 82921d1..9938d23 160000 --- a/mast +++ b/mast @@ -1 +1 @@ -Subproject commit 82921d13216dc635607bbc8de257419676cd2e54 +Subproject commit 9938d2334f28f8e376027318c28b1e0d4c675ad5 diff --git a/src/inner_jib_top.sv b/src/inner_jib_top.sv new file mode 100644 index 0000000..5bed2d4 --- /dev/null +++ b/src/inner_jib_top.sv @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: CERN-OHL-S-2.0 +// Copyright (c) 2026 PopSolutions Cooperative +// +// InnerJib7EA top-level: 1 upstream RISC-V core wired through +// MAST's AXI4 chain to a 256-line internal SRAM. Host loads the +// program through the mem_model's back-door loader port; once +// the program is in memory, a set_pc + ena pulse releases the +// core to execute it. +// +// This is the simulation top-level. The FPGA / silicon top-level +// will replace axi4_mem_model with a LiteDRAM controller and wire +// loader_en to a bootrom unrolling state machine; the core / +// adapter / master chain is unchanged from this module. + +`default_nettype none + +module inner_jib_top ( + input wire clk, + input wire rst_n, + + // Core control + input wire ena, + input wire set_pc_req, + input wire [addr_width-1:0] set_pc_addr, + + // Core observation + output wire halt, + output wire [data_width-1:0] out, + output wire outen, + output wire outflen, + + // Program loader pass-through + input wire loader_en, + input wire [phys_addr_width-1:0] loader_addr, + input wire [data_width-1:0] loader_data +); + + // Core ↔ adapter (upstream bespoke memory iface) + wire core_mem_rd_req; + wire core_mem_wr_req; + wire [addr_width-1:0] core_mem_addr; + wire [data_width-1:0] core_mem_rd_data; + wire [data_width-1:0] core_mem_wr_data; + wire core_mem_busy; + wire core_mem_ack; + + // Adapter ↔ master (simple req/resp) + wire ma_we; + wire [phys_addr_width-1:0] ma_addr; + wire [mem_data_width-1:0] ma_wdata; + wire [axi4_strb_width-1:0] ma_wstrb; + wire ma_start; + wire ma_busy; + wire ma_done; + wire [mem_data_width-1:0] ma_rdata; + wire ma_err; + + // Master ↔ mem (AXI4) + wire [axi4_id_width-1:0] ax_awid; + wire [phys_addr_width-1:0] ax_awaddr; + wire [axi4_len_width-1:0] ax_awlen; + wire [axi4_size_width-1:0] ax_awsize; + wire [axi4_burst_width-1:0] ax_awburst; + wire ax_awvalid; + wire ax_awready; + wire [mem_data_width-1:0] ax_wdata; + wire [axi4_strb_width-1:0] ax_wstrb; + wire ax_wlast; + wire ax_wvalid; + wire ax_wready; + wire [axi4_id_width-1:0] ax_bid; + wire [axi4_resp_width-1:0] ax_bresp; + wire ax_bvalid; + wire ax_bready; + wire [axi4_id_width-1:0] ax_arid; + wire [phys_addr_width-1:0] ax_araddr; + wire [axi4_len_width-1:0] ax_arlen; + wire [axi4_size_width-1:0] ax_arsize; + wire [axi4_burst_width-1:0] ax_arburst; + wire ax_arvalid; + wire ax_arready; + wire [axi4_id_width-1:0] ax_rid; + wire [mem_data_width-1:0] ax_rdata; + wire [axi4_resp_width-1:0] ax_rresp; + wire ax_rlast; + wire ax_rvalid; + wire ax_rready; + + // Upstream RISC-V core + core core1 ( + .rst(rst_n), + .clk(clk), + .clr(1'b0), // no synchronous reset; rst_n handles init + .ena(ena), + .set_pc_req(set_pc_req), + .set_pc_addr(set_pc_addr), + + .out(out), + .outen(outen), + .outflen(outflen), + .halt(halt), + + .mem_addr(core_mem_addr), + .mem_rd_data(core_mem_rd_data), + .mem_wr_data(core_mem_wr_data), + .mem_wr_req(core_mem_wr_req), + .mem_rd_req(core_mem_rd_req), + .mem_ack(core_mem_ack), + .mem_busy(core_mem_busy) + ); + + // Bridge core ↔ AXI4 + core_axi4_adapter adapter ( + .clk(clk), .rst_n(rst_n), + .core_mem_rd_req(core_mem_rd_req), .core_mem_wr_req(core_mem_wr_req), + .core_mem_addr(core_mem_addr), + .core_mem_rd_data(core_mem_rd_data), .core_mem_wr_data(core_mem_wr_data), + .core_mem_busy(core_mem_busy), .core_mem_ack(core_mem_ack), + .m_req_we(ma_we), .m_req_addr(ma_addr), .m_req_wdata(ma_wdata), + .m_req_wstrb(ma_wstrb), .m_req_start(ma_start), + .m_req_busy(ma_busy), .m_req_done(ma_done), + .m_req_rdata(ma_rdata), .m_req_err(ma_err) + ); + + // AXI4 master + axi4_master_simple master ( + .clk(clk), .rst_n(rst_n), + .req_we(ma_we), .req_addr(ma_addr), + .req_wdata(ma_wdata), .req_wstrb(ma_wstrb), .req_start(ma_start), + .req_busy(ma_busy), .req_done(ma_done), + .req_rdata(ma_rdata), .req_err(ma_err), + .m_awid(ax_awid), .m_awaddr(ax_awaddr), .m_awlen(ax_awlen), + .m_awsize(ax_awsize), .m_awburst(ax_awburst), + .m_awvalid(ax_awvalid), .m_awready(ax_awready), + .m_wdata(ax_wdata), .m_wstrb(ax_wstrb), .m_wlast(ax_wlast), + .m_wvalid(ax_wvalid), .m_wready(ax_wready), + .m_bid(ax_bid), .m_bresp(ax_bresp), + .m_bvalid(ax_bvalid), .m_bready(ax_bready), + .m_arid(ax_arid), .m_araddr(ax_araddr), .m_arlen(ax_arlen), + .m_arsize(ax_arsize), .m_arburst(ax_arburst), + .m_arvalid(ax_arvalid), .m_arready(ax_arready), + .m_rid(ax_rid), .m_rdata(ax_rdata), .m_rresp(ax_rresp), + .m_rlast(ax_rlast), .m_rvalid(ax_rvalid), .m_rready(ax_rready) + ); + + // 256-line internal SRAM (= 8 KB) with loader back-door + axi4_mem_model #(.DEPTH_WORDS(256)) mem ( + .clk(clk), .rst_n(rst_n), + .s_awid(ax_awid), .s_awaddr(ax_awaddr), .s_awlen(ax_awlen), + .s_awsize(ax_awsize), .s_awburst(ax_awburst), + .s_awvalid(ax_awvalid), .s_awready(ax_awready), + .s_wdata(ax_wdata), .s_wstrb(ax_wstrb), .s_wlast(ax_wlast), + .s_wvalid(ax_wvalid), .s_wready(ax_wready), + .s_bid(ax_bid), .s_bresp(ax_bresp), + .s_bvalid(ax_bvalid), .s_bready(ax_bready), + .s_arid(ax_arid), .s_araddr(ax_araddr), .s_arlen(ax_arlen), + .s_arsize(ax_arsize), .s_arburst(ax_arburst), + .s_arvalid(ax_arvalid), .s_arready(ax_arready), + .s_rid(ax_rid), .s_rdata(ax_rdata), .s_rresp(ax_rresp), + .s_rlast(ax_rlast), .s_rvalid(ax_rvalid), .s_rready(ax_rready), + .loader_en(loader_en), + .loader_addr(loader_addr), + .loader_data(loader_data) + ); + +endmodule diff --git a/verif/inner_jib_top/Makefile b/verif/inner_jib_top/Makefile new file mode 100644 index 0000000..044d6ba --- /dev/null +++ b/verif/inner_jib_top/Makefile @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +TOPLEVEL_LANG ?= verilog +SIM ?= verilator +WAVES ?= 0 + +ifeq ($(WAVES),1) +EXTRA_ARGS += --trace --trace-structs +endif +EXTRA_ARGS += -Wno-WIDTH -Wno-WIDTHEXPAND -Wno-WIDTHTRUNC -Wno-UNUSEDSIGNAL -Wno-UNUSEDPARAM \ + -Wno-ASCRANGE -Wno-CASEINCOMPLETE -Wno-INITIALDLY \ + -Wno-MULTIDRIVEN -Wno-LATCH -Wno-IMPLICIT -Wno-COMBDLY \ + -Wno-PROCASSWIRE -Wno-UNOPTFLAT \ + -Wno-IMPLICITSTATIC + +PROJECT_ROOT = $(shell git rev-parse --show-toplevel) +MAST = $(PROJECT_ROOT)/mast + +VERILOG_SOURCES = \ + $(MAST)/src/assert.sv \ + $(MAST)/src/op_const.sv \ + $(MAST)/src/const.sv \ + $(MAST)/src/int/int_div_regfile.sv \ + $(MAST)/src/float/float_params.sv \ + $(MAST)/src/float/float_add_pipeline.sv \ + $(MAST)/src/int/chunked_add_task.sv \ + $(MAST)/src/int/chunked_sub_task.sv \ + $(MAST)/src/generated/mul_pipeline_cycle_24bit_2bpc.sv \ + $(MAST)/src/float/float_mul_pipeline.sv \ + $(MAST)/src/generated/mul_pipeline_cycle_32bit_2bpc.sv \ + $(MAST)/src/int/mul_pipeline_32bit.sv \ + $(MAST)/src/core.sv \ + $(MAST)/src/popsolutions/axi4/axi4_const.sv \ + $(MAST)/src/popsolutions/axi4/axi4_mem_model.sv \ + $(MAST)/src/popsolutions/axi4/axi4_master_simple.sv \ + $(MAST)/src/popsolutions/axi4/core_axi4_adapter.sv \ + $(PROJECT_ROOT)/src/inner_jib_top.sv + +TOPLEVEL = inner_jib_top +MODULE = test_inner_jib_top + +include $(shell cocotb-config --makefiles)/Makefile.sim diff --git a/verif/inner_jib_top/fixtures/README.md b/verif/inner_jib_top/fixtures/README.md new file mode 100644 index 0000000..d16fe45 --- /dev/null +++ b/verif/inner_jib_top/fixtures/README.md @@ -0,0 +1,44 @@ + + +# Test fixtures for `inner_jib_top` + +## sum_ints.hex + +Pre-assembled hex of `mast/examples/direct/sum_ints.asm`, byte-aligned at +offset 128 (matches `PROG_BASE_ADDR` in the cocotb test). + +**Regenerate with:** + +```bash +# from this fixtures/ directory: +python3 ../../../mast/verigpu/assembler.py \ + --in-asm ../../../mast/examples/direct/sum_ints.asm \ + --offset 128 \ + --out-hex sum_ints.hex +``` + +The hex is **checked into the repo** rather than regenerated by CI so +that an upstream encoding change produces a clear, intentional test +failure (rather than silently re-aligning the test to a moving spec). +When you change `sum_ints.asm` upstream, regenerate the fixture +deliberately and update the expected output sequence in +`test_inner_jib_top.py` if the program semantics changed. + +## Expected output sequence + +`test_sum_ints_runs_and_halts` asserts the captured `out` sequence +equals `[0, 5, 4, 3, 2, 1, 0, 15]`. Hand-trace from the asm source: + +``` +li x1, 5 ; x1 = 5 +li x2, 0 ; x2 = 0 +outr x2 ; emit 0 +loop: + outr x1 ; emit 5, then 4, 3, 2, 1 + add x2, x1, x2 ; x2 += x1 + addi x1, x1, -1 ; x1 -= 1 + bne x1, x0, loop ; loop while x1 != 0 +outr x1 ; emit 0 +outr x2 ; emit 15 (= 0+5+4+3+2+1) +halt +``` diff --git a/verif/inner_jib_top/fixtures/sum_ints.hex b/verif/inner_jib_top/fixtures/sum_ints.hex new file mode 100644 index 0000000..ae3cb55 --- /dev/null +++ b/verif/inner_jib_top/fixtures/sum_ints.hex @@ -0,0 +1,20 @@ +00500093 +00000113 +000f4fb7 +240f8f93 +002fa023 +000f4fb7 +240f8f93 +001fa023 +00208133 +fff08093 +fe0096e3 +000f4fb7 +240f8f93 +001fa023 +000f4fb7 +240f8f93 +002fa023 +000f4fb7 +244f8f93 +01efa023 diff --git a/verif/inner_jib_top/test_inner_jib_top.py b/verif/inner_jib_top/test_inner_jib_top.py new file mode 100644 index 0000000..693aac1 --- /dev/null +++ b/verif/inner_jib_top/test_inner_jib_top.py @@ -0,0 +1,129 @@ +# SPDX-License-Identifier: Apache-2.0 +"""End-to-end Sprint C test: a real RISC-V program (sum_ints) running +on the upstream core, fetching instructions through MAST's AXI4 chain, +producing the expected output sequence on the core's `out`/`outen` ports. + +This is the validation milestone that proves the full integration: + upstream core → core_axi4_adapter → axi4_master_simple → axi4_mem_model + +If this test passes, every subsequent program (factorial, primes, +matrix multiply, eventually GGML kernels) lands on the same path. +""" +import os +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import RisingEdge + +CLK_PERIOD_NS = 10 +PROG_BASE_ADDR = 128 # matches the assembler --offset 128 we used + + +def fixture_path(): + """Locate the pre-assembled sum_ints.hex shipped with this testbench.""" + here = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(here, "fixtures", "sum_ints.hex") + + +def load_hex_words(path): + with open(path) as f: + return [int(line.strip(), 16) for line in f if line.strip()] + + +async def reset_dut(dut): + dut.rst_n.value = 0 + dut.ena.value = 0 + dut.set_pc_req.value = 0 + dut.set_pc_addr.value = 0 + dut.loader_en.value = 0 + dut.loader_addr.value = 0 + dut.loader_data.value = 0 + for _ in range(8): + await RisingEdge(dut.clk) + dut.rst_n.value = 1 + for _ in range(2): + await RisingEdge(dut.clk) + + +async def load_program(dut, words, base_addr): + """Push each 32-bit word into the mem_model via the back-door loader.""" + for i, word in enumerate(words): + dut.loader_en.value = 1 + dut.loader_addr.value = base_addr + i * 4 + dut.loader_data.value = word + await RisingEdge(dut.clk) + dut.loader_en.value = 0 + await RisingEdge(dut.clk) + + +async def start_core_at(dut, pc): + dut.set_pc_addr.value = pc + dut.set_pc_req.value = 1 + await RisingEdge(dut.clk) + dut.set_pc_req.value = 0 + dut.ena.value = 1 + await RisingEdge(dut.clk) + + +@cocotb.test() +async def test_reset(dut): + """After reset, halt is low, no output activity.""" + cocotb.start_soon(Clock(dut.clk, CLK_PERIOD_NS, unit="ns").start()) + await reset_dut(dut) + assert int(dut.halt.value) == 0 + assert int(dut.outen.value) == 0 + assert int(dut.outflen.value) == 0 + + +@cocotb.test() +async def test_sum_ints_runs_and_halts(dut): + """Run sum_ints.asm end-to-end; expect [0, 5, 4, 3, 2, 1, 0, 15] then halt. + + The expected output sequence comes from a hand-trace of sum_ints.asm: + li x1, 5 # x1 = 5 + li x2, 0 # x2 = 0 + outr x2 # emit 0 + loop: + outr x1 # emit 5, then 4, 3, 2, 1 + add x2, x1, x2 # x2 += x1 + addi x1, x1, -1 + bne x1, x0, loop # exit when x1 == 0 + outr x1 # emit final x1 (0) + outr x2 # emit final x2 (0+5+4+3+2+1 = 15) + halt + """ + cocotb.start_soon(Clock(dut.clk, CLK_PERIOD_NS, unit="ns").start()) + await reset_dut(dut) + + words = load_hex_words(fixture_path()) + assert len(words) > 0, "fixture hex is empty" + + await load_program(dut, words, PROG_BASE_ADDR) + await start_core_at(dut, PROG_BASE_ADDR) + + # Capture out values until halt, with a generous cycle bound + captured_int = [] + captured_float = [] + halted = False + for _ in range(50000): + await RisingEdge(dut.clk) + if int(dut.outen.value): + captured_int.append(int(dut.out.value)) + if int(dut.outflen.value): + captured_float.append(int(dut.out.value)) + if int(dut.halt.value): + halted = True + break + + assert halted, ( + f"core did not halt within 50000 cycles\n" + f" captured int outputs: {captured_int}\n" + f" captured float outputs: {captured_float}" + ) + + expected = [0, 5, 4, 3, 2, 1, 0, 15] + assert captured_int == expected, ( + f"out sequence mismatch:\n" + f" expected {expected}\n" + f" got {captured_int}" + ) + assert captured_float == [], f"unexpected float outputs: {captured_float}"